<commit_before>var debug = require('debug')('liftie:twitter');
var superagent = require('superagent');
var millis = require('../tools/millis');
var limiter = require('../tools/limiter');

var twitterBearerToken = process.env.TWITTER_TOKEN;

// no more than 300 calls per 15 minutes
var limit = limiter(300, 15 * millis.minute);

module.exports = fetch;
module.exports.interval = {
  active: 5 * millis.minute,
  inactive: 6 * millis.hour
};

function trim(tweet) {
  return ['id_str', 'created_at', 'text', 'entities'].reduce(function(r, prop) {
    r[prop] = tweet[prop];
    return r;
  }, {});
}

// API docs: https://dev.twitter.com/docs/api/1.1/get/statuses/user_timeline
function fetch(resort, fn) {
  if (!twitterBearerToken) {
    debug('Configure Twitter bearer token.');
    return process.nextTick(fn);
  }

  if (!resort.twitter) {
    // debug('Twitter handle for %s missing', resort.id);
    return process.nextTick(fn);
  }

  limit(function(err) {
    if (err) {
      debug('Twitter API limit %s', resort.id);
      return fn(err);
    }
    superagent('https://api.twitter.com/1.1/statuses/user_timeline.json')
      .query({
        screen_name: resort.twitter,
        count: 6,
        trim_user: 1,
        exclude_replies: 1,
        include_rts: 0,
        include_entities: 1
      })
      .set('Authorization', 'Bearer ' + twitterBearerToken)
      .end(function(err, res) {
        if (err) {
          return fn(err.status);
        }
        var tweets = res.body.map(trim);
        if (tweets.length > 3) {
          tweets.length = 3;
        }
        debug('Tweets for %s - %j', resort.id, tweets);
        fn(null, {
          user: resort.twitter,
          tweets: tweets
        });
      });
  });
}<commit_msg>add supports for >140 tweets<commit_after>var debug = require('debug')('liftie:twitter');
var superagent = require('superagent');
var millis = require('../tools/millis');
var limiter = require('../tools/limiter');

var twitterBearerToken = process.env.TWITTER_TOKEN;

// no more than 300 calls per 15 minutes
var limit = limiter(300, 15 * millis.minute);

module.exports = fetch;
module.exports.interval = {
  active: 5 * millis.minute,
  inactive: 6 * millis.hour
};

function trim(tweet) {
  var r = {
    text: tweet.full_text || tweet.text,
  };
  ['id_str', 'created_at', 'entities'].forEach(function(prop) {
    r[prop] = tweet[prop];
  });
  return r;
}

// API docs: https://dev.twitter.com/docs/api/1.1/get/statuses/user_timeline
function fetch(resort, fn) {
  if (!twitterBearerToken) {
    debug('Configure Twitter bearer token.');
    return process.nextTick(fn);
  }

  if (!resort.twitter) {
    // debug('Twitter handle for %s missing', resort.id);
    return process.nextTick(fn);
  }

  limit(function(err) {
    if (err) {
      debug('Twitter API limit %s', resort.id);
      return fn(err);
    }
    superagent('https://api.twitter.com/1.1/statuses/user_timeline.json')
      .query({
        screen_name: resort.twitter,
        count: 6,
        trim_user: 1,
        exclude_replies: 1,
        include_rts: 0,
        include_entities: 1,
        tweet_mode: 'extended'
      })
      .set('Authorization', 'Bearer ' + twitterBearerToken)
      .end(function(err, res) {
        if (err) {
          return fn(err.status);
        }
        var tweets = res.body.map(trim);
        if (tweets.length > 3) {
          tweets.length = 3;
        }
        debug('Tweets for %s - %j', resort.id, tweets);
        fn(null, {
          user: resort.twitter,
          tweets: tweets
        });
      });
  });
}
<|endoftext|>


<commit_before>var https = require("https");

/*
 * Node-urban-airship is a simple wrapper for the Urban Airship API.
 *
 *      _..--=--..._
 *   .-'            '-.  .-.
 *  /.'              '.\/  /
 * |=-                -=| (
 *  \'.              .'/\  \
 *   '-.,_____ _____.-'  '-'
 * jgs    [_____]=8
 *
 * @params
 *	key - Your API key
 *	secret - Your secret key
 */
var UrbanAirship = module.exports = function(key, secret, master) {
	this._key = key;
	this._secret = secret;
	this._master = master;
}

/*
 * Push a notification to a registered device.
 *
 * @params
 *	path of the push Notification.
 *	payload the object being sent to Urban Airship as specified http://urbanairship.com/docs/push.html
 *	callback
 */
UrbanAirship.prototype.pushNotification = function(path, payload, callback) {
	this._auth = new Buffer(this._key + ":" + this._master, "utf8").toString("base64");
	this._transport(path, "POST", payload, callback);
}

/*
 * Register a new device.
 *
 * @params
 *	device_id - The device identifier
 *	data - The JSON payload (optional)
 *	callback
 */
UrbanAirship.prototype.registerDevice = function(device_id, data, callback) {
	this._auth = new Buffer(this._key + ":" + this._secret, "utf8").toString("base64");

	var path = "/api/device_tokens/" + device_id;

	if (data) {
		// Registration with optional data
		this._transport(path, "PUT", data, callback);
	}
	else {
		// Simple registration with no additional data
		this._transport(path, "PUT", callback);
	}
}

/*
 * Unregister a device.
 *
 * @params
 *	device_id - The device identifier
 *	callback
 */
UrbanAirship.prototype.unregisterDevice = function(device_id, callback) {
	this._auth = new Buffer(this._key + ":" + this._secret, "utf8").toString("base64");
	this._transport("/api/device_tokens/" + device_id, "DELETE", callback);
}

/*
 * Send things to UA!
 *
 * @params
 *	path - API route
 *	method - The HTTPS method to employ
 *	request_data - The JSON data we are sending (optional)
 *	callback
 */
UrbanAirship.prototype._transport = function(path, method, request_data, callback) {
	var self = this,
		rd = "",
		response_data = "",
		https_opts = {
			"host": "go.urbanairship.com",
			"port": 443,
			"path": path,
			"method": method,
			"headers": {
				"Authorization": "Basic " + this._auth
			}
		};

	// We don't necessarily send data
	if (request_data instanceof Function) {
		callback = request_data;
		request_data = null;
	}

	// Set a Content-Type and Content-Length header if we are sending data
	if (request_data) {
		rd = JSON.stringify(request_data);

		https_opts.headers["Content-Type"] = "application/json";
		https_opts.headers["Content-Length"] = Buffer.byteLength(rd, "utf8");
	}
	else {
		https_opts.headers["Content-Length"] = 0;
	}

	var request = https.request(https_opts, function(response) {
		response.setEncoding("utf8");

		response.on("data", function(chunk) {
			response_data += chunk;
		});

		response.on("end", function() {
			// Success on 200 or 204
			if (response.statusCode === 200 || response.statusCode === 204) {
				callback();
			}
			else {
				callback(new Error(response_data));
			}
		});

		response.on("error", function(error) {
			callback(error);
		});
	});

	request.on("error", function(error) {
		callback(error);
	});

	if (request_data) {
		request.write(rd);
	}

	request.end();
}
<commit_msg>[feature] handles 301 and 302<commit_after>var https = require("https");

/*
 * Node-urban-airship is a simple wrapper for the Urban Airship API.
 *
 *      _..--=--..._
 *   .-'            '-.  .-.
 *  /.'              '.\/  /
 * |=-                -=| (
 *  \'.              .'/\  \
 *   '-.,_____ _____.-'  '-'
 * jgs    [_____]=8
 *
 * @params
 *	key - Your API key
 *	secret - Your secret key
 */
var UrbanAirship = module.exports = function(key, secret, master) {
	this._key = key;
	this._secret = secret;
	this._master = master;
}

/**
 * Gets the number of devices tokens authenticated with the application.
 *
 * @params callback(error, total, active)
 */
UrbanAirship.prototype.getDeviceTokenCounts = function(callback) {
	this._auth = new Buffer(this._key + ":" + this._master, "utf8").toString("base64");

	this._transport("/api/device_tokens/count/", "GET", function(error, response_data) {
		callback(error, response_data.device_tokens_count || 0, response_data.active_device_tokens_count || 0);
	});
}

/*
 * Push a notification to a registered device.
 *
 * @params
 *	path of the push Notification.
 *	payload the object being sent to Urban Airship as specified http://urbanairship.com/docs/push.html
 *	callback
 */
UrbanAirship.prototype.pushNotification = function(path, payload, callback) {
	this._auth = new Buffer(this._key + ":" + this._master, "utf8").toString("base64");
	this._transport(path, "POST", payload, callback);
}

/*
 * Register a new device.
 *
 * @params
 *	device_id - The device identifier
 *	data - The JSON payload (optional)
 *	callback
 */
UrbanAirship.prototype.registerDevice = function(device_id, data, callback) {
	this._auth = new Buffer(this._key + ":" + this._secret, "utf8").toString("base64");

	var path = "/api/device_tokens/" + device_id;

	if (data) {
		// Registration with optional data
		this._transport(path, "PUT", data, callback);
	}
	else {
		// Simple registration with no additional data
		this._transport(path, "PUT", callback);
	}
}

/*
 * Unregister a device.
 *
 * @params
 *	device_id - The device identifier
 *	callback
 */
UrbanAirship.prototype.unregisterDevice = function(device_id, callback) {
	this._auth = new Buffer(this._key + ":" + this._secret, "utf8").toString("base64");
	this._transport("/api/device_tokens/" + device_id, "DELETE", callback);
}

/*
 * Send things to UA!
 *
 * @params
 *	path - API route
 *	method - The HTTPS method to employ
 *	request_data - The JSON data we are sending (optional)
 *	callback
 */
UrbanAirship.prototype._transport = function(path, method, request_data, callback) {
	var self = this,
		rd = "",
		response_data = "",
		https_opts = {
			"host": "go.urbanairship.com",
			"port": 443,
			"path": path,
			"method": method,
			"headers": {
				"Authorization": "Basic " + this._auth
			}
		};

	// We don't necessarily send data
	if (request_data instanceof Function) {
		callback = request_data;
		request_data = null;
	}

	// Set a Content-Type and Content-Length header if we are sending data
	if (request_data) {
		rd = JSON.stringify(request_data);

		https_opts.headers["Content-Type"] = "application/json";
		https_opts.headers["Content-Length"] = Buffer.byteLength(rd, "utf8");
	}
	else {
		https_opts.headers["Content-Length"] = 0;
	}

	var request = https.request(https_opts, function(response) {
		response.setEncoding("utf8");

		response.on("data", function(chunk) {
			response_data += chunk;
		});

		response.on("end", function() {
			// You probably forget the trailing '/'
			if ((response.statusCode == 301 || response.statusCode == 302) && response.headers && response.headers.location) {
				var url = require("url"),
					parsed_url = url.parse(response.headers.location);

				self._transport(parsed_url.pathname + (parsed_url.search || ""), method, request_data, callback);
			}
			// Success on 200 or 204, 201 on new device registration
			else if ([200,201,204].indexOf(response.statusCode) >= 0) {
				try {
					callback(null, response_data !== "" && JSON.parse(response_data));
				}
				catch (ex) {
					callback(ex);
				}
			}
			else {
				callback(new Error(response_data));
			}
		});

		response.on("error", function(error) {
			callback(error);
		});
	});

	request.on("error", function(error) {
		callback(error);
	});

	if (request_data) {
		request.write(rd);
	}

	request.end();
}
<|endoftext|>


<commit_before>/* eslint prefer-spread:1 */
'use strict';

/**
 * @todo
 * 		- use 'fmt' object definitions
 * 		- use clean-stack trace?
 * 		- need prettyjson?
 * 		- use es2015
 */

const pretty = require('prettyjson');
const chalk = require('chalk');
const time = require('../fn').getTime;

/**
 * Apply args to the `console[method]` & Add a date stamp.
 * Bind `this` to an object with the following options:
 *
 * @param  {String} args.date     The color string to use for the date
 * @param  {String} args.method   The `console` method to use
 * @param  {String} args.custom   The custom styling to append to args
 */
function stamp() {
	const args = [].slice.call(arguments);

	// if (process.env.DEBUG) {
	// 	return _.apply(_, args);
	// }

	// print the curr time.
	process.stdout.write(`[${chalk[this.color](time())}]`);

	// apply arguments to `console` method
	console[this.method].apply(console, (this.custom ? [this.custom].concat(args) : args));
}

/**
 * Logging Utilities
 */

function log() {
	stamp.apply({method: 'log', color: 'magenta'}, arguments);
	return this;
}

function error() {
	stamp.apply({method: 'error', color: 'red'}, arguments);
	return this;
}

function alert() {
	if (process.env.VERBOSE) {
		stamp.apply({
			custom: chalk.yellow.bold('%s'),
			color: 'yellow',
			method: 'log'
		}, arguments);
	}
	return this;
}

/**
 * PrettyJSON wrapper & stack tracer
 * @param  {Object} e   The Error object
 */
function trace(e) {
	const msg = pretty.render(e)
		.replace(/(\sFunction|\sObject)\./g, `${chalk.blue('$1')}.`)
		.replace(/\((~?\/.*)\)/g, `(${chalk.gray('$1')})`)
		.replace(/:([0-9]*):([0-9]*)/g, ` ${chalk.yellow('$1')}:${chalk.yellow('$2')}`)
		.replace(new RegExp(process.env.HOME, 'g'), '~');
	console.error(msg);
}

module.exports = {
	trace: trace,
	alert: alert,
	error: error,
	log: log
};
<commit_msg>use node's os.homedir() helper; resolves #101<commit_after>/* eslint prefer-spread:1 */
'use strict';

/**
 * @todo
 * 		- use 'fmt' object definitions
 * 		- use clean-stack trace?
 * 		- need prettyjson?
 * 		- use es2015
 */

const homedir = require('os').homedir;
const pretty = require('prettyjson');
const chalk = require('chalk');
const time = require('../fn').getTime;

/**
 * Apply args to the `console[method]` & Add a date stamp.
 * Bind `this` to an object with the following options:
 *
 * @param  {String} args.date     The color string to use for the date
 * @param  {String} args.method   The `console` method to use
 * @param  {String} args.custom   The custom styling to append to args
 */
function stamp() {
	const args = [].slice.call(arguments);

	// if (process.env.DEBUG) {
	// 	return _.apply(_, args);
	// }

	// print the curr time.
	process.stdout.write(`[${chalk[this.color](time())}]`);

	// apply arguments to `console` method
	console[this.method].apply(console, (this.custom ? [this.custom].concat(args) : args));
}

/**
 * Logging Utilities
 */

function log() {
	stamp.apply({method: 'log', color: 'magenta'}, arguments);
	return this;
}

function error() {
	stamp.apply({method: 'error', color: 'red'}, arguments);
	return this;
}

function alert() {
	if (process.env.VERBOSE) {
		stamp.apply({
			custom: chalk.yellow.bold('%s'),
			color: 'yellow',
			method: 'log'
		}, arguments);
	}
	return this;
}

/**
 * PrettyJSON wrapper & stack tracer
 * @param  {Object} e   The Error object
 */
function trace(e) {
	const msg = pretty.render(e)
		.replace(/(\sFunction|\sObject)\./g, `${chalk.blue('$1')}.`)
		.replace(/\((~?\/.*)\)/g, `(${chalk.gray('$1')})`)
		.replace(/:([0-9]*):([0-9]*)/g, ` ${chalk.yellow('$1')}:${chalk.yellow('$2')}`)
		.replace(new RegExp(homedir(), 'g'), '~');
	console.error(msg);
}

module.exports = {
	trace: trace,
	alert: alert,
	error: error,
	log: log
};
<|endoftext|>


<commit_before>'use strict';

var spawn = require('child_process').spawn,
	_ = require('underscore'),
	path = require('path');

var isWin = process.platform === 'win32';

exports.exec = function(cmd, options, callback) {
	callback = _(callback).once();

	var spawnOptions = _(options).clone();

	if (options.log) spawnOptions.stdio = 'inherit';

	var shell, args;

	if (isWin) {
		shell = process.env.comspec || 'cmd';
		args = ['/s', '/c', '"' + cmd + '"'];
		spawnOptions.windowsVerbatimArguments = true;
	} else {
		shell = process.env.SHELL || '/bin/sh';
		args = ['-c', cmd];
	}

	return spawn(shell, args, spawnOptions)
		.on('close', function(code, signal) {
			var err;

			if (code) {
				err = new Error(
					'Command "' + cmd + '" failed with exit code: ' + code
				);
			} else if (signal) {
				err = new Error(
					'Command "' + cmd + '" terminated with signal: ' + signal
				);
			}

			if (err) {
				err.exitCode = code;
				err.signal = signal;
			}

			callback(err);
		})
		.on('error', function(err) {
			callback(err);
		});
};

var findEnvVarName = function(env, names) {
	return _(names).find(function(name) {
		return _(env).has(name);
	});
};

exports.execScript = function(script, options, callback) {
	var nodeModulesBinPath = path.join(options.cwd, 'node_modules', '.bin');

	var env = _(options.env || {}).defaults(process.env);

	_(env).extend({
		NPACK_DIR: options.dir,
		NPACK_PKG_PATH: options.cwd
	});

	var pathVarName = findEnvVarName(process.env, ['PATH', 'Path', 'path']);
	env[pathVarName] += path.delimiter + nodeModulesBinPath;

	exports.exec(script, {
		cwd: options.cwd,
		env: env,
		log: options.log
	}, callback);
};
<commit_msg>43947 fix options.env mutation in processUtils.execScript<commit_after>'use strict';

var spawn = require('child_process').spawn,
	_ = require('underscore'),
	path = require('path');

var isWin = process.platform === 'win32';

exports.exec = function(cmd, options, callback) {
	callback = _(callback).once();

	var spawnOptions = _(options).clone();

	if (options.log) spawnOptions.stdio = 'inherit';

	var shell, args;

	if (isWin) {
		shell = process.env.comspec || 'cmd';
		args = ['/s', '/c', '"' + cmd + '"'];
		spawnOptions.windowsVerbatimArguments = true;
	} else {
		shell = process.env.SHELL || '/bin/sh';
		args = ['-c', cmd];
	}

	return spawn(shell, args, spawnOptions)
		.on('close', function(code, signal) {
			var err;

			if (code) {
				err = new Error(
					'Command "' + cmd + '" failed with exit code: ' + code
				);
			} else if (signal) {
				err = new Error(
					'Command "' + cmd + '" terminated with signal: ' + signal
				);
			}

			if (err) {
				err.exitCode = code;
				err.signal = signal;
			}

			callback(err);
		})
		.on('error', function(err) {
			callback(err);
		});
};

var findEnvVarName = function(env, names) {
	return _(names).find(function(name) {
		return _(env).has(name);
	});
};

exports.execScript = function(script, options, callback) {
	var nodeModulesBinPath = path.join(options.cwd, 'node_modules', '.bin');

	var env = _({}).defaults(options.env, process.env);

	_(env).extend({
		NPACK_DIR: options.dir,
		NPACK_PKG_PATH: options.cwd
	});

	var pathVarName = findEnvVarName(process.env, ['PATH', 'Path', 'path']);
	env[pathVarName] += path.delimiter + nodeModulesBinPath;

	exports.exec(script, {
		cwd: options.cwd,
		env: env,
		log: options.log
	}, callback);
};
<|endoftext|>


<commit_before>var env = process.env;
var fs = require('fs');
var cache = require(env.Z_PATH_CACHE + 'cache');

var pojoViews = cache.pojo('views');
var compViews = cache.comp('views');

// this is a pojo cache, because ws in engine dont support binary data yet
var snippetCache = cache.pojo('snippets');

module.exports = setup;

function setup (instance) {

    // setup view
    instance.on(env.Z_SEND_VIEW_REQ, factoryService);

    // return view factory
    return factory;
}

// get html snipptets (ws)
function getSnipped (path, callback) {
    var self = this;

    if (!path) {
        return callback();
    }

    path = env.Z_PATH_PROCESS_MARKUP + path.replace(/[^a-z0-9\/\.\-_]|\.\.\//gi, "");

    // check cache
    var snippet = snippetCache.get(path);
    if (snippet) {
        return callback(null, snippet);
    }

    // read snippet file
    fs.readFile(path, {encoding: 'utf8'}, function (err, data) {

        if (err) {
            return callback(err);
        }

        // save snippet in cache
        snippetCache.set(path, data);

        callback(null, data);
    });
}

function factory (name, role, callback) {
    var self = this;

    // check cache
    var view = pojoViews.get(name);
    if (view) {
        return callback(null, view);
    }

    // get system store to fetch models
    compViews.set(name, role, function (err, view) {

        if (err) {
            callback(err);
        }

        // get here the html snipped
        getSnipped(view.html, function (err, snipped) {

            if (err) {
                return callback(err);
            }

            // add snipped to view
            if (snipped) {
                view.html = snipped;
            }

            // save view in cache
            pojoViews.set(name, view);

            callback(null, view);
        });
    });
}

function factoryService (err, message) {
    var self = this;
    var session = self.link.ws.session;

    // check message
    if (!message) {
        return self.emit(env.Z_SEND_VIEW_RES, 'Bad message');
    }

    // create model
    factory.call(self, session[env.Z_SESSION_ROLE_KEY], message, function (err, view) {

        if (err) {
            return self.emit(env.Z_SEND_VIEW_RES, err);
        }

        // create client view
        var clientView = {
            name: view.name
        };

        // add client config
        if (view.config && view.config.client) {
            clientView.config = view.config.client;
        }

        if (view.O) {
            clientView.O = view.O;
        }

        if (view.L) {
            clientView.L = view.L;
        }

        if (view.html) {
            clientView.html = view.html;
        }

        if (view.to) {
            clientView.to = view.to;
        }

        if (view.css) {
            clientView.css = view.css;
        }

        self.emit(env.Z_SEND_VIEW_RES, null, clientView);
    });
}
<commit_msg>fixed views<commit_after>var env = process.env;
var fs = require('fs');
var cache = require(env.Z_PATH_CACHE + 'cache');

var pojoViews = cache.pojo('views');
var compViews = cache.comp('views');

// this is a pojo cache, because ws in engine dont support binary data yet
var snippetCache = cache.pojo('snippets');

module.exports = setup;

function setup (instance) {

    // setup view
    instance.on(env.Z_SEND_VIEW_REQ, factoryService);

    // return view factory
    return factory;
}

// get html snipptets (ws)
function getSnipped (path, callback) {
    var self = this;

    if (!path) {
        return callback();
    }

    path = env.Z_PATH_PROCESS_MARKUP + path.replace(/[^a-z0-9\/\.\-_]|\.\.\//gi, "");

    // check cache
    var snippet = snippetCache.get(path);
    if (snippet) {
        return callback(null, snippet);
    }

    // read snippet file
    fs.readFile(path, {encoding: 'utf8'}, function (err, data) {

        if (err) {
            return callback(err);
        }

        // save snippet in cache
        snippetCache.set(path, data);

        callback(null, data);
    });
}

function factory (name, role, callback) {
    var self = this;

    // check cache
    var view = pojoViews.get(name);
    if (view) {
        return callback(null, view);
    }

    // get system store to fetch models
    compViews.set(name, role, function (err, view) {

        if (err) {
            return callback(err);
        }

        // get here the html snipped
        getSnipped(view.html, function (err, snipped) {

            if (err) {
                return callback(err);
            }

            // add snipped to view
            if (snipped) {
                view.html = snipped;
            }

            // save view in cache
            pojoViews.set(name, view);

            callback(null, view);
        });
    });
}

function factoryService (err, name) {
    var self = this;
    var session = self.link.ws.session;

    // check message
    if (!name) {
        return self.emit(env.Z_SEND_VIEW_RES, 'Bad message');
    }

    name = self.Z.name + '_' + name;

    // create model
    factory.call(self, name, session[env.Z_SESSION_ROLE_KEY], function (err, view) {

        if (err) {
            return self.emit(env.Z_SEND_VIEW_RES, err);
        }

        // create client view
        var clientView = {
            name: view.name.split('_').pop()
        };

        // add client config
        if (view.config && view.config.client) {
            clientView.config = view.config.client;
        }

        if (view.O) {
            clientView.O = view.O;
        }

        if (view.L) {
            clientView.L = view.L;
        }

        if (view.html) {
            clientView.html = view.html;
        }

        if (view.to) {
            clientView.to = view.to;
        }

        if (view.css) {
            clientView.css = view.css;
        }

        self.emit(env.Z_SEND_VIEW_RES, null, clientView);
    });
}
<|endoftext|>


<commit_before>var env = process.env;
var fs = require('fs');
var cache = require(env.Z_PATH_CACHE + 'cache');
var fingerprint = require(env.Z_PATH_UTILS + 'fingerprint');
var compViews = cache.comp('views');
var snippetCache = cache.file('snippets', true);

module.exports = factoryService;

function factory (name, role, callback) {
    var self = this;

    // check cache
    var view = compViews.pojo.get(name, role);

    // handle no access
    if (view === 0) {
        return callback(new Error('View ' + name + ' not found.'));
    }

    // return view from cache
    if (view) {
        return callback(null, view);
    }

    // get system store to fetch models
    compViews.get(name, role, function (err, view) {

        if (err || !view) {
            return callback(err || new Error('View ' + name + ' not found.'));
        }

        // save roles in view
        view._roles = view.roles;

        // add commit ids to css files
        fingerprint.addToFiles(null, view.css || [], function (err, css) {

            if (err) {
                return callback(err);
            }

            // update css
            if (css) {
                view.css = css;
            }

            if (!view.html) {

                // save view in cache
                compViews.pojo.set(name, view);

                return callback(null, view);
            }

            var path = env.Z_PATH_PROCESS_MARKUP + view.html.replace(/[^a-z0-9\/\.\-_]|\.\.\//gi, '');
            snippetCache.set(path, function (err, snipped) {

                if (err) {
                    return callback(err);
                }

                // add snipped to view
                view.html = snipped.data.toString('utf8');

                // save view in cache
                compViews.pojo.set(name, view);

                callback(null, view);
            });
        });
    });
}

function factoryService (err, name, callback) {
    var self = this;

    // check message
    if (!name) {
        return callback('Bad message');
    }

    name = self._name + '_' + name;

    // create model
    factory.call(self, name, self.link.role, function (err, view) {

        if (err) {
            return callback(err);
        }

        // create client view
        var clientView = {
            name: view.name.split('_').pop()
        };

        // add client config
        if (view.config && view.config.client) {
            clientView.config = view.config.client;
        }

        if (view.O) {
            clientView.O = view.O;
        }

        if (view.L) {
            clientView.L = view.L;
        }

        if (view.html) {
            clientView.html = view.html;
        }

        if (view.to) {
            clientView.to = view.to;
        }

        if (view.css) {
            clientView.css = view.css;
        }

        if (view.dontEscape) {
            clientView.dontEscape = view.dontEscape;
        }

        if (view.leaveKeys) {
            clientView.leaveKeys = view.leaveKeys;
        }

        callback(null, clientView);
    });
}
<commit_msg>on snippet change, update all connected views<commit_after>var env = process.env;
var fs = require('fs');
var cache = require(env.Z_PATH_CACHE + 'cache');
var fingerprint = require(env.Z_PATH_UTILS + 'fingerprint');
var compViews = cache.comp('views');
var snippetCache = cache.file('snippets', true);

module.exports = factoryService;

function factory (name, role, callback) {
    var self = this;

    // check cache
    var view = compViews.pojo.get(name, role);

    // handle no access
    if (view === 0) {
        return callback(new Error('View ' + name + ' not found.'));
    }

    // return view from cache
    if (view) {
        return callback(null, view);
    }

    // get system store to fetch models
    compViews.get(name, role, function (err, view, viewChanged) {

        if (err || !view) {
            return callback(err || new Error('View ' + name + ' not found.'));
        }

        // save roles in view
        view._roles = view.roles;

        // add commit ids to css files
        fingerprint.addToFiles(null, view.css || [], function (err, css) {

            if (err) {
                return callback(err);
            }

            // update css
            if (css) {
                view.css = css;
            }

            if (!view.html) {

                // save view in cache
                compViews.pojo.set(name, view);

                return callback(null, view);
            }

            var path = env.Z_PATH_PROCESS_MARKUP + view.html.replace(/[^a-z0-9\/\.\-_]|\.\.\//gi, '');
            snippetCache.set(path, function (err, snipped) {

                if (err) {
                    return callback(err);
                }

                // add snipped to view
                view.html = snipped.data.toString('utf8');

                // remove view on snippet change
                if (!viewChanged) {
                    snippetCache.obs.once('change:' + path, function () {
                        compViews.rm(name, role);
                    });
                }

                // save view in cache
                compViews.pojo.set(name, view);

                callback(null, view);
            });
        });
    });
}

function factoryService (err, name, callback) {
    var self = this;

    // check message
    if (!name) {
        return callback('Bad message');
    }

    name = self._name + '_' + name;

    // create model
    factory.call(self, name, self.link.role, function (err, view) {

        if (err) {
            return callback(err);
        }

        // create client view
        var clientView = {
            name: view.name.split('_').pop()
        };

        // add client config
        if (view.config && view.config.client) {
            clientView.config = view.config.client;
        }

        if (view.O) {
            clientView.O = view.O;
        }

        if (view.L) {
            clientView.L = view.L;
        }

        if (view.html) {
            clientView.html = view.html;
        }

        if (view.to) {
            clientView.to = view.to;
        }

        if (view.css) {
            clientView.css = view.css;
        }

        if (view.dontEscape) {
            clientView.dontEscape = view.dontEscape;
        }

        if (view.leaveKeys) {
            clientView.leaveKeys = view.leaveKeys;
        }

        callback(null, clientView);
    });
}
<|endoftext|>


<commit_before>
function getParameterByName(name) {
    // from: http://stackoverflow.com/a/901144/908430
    name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
    var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'),
        results = regex.exec(location.search);
    return results == null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}

function createHtmlFragment(htmlStr) {
    // from: http://stackoverflow.com/a/814649/908430
    // also, see: http://ejohn.org/blog/dom-documentfragments/
    var frag = document.createDocumentFragment(),
        temp = document.createElement('div');
    temp.innerHTML = htmlStr;
    while (temp.firstChild) {
        frag.appendChild(temp.firstChild);
    }
    return frag;
}

function setResults(htmlStr) {
    var results = document.getElementById('results');
    results.innerHTML = '';

    var fragment = createHtmlFragment(htmlStr);
    results.appendChild(fragment);

    if (htmlStr != '')
        results.style.visibility = 'visible';
    else
        results.style.visibility = 'hidden';
}

function calculateAndDisplayTaxes(income, deductions, exemptions) {
    var resultsHtml = '';

    function w(kind, text, amt) {
        if (kind == 'a')
            resultsHtml += '
' + text + ': ' + amt + '
';
        else if (kind == 'l_start')
            resultsHtml += '

' + text + '
';
        else if (kind == 'b')
            resultsHtml += '
' + text + '
';
        else if (kind == 'l_end')
            resultsHtml += '
' + text + ': ' + amt + '

';
        else if (kind == 't')
            resultsHtml += '
' + text + '
';
        else if (kind == 'x') // html decorators
            resultsHtml += text;
    }

    function do_nothing_w(kind, text, amt) {}

    var my_tax = calc_taxes(taxRates, income, deductions, exemptions, w),
        my_tax_plus = calc_taxes(taxRates, income + 1.0, deductions, exemptions, do_nothing_w);

    var marginal_tax_rate = (my_tax_plus - my_tax) * 100.0;
    w('a', 'Marginal Tax Rate', marginal_tax_rate.toFixed(2) + '%');

    return resultsHtml;
}

function calcAndDisplay(input) {
    setResults(calculateAndDisplayTaxes(input.income, input.deductions, input.exemptions));
}

function constructErrorMessage(input, incomeParam, deductionsParam, exemptionsParam) {
    var errorMessage = '';

    if (!incomeParam)
        errorMessage += 'Income must be specified.
';
    if (incomeParam && input.income == 0)
        errorMessage += 'Income must be greater than zero.
';
    if (incomeParam && !(input.income >= 0))
        errorMessage += "Income: '" + incomeParam + "'" + ' is not valid.
';
    if (!deductionsParam)
        errorMessage += 'The total dollar amount of tax deductions must be specified.
';
    if (deductionsParam && !(input.deductions >= 0))
        errorMessage += "Deductions: '" + deductionsParam + "'" + ' is not valid.
';
    if (!exemptionsParam)
        errorMessage += 'The number of personal exemptions must be specified.
';
    if (exemptionsParam && !(input.exemptions >= 0))
        errorMessage += "Exemptions: '" + exemptionsParam + "'" + ' is not valid.
';

    return errorMessage;
}

function removeCommas(text) {
    return text.replace(/,/g, '');
}

function createInputObject(incomeParam, deductionsParam, exemptionsParam) {
    var input = {
        income: parseFloat(removeCommas(incomeParam)),
        deductions: parseFloat(removeCommas(deductionsParam)),
        exemptions: parseFloat(removeCommas(exemptionsParam))
    };

    if (input.income > 0 && input.deductions >= 0 && input.exemptions >= 0)
        return input;
    else
        throw constructErrorMessage(input, incomeParam, deductionsParam, exemptionsParam);
}

function setFormValues(incomeVal, deductionsVal, exemptionsVal) {
    document.getElementById('agi').value = incomeVal;
    document.getElementById('itd').value = deductionsVal;
    document.getElementById('npe').value = exemptionsVal;
}

function getParams() {
    var incomeParam = getParameterByName('income');
    var deductionsParam = getParameterByName('deductions');
    var exemptionsParam = getParameterByName('exemptions');

    if (deductionsParam == '')
        deductionsParam = '0';

    if (exemptionsParam == '')
        exemptionsParam = '1';

    setFormValues(incomeParam, deductionsParam, exemptionsParam);

    return createInputObject(incomeParam, deductionsParam, exemptionsParam);
}

function getFormParams() {
    return {
        income: document.getElementById('agi').value,
        deductions: document.getElementById('itd').value,
        exemptions: document.getElementById('npe').value
    };
}

function createInputObjectFromParams(formParams) {
    return createInputObject(formParams.income, formParams.deductions, formParams.exemptions);
}

function overrideSubmit(onsubmit_function) {
    // from: http://stackoverflow.com/a/1395254
    // also see: http://stackoverflow.com/a/3350260
    function jsSubmit() {
        onsubmit_function();
        return false;
    }
    document.forms['input_form'].onsubmit = jsSubmit;
}

function doesSupportHistoryAPI() {
    // from: http://stackoverflow.com/a/9446306
    return !!(window.history && history.pushState);
}

function encodeQueryData(data) {
    // from: http://stackoverflow.com/a/111545
    var ret = [];
    for (var d in data)
        ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]));
    return ret.join('&');
}

function dynamicSubmit() {
    var formParams = getFormParams();

    try {
        var input = createInputObjectFromParams(formParams);

        history.pushState(formParams, '', '?' + encodeQueryData(formParams));

        calcAndDisplay(input);
    }
    catch (err_output) {
        history.pushState(formParams, '', '?' + encodeQueryData(formParams));

        setResults(err_output);
    }
}

function main() {
    try {
        var input = getParams();

        calcAndDisplay(input);
    }
    catch (err_output) {
        setResults(err_output);
        var input = null;
    }

    if (doesSupportHistoryAPI()) {
        overrideSubmit(dynamicSubmit);

        window.onpopstate = function(event) {
            var formParams = event.state;

            if (formParams)
                setFormValues(formParams.income, formParams.deductions, formParams.exemptions);

            try {
                var input = formParams ? createInputObjectFromParams(formParams) : getParams();

                calcAndDisplay(input);
            }
            catch (err_output) {
                setResults(err_output);
            }
        };
    }
}

main();
<commit_msg>Strengthen web text inputs validation and error reporting<commit_after>
function getParameterByName(name) {
    // from: http://stackoverflow.com/a/901144/908430
    name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
    var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'),
        results = regex.exec(location.search);
    return results == null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}

function createHtmlFragment(htmlStr) {
    // from: http://stackoverflow.com/a/814649/908430
    // also, see: http://ejohn.org/blog/dom-documentfragments/
    var frag = document.createDocumentFragment(),
        temp = document.createElement('div');
    temp.innerHTML = htmlStr;
    while (temp.firstChild) {
        frag.appendChild(temp.firstChild);
    }
    return frag;
}

function setResults(htmlStr) {
    var results = document.getElementById('results');
    results.innerHTML = '';

    var fragment = createHtmlFragment(htmlStr);
    results.appendChild(fragment);

    if (htmlStr != '')
        results.style.visibility = 'visible';
    else
        results.style.visibility = 'hidden';
}

function calculateAndDisplayTaxes(income, deductions, exemptions) {
    var resultsHtml = '';

    function w(kind, text, amt) {
        if (kind == 'a')
            resultsHtml += '
' + text + ': ' + amt + '
';
        else if (kind == 'l_start')
            resultsHtml += '

' + text + '
';
        else if (kind == 'b')
            resultsHtml += '
' + text + '
';
        else if (kind == 'l_end')
            resultsHtml += '
' + text + ': ' + amt + '

';
        else if (kind == 't')
            resultsHtml += '
' + text + '
';
        else if (kind == 'x') // html decorators
            resultsHtml += text;
    }

    function do_nothing_w(kind, text, amt) {}

    var my_tax = calc_taxes(taxRates, income, deductions, exemptions, w),
        my_tax_plus = calc_taxes(taxRates, income + 1.0, deductions, exemptions, do_nothing_w);

    var marginal_tax_rate = (my_tax_plus - my_tax) * 100.0;
    w('a', 'Marginal Tax Rate', marginal_tax_rate.toFixed(2) + '%');

    return resultsHtml;
}

function calcAndDisplay(input) {
    setResults(calculateAndDisplayTaxes(input.income, input.deductions, input.exemptions));
}

function constructErrorMessage(input, incomeParam, deductionsParam, exemptionsParam) {
    var errorMessage = '';

    if (!incomeParam)
        errorMessage += 'Income must be specified.
';
    if (incomeParam && input.income == 0)
        errorMessage += 'Income must be greater than zero.
';
    if (incomeParam && (!isValidNumber(incomeParam) || !(input.income >= 0)))
        errorMessage += "Income: '" + incomeParam + "'" + ' is not valid.
';
    if (!deductionsParam)
        errorMessage += 'The total dollar amount of tax deductions must be specified.
';
    if (deductionsParam && (!isValidNumber(deductionsParam) || !(input.deductions >= 0)))
        errorMessage += "Deductions: '" + deductionsParam + "'" + ' is not valid.
';
    if (!exemptionsParam)
        errorMessage += 'The number of personal exemptions must be specified.
';
    if (exemptionsParam && (!isValidNumber(exemptionsParam) || !(input.exemptions >= 0)))
        errorMessage += "Exemptions: '" + exemptionsParam + "'" + ' is not valid.
';

    return errorMessage;
}

function isValidNumber(text) { // from: http://stackoverflow.com/a/21907572/908430
    return /^[0-9,.]*$/.test(text);
}

function removeCommas(text) {
    return text.replace(/,/g, '');
}

function createInputObject(incomeParam, deductionsParam, exemptionsParam) {
    var input = {
        income: parseFloat(removeCommas(incomeParam)),
        deductions: parseFloat(removeCommas(deductionsParam)),
        exemptions: parseFloat(removeCommas(exemptionsParam))
    };

    if (isValidNumber(incomeParam) && input.income > 0 &&
        isValidNumber(deductionsParam) && input.deductions >= 0 &&
        isValidNumber(exemptionsParam) && input.exemptions >= 0)
        return input;
    else
        throw constructErrorMessage(input, incomeParam, deductionsParam, exemptionsParam);
}

function setFormValues(incomeVal, deductionsVal, exemptionsVal) {
    document.getElementById('agi').value = incomeVal;
    document.getElementById('itd').value = deductionsVal;
    document.getElementById('npe').value = exemptionsVal;
}

function getParams() {
    var incomeParam = getParameterByName('income');
    var deductionsParam = getParameterByName('deductions');
    var exemptionsParam = getParameterByName('exemptions');

    if (deductionsParam == '')
        deductionsParam = '0';

    if (exemptionsParam == '')
        exemptionsParam = '1';

    setFormValues(incomeParam, deductionsParam, exemptionsParam);

    return createInputObject(incomeParam, deductionsParam, exemptionsParam);
}

function getFormParams() {
    return {
        income: document.getElementById('agi').value,
        deductions: document.getElementById('itd').value,
        exemptions: document.getElementById('npe').value
    };
}

function createInputObjectFromParams(formParams) {
    return createInputObject(formParams.income, formParams.deductions, formParams.exemptions);
}

function overrideSubmit(onsubmit_function) {
    // from: http://stackoverflow.com/a/1395254
    // also see: http://stackoverflow.com/a/3350260
    function jsSubmit() {
        onsubmit_function();
        return false;
    }
    document.forms['input_form'].onsubmit = jsSubmit;
}

function doesSupportHistoryAPI() {
    // from: http://stackoverflow.com/a/9446306
    return !!(window.history && history.pushState);
}

function encodeQueryData(data) {
    // from: http://stackoverflow.com/a/111545
    var ret = [];
    for (var d in data)
        ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]));
    return ret.join('&');
}

function dynamicSubmit() {
    var formParams = getFormParams();

    try {
        var input = createInputObjectFromParams(formParams);

        history.pushState(formParams, '', '?' + encodeQueryData(formParams));

        calcAndDisplay(input);
    }
    catch (err_output) {
        history.pushState(formParams, '', '?' + encodeQueryData(formParams));

        setResults(err_output);
    }
}

function main() {
    try {
        var input = getParams();

        calcAndDisplay(input);
    }
    catch (err_output) {
        setResults(err_output);
        var input = null;
    }

    if (doesSupportHistoryAPI()) {
        overrideSubmit(dynamicSubmit);

        window.onpopstate = function(event) {
            var formParams = event.state;

            if (formParams)
                setFormValues(formParams.income, formParams.deductions, formParams.exemptions);

            try {
                var input = formParams ? createInputObjectFromParams(formParams) : getParams();

                calcAndDisplay(input);
            }
            catch (err_output) {
                setResults(err_output);
            }
        };
    }
}

main();
<|endoftext|>


<commit_before>define(["dojo/_base/declare", "ppwcode/contracts/_Mixin",
        "dojo/store/util/QueryResults", "dojo/store/util/SimpleQueryEngine",
        "dojo/store/Observable" /*=====, "./api/Store" =====*/],
  function(declare, _ContractsMixin,
           QueryResults, SimpleQueryEngine,
           Observable /*=====, Store =====*/) {

    // No base class, but for purposes of documentation, the base class is dojo/store/api/Store
    var Base = null;
    /*===== Base = Store; =====*/

    var OurStore = declare([_ContractsMixin, Base], {
      // summary:
      //		An in-memory Observable object store like dojo/store/Memory wrapped in dojo/store/Observable,
      //    but with the following differences:
      //    1) We don't change the objects that are kept in the store. If you are not very
      //       carefull, dojo/store/Memory inserts an "id" property for its own benefit,
      //       whose behavior depends on chance (random).
      //    2) We are aware of Stateful.
      //       If an element changes, we also send changed events. You do not have
      //       to re-put.
      //    3) removeAll, loadAll
      // description:
      //    On development we immediately need constructor, put, removeAll, loadAll, and query.
      //    get, getIdentity, and remove are optional.
      //    Query needs an array to be returned. It therefor is wasteful to keep an index, which
      //    only helps with get and remove.
      //    idProperty is wasteful and stupid, but used in many uses of Store.
      //    Therefor, we will wrap the real objects in a helper object. The helper object
      //    has the id-attribute. It's value is calculated and kept in sync by a
      //    idPropertyDerivation function.
      //    If idProperty changes, all bets are off.
      //
      //    Based on dojo/store/Memory

      _c_invar: [
        function() {return this._c_prop_mandatory("idProperty");},
        function() {return this._c_prop_string("idProperty");},
        function() {return this._c_prop_mandatory("idPropertyDerivation");},
        function() {return this._c_prop_function("idPropertyDerivation");}
      ],

      // _data: Array
      //   The internal representation of the data
      _data: null,

      // idProperty: String
      //		Indicates the property to use as the identity property. Don't change this.
      idProperty: "id",

      // idPropertyDerivation: Function
      //    Stateful --> String
      //    Derives a unique id from a Stateful object as argument.
      idPropertyDerivation: null,

      // queryEngine: Function
      //		Defines the query engine to use for querying the data store
      queryEngine: SimpleQueryEngine,

      _wrap: function(/*Stateful*/ s) {
        var thisStore = this;

        function addWatcher(wrapper) {
          var watcher = function(name, oldValue, newValue) {
            // on change, see if the id has changed
            // if it has, signal removal, and a new addition
            // if is has not, signal change
            var oldId = wrapper[thisStore.idProperty];
            var newId = thisStore.idPropertyDerivation();
            if (oldId != newId) {
              wrapper[thisStore.idProperty] = newId;
              thisStore.notify(null, oldId);
              thisStore.notify(wrapper.data, null);
            }
            else {
              thisStore.notify(wrapper.data, oldId);
            }
          };
          wrapper.watcher = wrapper.data.watch(watcher);
        }

        var id = thisStore.idPropertyDerivation(s);
        var result = {};
        result[thisStore.idProperty] = id;
        result.data = s;
        addWatcher(result);
        return result;
      },

      constructor: function(options){
        // summary:
        //		Creates a store of stateful objects.
        // options: dojo/store/Memory
        //		This provides any configuration information that will be mixed into the store.
        //		This should generally include the data property to provide the starting set of data.

        this._c_pre(function() {return options;});
        this._c_pre(function() {return this._c_prop_mandatory(options, "idPropertyDerivation");});

        var thisStore = this;
        Object.keys(options).
          filter(function(key) {return key !== "data";}).
          forEach(function(key) {thisStore[key] = options[key];});
        if (options && options.data) {
          thisStore._data = options.data.reduce(
            function(acc, element) {
              acc.push(thisStore._wrap(element));
              return acc;
            },
            []
          );
        }
        else {
          thisStore._data = [];
        }
      },

      get: function(id){
        // summary:
        //		Retrieves an object by its identity
        // id: Number
        //		The identity to use to lookup the object
        // returns: Object
        //		The object in the store that matches the given id.

        var filterResult = this._data.filter(function(wrapper) {
          return wrapper[this.idProperty] === id;
        });
        if (filterResult.length > 1) {
          throw "ERROR: duplicate id in store (id: " + id + ", store: " + this + ")";
        }
        else if (filterResult.length === 0) {
          return null;
        }
        else {
          return filterResult[0].data;
        }
      },

      getIdentity: function(object){
        // summary:
        //		Returns an object's identity
        // object: Object
        //		The object to get the identity from
        // returns: Number

        var filterResult = this._data.filter(function(wrapper) {
          return wrapper[data] === object;
        });
        if (filterResult.length > 1) {
          throw "ERROR: object in store more then once (object: " + object + ", store: " + this + ")";
        }
        else if (filterResult.length === 0) {
          return null;
        }
        else {
          return filterResult[0][this.idProperty];
        }
      },

      put: function(object) {
        // summary:
        //		Stores an object. Options are ignored.
        // object: Object
        //		The object to store.
        // returns: Number

        var wrapper = this._wrap(object);
        this._data.push(wrapper); // Store spec doesn't say what we should return. We return nothing.
      },

      add: function(object){
        // summary:
        //		Creates an object, throws an error if the object already exists
        //		Options are ignored.
        // object: Object
        //		The object to store.
        // returns: Number

        if (this.getIdentity(object)) {
          throw new Error("Object already exists");
        }
        this.put(object); // Store spec doesn't say what we should return. We return nothing.
      },

      remove: function(id){
        // summary:
        //		Deletes an object by its identity
        // id: Number
        //		The identity to use to delete the object
        // returns: Boolean
        //		Returns true if an object was removed, falsy (undefined) if no object matched the id

        var thisStore = this;
        var foundAtIndex = this._data.reduce(
          function(acc, element, index) {
            if (element[thisStore.idProperty] === id) {
              if (acc > -1) {
                throw "ERROR: duplicate id in store (id: " + id + ", store: " + thisStore + ")";
              }
              else {
                element.watcher.unwatch();
                return index;
              }
            }
            else {
              return acc;
            }
          },
          -1
        );
        if (foundAtIndex > -1) {
          thisStore._data.splice(foundAtIndex, 1);
        }
        // Store spec doesn't say what we should return. We return nothing.
      },

      query: function(query, options){
        // summary:
        //		Queries the store for objects.
        // query: Object
        //		The query to use for retrieving objects from the store.
        // options: dojo/store/api/Store.QueryOptions?
        //		The optional arguments to apply to the resultset.
        // returns: dojo/store/api/Store.QueryResults
        //		The results of the query, extended with iterative methods.
        //
        // example:
        //		Given the following store:
        //
        // 	|	var store = new Memory({
        // 	|		data: [
        // 	|			{id: 1, name: "one", prime: false },
        //	|			{id: 2, name: "two", even: true, prime: true},
        //	|			{id: 3, name: "three", prime: true},
        //	|			{id: 4, name: "four", even: true, prime: false},
        //	|			{id: 5, name: "five", prime: true}
        //	|		]
        //	|	});
        //
        //	...find all items where "prime" is true:
        //
        //	|	var results = store.query({ prime: true });
        //
        //	...or find all items where "even" is true:
        //
        //	|	var results = store.query({ even: true });
        return QueryResults(this.queryEngine(query, options)(this.data));
      },

      loadAll: function(data) {
        // summary:
        //   replaces current data with new data; common objects
        //   are not signalled as removed and added again

        var thisStore = this;
        var inData = data.slice(0);
        thisStore._data = thisStore._data.reduce(
          function (acc, wrapper) {
            var indexInData = inData.indexOf(wrapper.data);
            if (indexInData < 0) {
              // not in inData; don't add to acc, signal removal
              wrapper.watcher.unwatch();
              thisStore.notify(null, wrapper[thisStore.idProperty]);
            }
            else {
              // keep the element (add to acc), and note handled.
              inData.splice(indexInData, 1);
              acc.push(wrapper);
            }
            return acc;
          },
          []
        );
        // what is left in inData needs to be added
        inData.forEach(function(element) {
          thisStore.put(element);
        });
      },

      removeAll: function() {
        var thisStore = this;

        var oldData = thisStore._data;
        thisStore._data = [];
        oldData.forEach(function(wrapper) {
          wrapper.watcher.unwatch();
          thisStore.notify(null, wrapper[thisStore.idProperty]);
        });
      }

    });

    var OurObservableStore = Observable(OurStore);

    return OurObservableStore;

  });
<commit_msg>It turns out our "idPropertyDerivation" actually has to be the getIdentity function. Changed accordingly. This now works in some human tests with an example in grid.<commit_after>define(["dojo/_base/declare", "ppwcode/contracts/_Mixin",
        "dojo/store/util/QueryResults", "dojo/store/util/SimpleQueryEngine",
        "dojo/store/Observable" /*=====, "./api/Store" =====*/],
  function(declare, _ContractsMixin,
           QueryResults, SimpleQueryEngine,
           Observable) {

    var ERROR_ALREADY_IN_STORE = new Error("Object already exists in this store");

    var OurStore = declare([_ContractsMixin], {
      // summary:
      //		An in-memory Observable object store like dojo/store/Memory wrapped in dojo/store/Observable,
      //    but with the following differences:
      //    1) We don't change the objects that are kept in the store. If you are not very
      //       careful, dojo/store/Memory inserts an "id" property for its own benefit,
      //       whose behavior depends on chance (random).
      //    2) We are aware of Stateful.
      //       If an element changes, we also send changed events. You do not have
      //       to re-put.
      //    3) removeAll, loadAll
      //    4) You need to inject a getIdentity function. It must work on all objects
      //       that ever were in the store or will be in the store, independent of the store.
      // description:
      //    On development we immediately need constructor, put, removeAll, loadAll, and query.
      //    getIdentity is needed for Observable.
      //    get and remove are optional.
      //    Query needs an array to be supplied. It therefor is wasteful to keep an index, which
      //    only helps with get and remove.
      //    idProperty is wasteful and stupid. We kill it.
      //    We wrap the real objects in a helper object, to keep track of the watchers of
      //    Stateful objects.
      //    If getIdentity changes, all bets are off.
      //
      //    Before you throw away the store, you must call removeAll, so that
      //    we can clean up listeners. Otherwise, you will have a memory leak.
      //
      //    Based on dojo/store/Memory

      _c_invar: [
        function() {return this._c_prop_mandatory("idProperty");},
        function() {return this._c_prop_string("idProperty");},
        function() {return this._c_prop_mandatory("idPropertyDerivation");},
        function() {return this._c_prop_function("idPropertyDerivation");}
      ],

      // _data: Array
      //   The internal representation of the data
      _data: null,

      // idPropertyDerivation: Function
      //    Stateful --> String
      //    Derives a unique id from a Stateful object as argument.
      getIdentity: null,

      // queryEngine: Function
      //		Defines the query engine to use for querying the data store
      queryEngine: SimpleQueryEngine,

      _wrap: function(/*Stateful*/ s) {
        var thisStore = this;

        function addWatcher(wrapper) {
          var watcher = function(name, oldValue, newValue) {
            // on change, see if the id has changed
            // if it has, signal removal, and a new addition
            // if is has not, signal change
            var oldId = wrapper.id;
            var newId = thisStore.getIdentity(wrapper.data);
            if (oldId != newId) {
              wrapper.id = newId;
              thisStore.notify(null, oldId);
              thisStore.notify(wrapper.data, null);
            }
            else {
              thisStore.notify(wrapper.data, oldId);
            }
          };
          wrapper.watcher = wrapper.data.watch(watcher);
        }

        var result = {
          id: thisStore.getIdentity(s),
          data: s
        };
        addWatcher(result);
        return result;
      },

      constructor: function(options){
        // summary:
        //		Creates a store of stateful objects.
        // options: dojo/store/Memory
        //		This provides any configuration information that will be mixed into the store.
        //		This should generally include the data property to provide the starting set of data.

        this._c_pre(function() {return options;});
        this._c_pre(function() {return this._c_prop_mandatory(options, "getIdentity");});

        var thisStore = this;
        Object.keys(options).
          filter(function(key) {return key !== "data";}).
          forEach(function(key) {thisStore[key] = options[key];});
        if (options && options.data) {
          thisStore._data = options.data.reduce(
            function(acc, element) {
              acc.push(thisStore._wrap(element));
              return acc;
            },
            []
          );
        }
        else {
          thisStore._data = [];
        }
      },

      contains: function(object){
        // summary:
        //		Is the object in this store?
        // object: Object
        //		The object to check membership for. We compare
        // returns: Boolean

        var filterResult = this._data.filter(function(wrapper) {
          return wrapper.data === object;
        });
        if (filterResult.length > 1) {
          throw "ERROR: object in store more then once (object: " + object + ", store: " + this + ")";
        }
        else if (filterResult.length === 0) {
          return null;
        }
        else {
          return filterResult[0][this.idProperty];
        }
      },

      get: function(id){
        // summary:
        //		Retrieves an object by its identity
        // id: Number
        //		The identity to use to lookup the object
        // returns: Object
        //		The object in the store that matches the given id.

        var filterResult = this._data.filter(function(wrapper) {
          return wrapper.id === id;
        });
        if (filterResult.length > 1) {
          throw "ERROR: duplicate id in store (id: " + id + ", store: " + this + ")";
        }
        else if (filterResult.length === 0) {
          return null;
        }
        else {
          return filterResult[0].data;
        }
      },

      put: function(object) {
        // summary:
        //		Stores an object. Options are ignored.
        // object: Object
        //		The object to store.
        // returns: Number

        try {
          this.add(object);
        }
        catch (e) {
          if (e !== ERROR_ALREADY_IN_STORE) {
            throw e;
          }
        }
      },

      add: function(object){
        // summary:
        //		Creates an object, throws an error if the object already exists
        //		Options are ignored.
        // object: Object
        //		The object to store.
        // returns: Number

        if (this.get(this.getIdentity(object))) { // we have this object already
          throw ERROR_ALREADY_IN_STORE;
        }
        var wrapper = this._wrap(object);
        this._data.push(wrapper); // Store spec doesn't say what we should return. We return nothing.
      },

      remove: function(id){
        // summary:
        //		Deletes an object by its identity
        // id: Number
        //		The identity to use to delete the object
        // returns: Boolean
        //		Returns true if an object was removed, falsy (undefined) if no object matched the id

        var thisStore = this;
        var foundAtIndex = this._data.reduce(
          function(acc, wrapper, index) {
            if (wrapper.id === id) {
              if (acc > -1) {
                throw "ERROR: duplicate id in store (id: " + id + ", store: " + thisStore + ")";
              }
              else {
                wrapper.watcher.unwatch();
                return index;
              }
            }
            else {
              return acc;
            }
          },
          -1
        );
        if (foundAtIndex > -1) {
          thisStore._data.splice(foundAtIndex, 1);
        }
        // Store spec doesn't say what we should return. We return nothing.
      },

      query: function(query, options){
        // summary:
        //		Queries the store for objects.
        // query: Object
        //		The query to use for retrieving objects from the store.
        // options: dojo/store/api/Store.QueryOptions?
        //		The optional arguments to apply to the resultset.
        // returns: dojo/store/api/Store.QueryResults
        //		The results of the query, extended with iterative methods.
        //
        // example:
        //		Given the following store:
        //
        // 	|	var store = new Memory({
        // 	|		data: [
        // 	|			{id: 1, name: "one", prime: false },
        //	|			{id: 2, name: "two", even: true, prime: true},
        //	|			{id: 3, name: "three", prime: true},
        //	|			{id: 4, name: "four", even: true, prime: false},
        //	|			{id: 5, name: "five", prime: true}
        //	|		]
        //	|	});
        //
        //	...find all items where "prime" is true:
        //
        //	|	var results = store.query({ prime: true });
        //
        //	...or find all items where "even" is true:
        //
        //	|	var results = store.query({ even: true });
        return QueryResults(this.queryEngine(query, options)(this.data));
      },

      loadAll: function(data) {
        // summary:
        //   replaces current data with new data; common objects
        //   are not signalled as removed and added again

        var thisStore = this;
        var inData = data.slice(0);
        thisStore._data = thisStore._data.reduce(
          function (acc, wrapper) {
            var indexInData = inData.indexOf(wrapper.data);
            if (indexInData < 0) {
              // not in inData; don't add to acc, signal removal
              wrapper.watcher.unwatch();
              thisStore.notify(null, wrapper.id);
            }
            else {
              // keep the element (add to acc), and note handled.
              inData.splice(indexInData, 1);
              acc.push(wrapper);
            }
            return acc;
          },
          []
        );
        // what is left in inData needs to be added
        inData.forEach(function(newElement) {
          thisStore.put(newElement);
        });
      },

      removeAll: function() {
        var thisStore = this;

        var oldData = thisStore._data;
        thisStore._data = [];
        oldData.forEach(function(wrapper) {
          wrapper.watcher.unwatch();
          thisStore.notify(null, wrapper.id);
        });
      }

    });

    var OurObservableStore = function(options) {
      return Observable(new OurStore(options));
    };

    return OurObservableStore;

  });
<|endoftext|>


<commit_before>var clientApp = clientApp || {};
var helpers = clientApp.helpers || {};
var input = input || {};
var upload = upload || {};
var editor = editor || {};

// Print in the web Interface the sets.
input.printSets = function(sets) {
  for (var i = 0; i < sets.length; i++) {
    var set = sets[i].blobs;
    var blobs = Object.keys(set);
    var name = sets[i].name;
    $(".setContainer").append("

")
      .append("

");
    var blobSet = $(".blobSet-body-" + i);
    var blobSetArray = [];

    addSetClickEvent(blobSet, set);

    blobSetArray += ""; // first photo
    if (blobs.length == 3) { // Middle photo (3 photos)
      blobSetArray += "";
    }
    if (blobs.length >= 4) { // +3 photo set. ···
      blobSetArray += "···";
    }
    if (blobs.length > 1) { // +1 photo. last photo.
      blobSetArray += "";
    }
    blobSet.html(blobSetArray);
    $(".blobSet_" + i).append($(".blobSet-body-" + i));
    if (blobs.length == 1) {
      $(".blobSet_" + i).append("" + set[blobs].title + "");
    } else {
      $(".blobSet_" + i).append("" + sets[i].name + "");
    }
    $(".blobSet_" + i).addClass("text-center");
  }

  var setsContainer = document.getElementById("sets");
  var isChrome = !!window.chrome && !!window.chrome.webstore;
  var isFirefox = typeof InstallTrigger !== 'undefined';
  var isSafari = /constructor/i.test(window.HTMLElement) || (function(p) {
    return p.toString() === "[object SafariRemoteNotification]";
  })(!window['safari'] || safari.pushNotification);
  if (isFirefox) {
    setsContainer.addEventListener("DOMMouseScroll", scrollHorizontally, false);
  } else {
    setsContainer.addEventListener("mousewheel", MouseWheelHandler(), false);
  }
}

input.printInputInformationIcon = function(ddl_general){
  checkInputDescriptionIconVisibility(ddl_general);
  if(ddl_general.input_description) $('#inputs-description').addDescription(ddl_general.input_description);
}

function scrollHorizontally(e) {
  e = window.event || e;
  if (e.deltaY) deltaValue = e.deltaY;
  else deltaValue = e.wheelDelta;
  var delta = Math.max(-1, Math.min(1, (deltaValue || -e.detail)));
  this.scrollLeft -= (delta * 70);
  e.preventDefault();
}

function MouseWheelHandler() {
  return function(e) {
    // cross-browser wheel delta
    var e = window.event || e;
    // Get delta value from deltaY instead of wheelDelta for trackpad support.
    var deltaValue;
    if (e.deltaY) deltaValue = e.deltaY;
    else deltaValue = e.wheelDelta;
    var delta = Math.max(-1, Math.min(1, (deltaValue || -e.detail)));
    if (delta < 0) this.scrollLeft += 20;
    else this.scrollLeft -= 20;

    e.preventDefault();
    return false;
  }
}

function addSetClickEvent(blobSet, blobs) {
  blobSet.addClass("blobSet")
    .click(function() {
      var id_blobs = [];
      for (let i = 0; i < Object.keys(blobs).length; i++) {
        id_blobs.push(blobs[i].id);
      }
      helpers.addToStorage("demoSet", blobs);
      helpers.addToStorage("id_blobs", {"id_blobs" : id_blobs});
      helpers.setOrigin("blobSet");
      editor.printEditor();
    });
}

$.fn.addDescription = function(description) {
  var text = typeof description != 'string' ? description.join('') : description;
  if (description) $(this).attr('title', text);
}

function checkInputDescriptionIconVisibility(ddl_general) {
  if (ddl_general.input_description != undefined) $('#inputs-description').removeClass('di-none');
}
<commit_msg>[ClientApp2/description] No params description in DDL error fixed.<commit_after>var clientApp = clientApp || {};
var helpers = clientApp.helpers || {};
var input = input || {};
var upload = upload || {};
var editor = editor || {};

// Print in the web Interface the sets.
input.printSets = function(sets) {
  for (var i = 0; i < sets.length; i++) {
    var set = sets[i].blobs;
    var blobs = Object.keys(set);
    var name = sets[i].name;
    $(".setContainer").append("

")
      .append("

");
    var blobSet = $(".blobSet-body-" + i);
    var blobSetArray = [];

    addSetClickEvent(blobSet, set);

    blobSetArray += ""; // first photo
    if (blobs.length == 3) { // Middle photo (3 photos)
      blobSetArray += "";
    }
    if (blobs.length >= 4) { // +3 photo set. ···
      blobSetArray += "···";
    }
    if (blobs.length > 1) { // +1 photo. last photo.
      blobSetArray += "";
    }
    blobSet.html(blobSetArray);
    $(".blobSet_" + i).append($(".blobSet-body-" + i));
    if (blobs.length == 1) {
      $(".blobSet_" + i).append("" + set[blobs].title + "");
    } else {
      $(".blobSet_" + i).append("" + sets[i].name + "");
    }
    $(".blobSet_" + i).addClass("text-center");
  }

  var setsContainer = document.getElementById("sets");
  var isChrome = !!window.chrome && !!window.chrome.webstore;
  var isFirefox = typeof InstallTrigger !== 'undefined';
  var isSafari = /constructor/i.test(window.HTMLElement) || (function(p) {
    return p.toString() === "[object SafariRemoteNotification]";
  })(!window['safari'] || safari.pushNotification);
  if (isFirefox) {
    setsContainer.addEventListener("DOMMouseScroll", scrollHorizontally, false);
  } else {
    setsContainer.addEventListener("mousewheel", MouseWheelHandler(), false);
  }
}

input.printInputInformationIcon = function(ddl_general){
  checkInputDescriptionIconVisibility(ddl_general);
  $('#inputs-description').addDescription(ddl_general.input_description);
}

function scrollHorizontally(e) {
  e = window.event || e;
  if (e.deltaY) deltaValue = e.deltaY;
  else deltaValue = e.wheelDelta;
  var delta = Math.max(-1, Math.min(1, (deltaValue || -e.detail)));
  this.scrollLeft -= (delta * 70);
  e.preventDefault();
}

function MouseWheelHandler() {
  return function(e) {
    // cross-browser wheel delta
    var e = window.event || e;
    // Get delta value from deltaY instead of wheelDelta for trackpad support.
    var deltaValue;
    if (e.deltaY) deltaValue = e.deltaY;
    else deltaValue = e.wheelDelta;
    var delta = Math.max(-1, Math.min(1, (deltaValue || -e.detail)));
    if (delta < 0) this.scrollLeft += 20;
    else this.scrollLeft -= 20;

    e.preventDefault();
    return false;
  }
}

function addSetClickEvent(blobSet, blobs) {
  blobSet.addClass("blobSet")
    .click(function() {
      var id_blobs = [];
      for (let i = 0; i < Object.keys(blobs).length; i++) {
        id_blobs.push(blobs[i].id);
      }
      helpers.addToStorage("demoSet", blobs);
      helpers.addToStorage("id_blobs", {"id_blobs" : id_blobs});
      helpers.setOrigin("blobSet");
      editor.printEditor();
    });
}

$.fn.addDescription = function(description) {
  if(!description) return;
  var text = typeof description != 'string' ? description.join('') : description;
  if (description) $(this).attr('title', text);
}

function checkInputDescriptionIconVisibility(ddl_general) {
  if (ddl_general.input_description != undefined) $('#inputs-description').removeClass('di-none');
}
<|endoftext|>


<commit_before>// Copyright (c) 2012 VMware, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

/**
 * serve autocomplete results from local + remote cache.
 *
 * options:
 *  remoteDelay - number of milliseconds delay before firing remote request (default=200)
 *  remoteSource - function responsible for creating xhr to perform ajax request
 *
 * example usage:
 *  var cache = [];
 *  $('input').autocomplete({
 *    cache: cache,
 *    remoteSource: function(request, response) {
 *      return $.getJSON('/path/to/search', function(data) {
 *        response(data.entries);
 *      });
 *    }
 *  });
 */
(function($) {
  'use strict';
  var proto = $.ui.autocomplete.prototype;
  var origInitSource = proto._initSource;

  proto.options.cache = null;
  proto.options.remoteDelay = 200;
  proto.options.filter = $.ui.autocomplete.filter;

  $.extend(proto, {
    requestedTerm: null,
    currentXhr: null,
    remoteSourceDelay: null,
    _initSource: function() {
      if (this.options.cache) {
        //move options to top level object to fix scoping
        this.remoteSource = this.options.remoteSource;
        this.cache = this.options.cache;

        var self = this;
        this.element.on('autocompletesearch autocompleteclose', function() {
          self.abort();
        });

        this.source = this.localAndRemoteSource;
      } else {
        origInitSource.call(this);
      }
    },
    // abort current xhr and cancel pending remoteSource call
    abort: function() {
      if (this.remoteSourceDelay) {
        clearTimeout(this.remoteSourceDelay);
        this.remoteSourceDelay = null;
      }

      if (this.currentXhr) {
        this.currentXhr.abort();
        this.currentXhr = null;
      }
    },
    /**
     * append new results to existing autocomplete or show the autocomplete for the first time
     * adds new results to the cache
     * prevents display of results if input value has changed since the search was performed
     * @param results [Array] list of results for the requested term.  new entries will automatically be added to the cache
     */
    amendResponse: function(results) {
      var self = this;
      var newEntries = _.reject(this._normalize(results), function(entry) {
        return _.detect(self.cache, function(e) { return e.value === entry.value; });
      });
      this.cache = this.cache.concat(newEntries);
      if ((this.requestedTerm && this.requestedTerm !== this.element.val()) || !newEntries.length) {
        // input value has changed since the search was performed or there are
        // no new results to add
        return;
      }

      if (this.menu.element.is(':visible') && this.menu.element.children('.ui-menu-item').length) {
        // amend new entries to existing menu
        this._trigger('response', null, {content: newEntries});
        this._renderMenu(this.menu.element, newEntries);
        this.menu.refresh();
        this._resizeMenu();
      } else {
        // show menu for the first time
        this.__response.apply(this, [newEntries]);
      }
    },
    /**
     * remove an element from the cache
     */
    removeFromCache: function(element) {
      this.cache.splice(_.indexOf(this.cache, element), 1);
    },
    /**
     * show results from local cache immediately and prepare to fire off ajax request to load more results
     */
    localAndRemoteSource: function(request, response) {
      var localResults = this.options.filter.call(this, this.cache, request.term);
      response(localResults);

      var self = this;
      this.remoteSourceDelay = setTimeout(function() {
        var event = null;
        if (self._trigger("search", event) === false) {
          return;
        }

        self.requestedTerm = self.element.val();
        self.pending++;
        self.element.addClass('ui-autocomplete-loading');
        self.currentXhr = self.remoteSource(request, function() { self.amendResponse.apply(self, arguments); });
        self.currentXhr.always(function() {
          self.pending--;
          if (!self.pending) {
            self.element.removeClass('ui-autocomplete-loading');
          }
        });
      }, this.options.remoteDelay);
    }
  });
})(jQuery);
<commit_msg>remove unnecessary self variable<commit_after>// Copyright (c) 2012 VMware, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

/**
 * serve autocomplete results from local + remote cache.
 *
 * options:
 *  remoteDelay - number of milliseconds delay before firing remote request (default=200)
 *  remoteSource - function responsible for creating xhr to perform ajax request
 *
 * example usage:
 *  var cache = [];
 *  $('input').autocomplete({
 *    cache: cache,
 *    remoteSource: function(request, response) {
 *      return $.getJSON('/path/to/search', function(data) {
 *        response(data.entries);
 *      });
 *    }
 *  });
 */
(function($) {
  'use strict';
  var proto = $.ui.autocomplete.prototype;
  var origInitSource = proto._initSource;

  proto.options.cache = null;
  proto.options.remoteDelay = 200;
  proto.options.filter = $.ui.autocomplete.filter;

  $.extend(proto, {
    requestedTerm: null,
    currentXhr: null,
    remoteSourceDelay: null,
    _initSource: function() {
      if (this.options.cache) {
        //move options to top level object to fix scoping
        this.remoteSource = this.options.remoteSource;
        this.cache = this.options.cache;

        var self = this;
        this.element.on('autocompletesearch autocompleteclose', function() {
          self.abort();
        });

        this.source = this.localAndRemoteSource;
      } else {
        origInitSource.call(this);
      }
    },
    // abort current xhr and cancel pending remoteSource call
    abort: function() {
      if (this.remoteSourceDelay) {
        clearTimeout(this.remoteSourceDelay);
        this.remoteSourceDelay = null;
      }

      if (this.currentXhr) {
        this.currentXhr.abort();
        this.currentXhr = null;
      }
    },
    /**
     * append new results to existing autocomplete or show the autocomplete for the first time
     * adds new results to the cache
     * prevents display of results if input value has changed since the search was performed
     * @param results [Array] list of results for the requested term.  new entries will automatically be added to the cache
     */
    amendResponse: function(results) {
      var newEntries = _.reject(this._normalize(results), function(entry) {
        return _.detect(this.cache, function(e) { return e.value === entry.value; });
      }, this);
      this.cache = this.cache.concat(newEntries);
      if ((this.requestedTerm && this.requestedTerm !== this.element.val()) || !newEntries.length) {
        // input value has changed since the search was performed or there are
        // no new results to add
        return;
      }

      if (this.menu.element.is(':visible') && this.menu.element.children('.ui-menu-item').length) {
        // amend new entries to existing menu
        this._trigger('response', null, {content: newEntries});
        this._renderMenu(this.menu.element, newEntries);
        this.menu.refresh();
        this._resizeMenu();
      } else {
        // show menu for the first time
        this.__response.apply(this, [newEntries]);
      }
    },
    /**
     * remove an element from the cache
     */
    removeFromCache: function(element) {
      this.cache.splice(_.indexOf(this.cache, element), 1);
    },
    /**
     * show results from local cache immediately and prepare to fire off ajax request to load more results
     */
    localAndRemoteSource: function(request, response) {
      var localResults = this.options.filter.call(this, this.cache, request.term);
      response(localResults);

      var self = this;
      this.remoteSourceDelay = setTimeout(function() {
        var event = null;
        if (self._trigger("search", event) === false) {
          return;
        }

        self.requestedTerm = self.element.val();
        self.pending++;
        self.element.addClass('ui-autocomplete-loading');
        self.currentXhr = self.remoteSource(request, function() { self.amendResponse.apply(self, arguments); });
        self.currentXhr.always(function() {
          self.pending--;
          if (!self.pending) {
            self.element.removeClass('ui-autocomplete-loading');
          }
        });
      }, this.options.remoteDelay);
    }
  });
})(jQuery);
<|endoftext|>