<commit_before>define(function (require) {

  var React = require('react/addons'),
    Backbone = require('backbone'),
    stores = require('stores'),
    Router = require('react-router'),
    moment = require('moment'),
    Showdown = require('showdown'),
    ProjectResource = require('./ProjectResource.react');

  return React.createClass({
    displayName: "Project",

    propTypes: {
      project: React.PropTypes.instanceOf(Backbone.Model).isRequired,
      onClick: React.PropTypes.func,
      className: React.PropTypes.string,
      useRouter: React.PropTypes.bool
    },
    clicked: function () {
      //Not required by default.. Add-on implementation.
      if (!this.props.onClick) {
        return;
      }
      return this.props.onClick(this.props.project);
    },
    renderForRouter: function () {
      var project = this.props.project;
      return (

            <Router.Link to="project-resources"
              params={{projectId: project.id}}
              style={{color: "inherit"}}
            >
              {this.renderBody()}
            </Router.Link>

);
    },
    renderForClick: function () {
      var project = this.props.project;
      return (

            {this.renderBody()}

);
    },
    render: function () {
      var project = this.props.project,
        projectExternalLinks = stores.ProjectExternalLinkStore.getExternalLinksFor(project),
        projectInstances = stores.ProjectInstanceStore.getInstancesFor(project),
        projectImages = stores.ProjectImageStore.getImagesFor(project),
        projectVolumes = stores.ProjectVolumeStore.getVolumesFor(project);

      if (!project.id || !projectExternalLinks || !projectInstances || !projectVolumes || !projectImages) {
        return (

{project.get('name')}

        );
      }

      if (this.props.useRouter == false) {
        return this.renderForClick();
      } else {
        return this.renderForRouter();
      }
    },
    renderBody: function () {
      var project = this.props.project,
        converter = new Showdown.Converter(),
        description = project.get('description'),
        descriptionHtml = converter.makeHtml(description),
        projectExternalLinks = stores.ProjectExternalLinkStore.getExternalLinksFor(project),
        projectInstances = stores.ProjectInstanceStore.getInstancesFor(project),
        projectVolumes = stores.ProjectVolumeStore.getVolumesFor(project),
        projectImages = stores.ProjectImageStore.getImagesFor(project),
        projectCreationDate = moment(project.get('start_date')).format("MMM D, YYYY hh:mm a");

      return (

{project.get('name')}

            <time>{"Created " + projectCreationDate}</time>

            <ProjectResource icon={"tasks"}
                             count={projectInstances.length}
                             resourceType={"instances"}
              />
            <ProjectResource icon={"hdd"}
                             count={projectVolumes.length}
                             resourceType={"volumes"}
              />
            <ProjectResource icon={"floppy-disk"}
                             count={projectImages.length}
                             resourceType={"images"}
              />
            <ProjectResource icon={"globe"}
                             count={projectExternalLinks.length}
                             resourceType={"links"}
              />

      );
    }
  });

});
<commit_msg>Reformat Project to media card structure<commit_after>define(function (require) {

  var React = require('react/addons'),
    Backbone = require('backbone'),
    stores = require('stores'),
    Router = require('react-router'),
    moment = require('moment'),
    Showdown = require('showdown'),
    ProjectResource = require('./ProjectResource.react');

  return React.createClass({
    displayName: "Project",

    propTypes: {
      project: React.PropTypes.instanceOf(Backbone.Model).isRequired,
      onClick: React.PropTypes.func,
      className: React.PropTypes.string,
      useRouter: React.PropTypes.bool
    },
    clicked: function () {
      //Not required by default.. Add-on implementation.
      if (!this.props.onClick) {
        return;
      }
      return this.props.onClick(this.props.project);
    },
    renderForRouter: function () {
      var project = this.props.project;
      return (

            <Router.Link to="project-resources"
              params={{projectId: project.id}}
              style={{color: "inherit"}}
            >
              {this.renderBody()}
            </Router.Link>

);
    },
    renderForClick: function () {
      var project = this.props.project;
      return (

            {this.renderBody()}

);
    },
    render: function () {
      var project = this.props.project,
        projectExternalLinks = stores.ProjectExternalLinkStore.getExternalLinksFor(project),
        projectInstances = stores.ProjectInstanceStore.getInstancesFor(project),
        projectImages = stores.ProjectImageStore.getImagesFor(project),
        projectVolumes = stores.ProjectVolumeStore.getVolumesFor(project);

      if (!project.id || !projectExternalLinks || !projectInstances || !projectVolumes || !projectImages) {
        return (

{project.get('name')}

        );
      }

      if (this.props.useRouter == false) {
        return this.renderForClick();
      } else {
        return this.renderForRouter();
      }
    },
    renderBody: function () {
      var project = this.props.project,
        converter = new Showdown.Converter(),
        description = project.get('description'),
        projectExternalLinks = stores.ProjectExternalLinkStore.getExternalLinksFor(project),
        projectInstances = stores.ProjectInstanceStore.getInstancesFor(project),
        projectVolumes = stores.ProjectVolumeStore.getVolumesFor(project),
        projectImages = stores.ProjectImageStore.getImagesFor(project),
        projectCreationDate = moment(project.get('start_date')).format("MMM D, YYYY hh:mm a");

      return (

{project.get('name')}

            <time className="t-caption" style={{display: "block"}}>{"Created " + projectCreationDate}</time>

                {description}

                <ProjectResource icon={"tasks"}
                                count={projectInstances.length}
                                resourceType={"instances"}
                />
                <ProjectResource icon={"hdd"}
                                count={projectVolumes.length}
                                resourceType={"volumes"}
                />
                <ProjectResource icon={"floppy-disk"}
                                count={projectImages.length}
                                resourceType={"images"}
                />
                <ProjectResource icon={"globe"}
                                count={projectExternalLinks.length}
                                resourceType={"links"}
                />

      );
    }
  });

});
<|endoftext|>


<commit_before>/************************************************************************
 *  Copyright 2010-2011 Worlize Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 ***********************************************************************/

var crypto = require('crypto');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var WebSocketConnection = require('./WebSocketConnection');

var headerValueSplitRegExp = /,\s*/;
var headerParamSplitRegExp = /;\s*/;
var headerSanitizeRegExp = /[\r\n]/g;
var protocolSeparators = [
	"(", ")", "<", ">", "@",
	",", ";", ":", "\\", "\"",
	"/", "[", "]", "?", "=",
	"{", "}", " ", String.fromCharCode(9)
];

var httpStatusDescriptions = {
    100: "Continue",
    101: "Switching Protocols",
    200: "OK",
    201: "Created",
    203: "Non-Authoritative Information",
    204: "No Content",
    205: "Reset Content",
    206: "Partial Content",
    300: "Multiple Choices",
    301: "Moved Permanently",
    302: "Found",
    303: "See Other",
    304: "Not Modified",
    305: "Use Proxy",
    307: "Temporary Redirect",
    400: "Bad Request",
    401: "Unauthorized",
    402: "Payment Required",
    403: "Forbidden",
    404: "Not Found",
    406: "Not Acceptable",
    407: "Proxy Authorization Required",
    408: "Request Timeout",
    409: "Conflict",
    410: "Gone",
    411: "Length Required",
    412: "Precondition Failed",
    413: "Request Entity Too Long",
    414: "Request-URI Too Long",
    415: "Unsupported Media Type",
    416: "Requested Range Not Satisfiable",
    417: "Expectation Failed",
    426: "Upgrade Required",
    500: "Internal Server Error",
    501: "Not Implemented",
    502: "Bad Gateway",
    503: "Service Unavailable",
    504: "Gateway Timeout",
    505: "HTTP Version Not Supported"
};

function WebSocketRequest(socket, httpRequest, serverConfig) {
    this.socket = socket;
    this.httpRequest = httpRequest;
    this.resource = httpRequest.url;
    this.remoteAddress = socket.remoteAddress;
    this.serverConfig = serverConfig;
};

util.inherits(WebSocketRequest, EventEmitter);

WebSocketRequest.prototype.readHandshake = function() {
    var request = this.httpRequest;
    this.host = request.headers['host'];
    if (!this.host) {
        throw new Error("Client must provide a Host header.");
    }

    this.key = request.headers['sec-websocket-key'];
    if (!this.key) {
        throw new Error("Client must provide a value for Sec-WebSocket-Key.");
    }

    this.websocketVersion = parseInt(request.headers['sec-websocket-version'], 10);

    if (!this.websocketVersion || isNaN(this.websocketVersion)) {
        throw new Error("Client must provide a value for Sec-WebSocket-Version.");
    }

    switch (this.websocketVersion) {
        case 8:
        case 13:
            break;
        default:
            var e = new Error("Unsupported websocket client version: " + this.websocketVersion +
                              "Only versions 8 and 13 are supported.");
            e.httpCode = 426;
            e.headers = {
                "Sec-WebSocket-Version": "13"
            };
            throw e;
    }

    if (this.websocketVersion === 13) {
        this.origin = request.headers['origin'];
    }
    else if (this.websocketVersion === 8) {
        this.origin = request.headers['sec-websocket-origin'];
    }

    // Protocol is optional.
    var protocolString = request.headers['sec-websocket-protocol'];
    if (protocolString) {
        this.requestedProtocols = protocolString.toLocaleLowerCase().split(headerValueSplitRegExp);
    }
    else {
        this.requestedProtocols = [];
    }

    if (request.headers['x-forwarded-for']) {
        this.remoteAddress = request.headers['x-forwarded-for'].split(', ')[0];
    }

    // Extensions are optional.
    var extensionsString = request.headers['sec-websocket-extensions'];
    this.requestedExtensions = this.parseExtensions(extensionsString);
};

WebSocketRequest.prototype.parseExtensions = function(extensionsString) {
    if (!extensionsString || extensionsString.length === 0) {
        return [];
    }
    extensions = extensionsString.toLocaleLowerCase().split(headerValueSplitRegExp);
    extensions.forEach(function(extension, index, array) {
        var params = extension.split(headerParamSplitRegExp);
        var extensionName = params[0];
        var extensionParams = params.slice(1);
        extensionParams.forEach(function(rawParam, index, array) {
            var arr = rawParam.split('=');
            var obj = {
                name: arr[0],
                value: arr[1]
            };
            array.splice(index, 1, obj);
        });
        var obj = {
            name: extensionName,
            params: extensionParams
        };
        array.splice(index, 1, obj);
    });
    return extensions;
},
WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, cookies) {
    // TODO: Handle extensions
    var connection = new WebSocketConnection(this.socket, [], acceptedProtocol, false, this.serverConfig);

    connection.websocketVersion = this.websocketVersion;
    connection.remoteAddress = this.remoteAddress;

    // Create key validation hash
    var sha1 = crypto.createHash('sha1');
    sha1.update(this.key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
    var acceptKey = sha1.digest('base64');

    var response = "HTTP/1.1 101 Switching Protocols\r\n" +
                   "Upgrade: websocket\r\n" +
                   "Connection: Upgrade\r\n" +
                   "Sec-WebSocket-Accept: " + acceptKey + "\r\n";

    if (acceptedProtocol) {
        // validate protocol
		for (var i=0; i < acceptedProtocol.length; i++) {
			var charCode = acceptedProtocol.charCodeAt(i);
			var character = acceptedProtocol.charAt(i);
			if (charCode < 0x21 || charCode > 0x7E || protocolSeparators.indexOf(character) !== -1) {
			    this.reject(500);
				throw new Error("Illegal character '" + String.fromCharCode(character) + "' in subprotocol.");
			}
		}
		if (this.requestedProtocols.indexOf(acceptedProtocol) === -1) {
		    this.reject(500);
		    throw new Error("Specified protocol was not requested by the client.");
		}

        acceptedProtocol = acceptedProtocol.replace(headerSanitizeRegExp, '');
        response += "Sec-WebSocket-Protocol: " + acceptedProtocol + "\r\n";
    }
    if (allowedOrigin) {
        allowedOrigin = allowedOrigin.replace(headerSanitizeRegExp, '');
        if (this.websocketVersion === 13) {
            response += "Origin: " + allowedOrigin + "\r\n";
        }
        else if (this.websocketVersion === 8) {
            response += "Sec-WebSocket-Origin: " + allowedOrigin + "\r\n";
        }
    }

    if (cookies) {
    	for(var c in cookies){
    		var cookie = cookies[c];
    		if(!cookie.name || !cookie.value) throw new Error("No cookie name value pair specified in cookie");

    		//token
    		var invalidChar = cookie.name.match(/([\x00-\x20\x22\x28\x29\x2c\x2f\x3a-\x3f\x40\x5b-\x5e\x7b\x7d\x7f])/);
    		if(invalidChar) throw new Error("Illegal character(s) " + invalidChar[0] + " in cookie name");

    		// *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) | %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
    		invalidChar = cookie.value.match(/[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]/) //TODO: add double quote handling
    		if(invalidChar) throw new Error("Illegal character(s) " + invalidChar[0] + " in cookie name");

    		response += "Set-Cookie: " + cookie.name + "="  + cookie.value + ";";
    		if(cookie.expires)  response += "Expires=" + cookie.expires + ";";
    		if(cookie.maxage)  response += "Max-Age=" + cookie.maxage + ";";
    		if(cookie.domain)  response += "Domain=" + cookie.domain + ";";
    		if(cookie.path)  response += "Expires=" + cookie.path + ";";
    		if(cookie.secure)  response += "Secure;";
    		if(cookie.httponly)  response += "HttpOnly;";

    		response += "\r\n";

    	}
    }
    // TODO: handle negotiated extensions
    // if (negotiatedExtensions) {
    //     response += "Sec-WebSocket-Extensions: " + negotiatedExtensions.join(", ") + "\r\n";
    // }
    response += "\r\n";

    this.socket.write(response, 'ascii');

    this.emit('requestAccepted', connection);

    return connection;
};

WebSocketRequest.prototype.reject = function(status, reason, extraHeaders) {
    if (typeof(status) !== 'number') {
        status = 403;
    }
    var response = "HTTP/1.1 " + status + " " + httpStatusDescriptions[status] + "\r\n" +
                   "Connection: close\r\n";
    if (reason) {
        reason = reason.replace(headerSanitizeRegExp, '');
        response += "X-WebSocket-Reject-Reason: " + reason + "\r\n";
    }

    if (extraHeaders) {
        for (var key in extraHeaders) {
            var sanitizedValue = extraHeaders[key].toString().replace(headerSanitizeRegExp, '');
            var sanitizedKey = key.replace(headerSanitizeRegExp, '');
            response += (key + ": " + sanitizedValue + "\r\n");
        }
    }

    response += "\r\n";
    this.socket.end(response, 'ascii');

    this.emit('requestRejected', this);
};

module.exports = WebSocketRequest;<commit_msg>Added a few more RCF validations to set cookie<commit_after>/************************************************************************
 *  Copyright 2010-2011 Worlize Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 ***********************************************************************/

var crypto = require('crypto');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var WebSocketConnection = require('./WebSocketConnection');

var headerValueSplitRegExp = /,\s*/;
var headerParamSplitRegExp = /;\s*/;
var headerSanitizeRegExp = /[\r\n]/g;
var protocolSeparators = [
	"(", ")", "<", ">", "@",
	",", ";", ":", "\\", "\"",
	"/", "[", "]", "?", "=",
	"{", "}", " ", String.fromCharCode(9)
];

var httpStatusDescriptions = {
    100: "Continue",
    101: "Switching Protocols",
    200: "OK",
    201: "Created",
    203: "Non-Authoritative Information",
    204: "No Content",
    205: "Reset Content",
    206: "Partial Content",
    300: "Multiple Choices",
    301: "Moved Permanently",
    302: "Found",
    303: "See Other",
    304: "Not Modified",
    305: "Use Proxy",
    307: "Temporary Redirect",
    400: "Bad Request",
    401: "Unauthorized",
    402: "Payment Required",
    403: "Forbidden",
    404: "Not Found",
    406: "Not Acceptable",
    407: "Proxy Authorization Required",
    408: "Request Timeout",
    409: "Conflict",
    410: "Gone",
    411: "Length Required",
    412: "Precondition Failed",
    413: "Request Entity Too Long",
    414: "Request-URI Too Long",
    415: "Unsupported Media Type",
    416: "Requested Range Not Satisfiable",
    417: "Expectation Failed",
    426: "Upgrade Required",
    500: "Internal Server Error",
    501: "Not Implemented",
    502: "Bad Gateway",
    503: "Service Unavailable",
    504: "Gateway Timeout",
    505: "HTTP Version Not Supported"
};

function WebSocketRequest(socket, httpRequest, serverConfig) {
    this.socket = socket;
    this.httpRequest = httpRequest;
    this.resource = httpRequest.url;
    this.remoteAddress = socket.remoteAddress;
    this.serverConfig = serverConfig;
};

util.inherits(WebSocketRequest, EventEmitter);

WebSocketRequest.prototype.readHandshake = function() {
    var request = this.httpRequest;
    this.host = request.headers['host'];
    if (!this.host) {
        throw new Error("Client must provide a Host header.");
    }

    this.key = request.headers['sec-websocket-key'];
    if (!this.key) {
        throw new Error("Client must provide a value for Sec-WebSocket-Key.");
    }

    this.websocketVersion = parseInt(request.headers['sec-websocket-version'], 10);

    if (!this.websocketVersion || isNaN(this.websocketVersion)) {
        throw new Error("Client must provide a value for Sec-WebSocket-Version.");
    }

    switch (this.websocketVersion) {
        case 8:
        case 13:
            break;
        default:
            var e = new Error("Unsupported websocket client version: " + this.websocketVersion +
                              "Only versions 8 and 13 are supported.");
            e.httpCode = 426;
            e.headers = {
                "Sec-WebSocket-Version": "13"
            };
            throw e;
    }

    if (this.websocketVersion === 13) {
        this.origin = request.headers['origin'];
    }
    else if (this.websocketVersion === 8) {
        this.origin = request.headers['sec-websocket-origin'];
    }

    // Protocol is optional.
    var protocolString = request.headers['sec-websocket-protocol'];
    if (protocolString) {
        this.requestedProtocols = protocolString.toLocaleLowerCase().split(headerValueSplitRegExp);
    }
    else {
        this.requestedProtocols = [];
    }

    if (request.headers['x-forwarded-for']) {
        this.remoteAddress = request.headers['x-forwarded-for'].split(', ')[0];
    }

    // Extensions are optional.
    var extensionsString = request.headers['sec-websocket-extensions'];
    this.requestedExtensions = this.parseExtensions(extensionsString);
};

WebSocketRequest.prototype.parseExtensions = function(extensionsString) {
    if (!extensionsString || extensionsString.length === 0) {
        return [];
    }
    extensions = extensionsString.toLocaleLowerCase().split(headerValueSplitRegExp);
    extensions.forEach(function(extension, index, array) {
        var params = extension.split(headerParamSplitRegExp);
        var extensionName = params[0];
        var extensionParams = params.slice(1);
        extensionParams.forEach(function(rawParam, index, array) {
            var arr = rawParam.split('=');
            var obj = {
                name: arr[0],
                value: arr[1]
            };
            array.splice(index, 1, obj);
        });
        var obj = {
            name: extensionName,
            params: extensionParams
        };
        array.splice(index, 1, obj);
    });
    return extensions;
},
WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, cookies) {
    // TODO: Handle extensions
    var connection = new WebSocketConnection(this.socket, [], acceptedProtocol, false, this.serverConfig);

    connection.websocketVersion = this.websocketVersion;
    connection.remoteAddress = this.remoteAddress;

    // Create key validation hash
    var sha1 = crypto.createHash('sha1');
    sha1.update(this.key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
    var acceptKey = sha1.digest('base64');

    var response = "HTTP/1.1 101 Switching Protocols\r\n" +
                   "Upgrade: websocket\r\n" +
                   "Connection: Upgrade\r\n" +
                   "Sec-WebSocket-Accept: " + acceptKey + "\r\n";

    if (acceptedProtocol) {
        // validate protocol
		for (var i=0; i < acceptedProtocol.length; i++) {
			var charCode = acceptedProtocol.charCodeAt(i);
			var character = acceptedProtocol.charAt(i);
			if (charCode < 0x21 || charCode > 0x7E || protocolSeparators.indexOf(character) !== -1) {
			    this.reject(500);
				throw new Error("Illegal character '" + String.fromCharCode(character) + "' in subprotocol.");
			}
		}
		if (this.requestedProtocols.indexOf(acceptedProtocol) === -1) {
		    this.reject(500);
		    throw new Error("Specified protocol was not requested by the client.");
		}

        acceptedProtocol = acceptedProtocol.replace(headerSanitizeRegExp, '');
        response += "Sec-WebSocket-Protocol: " + acceptedProtocol + "\r\n";
    }
    if (allowedOrigin) {
        allowedOrigin = allowedOrigin.replace(headerSanitizeRegExp, '');
        if (this.websocketVersion === 13) {
            response += "Origin: " + allowedOrigin + "\r\n";
        }
        else if (this.websocketVersion === 8) {
            response += "Sec-WebSocket-Origin: " + allowedOrigin + "\r\n";
        }
    }

    if (cookies) {
    	for(var c in cookies){
    		var cookie = cookies[c];

    		if(!cookie.name || !cookie.value) throw new Error("No cookie name value pair specified in cookie");

    		//token
    		var invalidChar = cookie.name.match(/([\x00-\x20\x22\x28\x29\x2c\x2f\x3a-\x3f\x40\x5b-\x5e\x7b\x7d\x7f])/);
    		if(invalidChar) throw new Error("Illegal character " + invalidChar[0] + " in cookie name");

    		// *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) | %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
    		invalidChar = cookie.value.match(/[^\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]/) //TODO: add double quote handling
    		if(invalidChar) throw new Error("Illegal character " + invalidChar[0] + " in cookie name");

    		// "Path=" path-value | <any CHAR except CTLs or ";">
    		if(cookie.path){
    			invalidChar = cookie.path.match(/[\x00-\x20\x3b]/)
    			if(invalidChar) throw new Error("Illegal character " + invalidChar[0] + " in cookie path");
    		}

    		//TODO: add validation for domain

    		//"Expires=" sane-cookie-date | Force Date object requirement by using only epoch
    		if(cookie.expires && typeof cookie.expires.getTime() !== "number") throw new Error("Invalid date object in cookie expires");

    		//"Max-Age=" non-zero-digit *DIGIT
    		if(cookie.maxage && (isNaN(parseInt(cookie.maxage)) || parseInt(cookie.maxage) <= 0)) throw new Error("Cookie max-age must be a non-zero digit");

    		//"Secure;"
    		if(cookie.secure && typeof cookie.secure !== "boolean") throw new Error("Cookie secure must be of type boolean");

    		//"HttpOnly;"
    		if(cookie.httponly && typeof cookie.httponly !== "boolean") throw new Error("Cookie http only must be of type boolean");

    		response += "Set-Cookie: " + cookie.name + "="  + cookie.value + ";";
    		if(cookie.expires)  response += " Expires=" + new Date(cookie.expires.getTime()).toGMTString() + ";";
    		if(cookie.maxage)  response += " Max-Age=" + parseInt(cookie.maxage) + ";";
    		if(cookie.domain)  response += " Domain=" + cookie.domain + ";";
    		if(cookie.path)  response += " Expires=" + cookie.path + ";";
    		if(cookie.secure)  response += " Secure;";
    		if(cookie.httponly)  response += " HttpOnly;";

    		response += "\r\n";
    		console.log("RESPONSE: " + response);
    	}
    }
    // TODO: handle negotiated extensions
    // if (negotiatedExtensions) {
    //     response += "Sec-WebSocket-Extensions: " + negotiatedExtensions.join(", ") + "\r\n";
    // }
    response += "\r\n";

    this.socket.write(response, 'ascii');

    this.emit('requestAccepted', connection);

    return connection;
};

WebSocketRequest.prototype.reject = function(status, reason, extraHeaders) {
    if (typeof(status) !== 'number') {
        status = 403;
    }
    var response = "HTTP/1.1 " + status + " " + httpStatusDescriptions[status] + "\r\n" +
                   "Connection: close\r\n";
    if (reason) {
        reason = reason.replace(headerSanitizeRegExp, '');
        response += "X-WebSocket-Reject-Reason: " + reason + "\r\n";
    }

    if (extraHeaders) {
        for (var key in extraHeaders) {
            var sanitizedValue = extraHeaders[key].toString().replace(headerSanitizeRegExp, '');
            var sanitizedKey = key.replace(headerSanitizeRegExp, '');
            response += (key + ": " + sanitizedValue + "\r\n");
        }
    }

    response += "\r\n";
    this.socket.end(response, 'ascii');

    this.emit('requestRejected', this);
};

module.exports = WebSocketRequest;<|endoftext|>


<commit_before>function makeLink(url, text) {
    var link = $('<a />', {
        href: url,
        text: text,
    });
    return $('<div />').append(link).html();
}

function makeList(container, options) {
    $.getJSON(API_BASE + options.endpoint)
        .done(function (json) {
            $container = $(container);
            $container.append($('<h3 />').text(options.title).addClass('text-center'));
            $list = $('<ul />');

            data = json.data;
            data.forEach(function (e) {
                $item = $('<li />');
                if (typeof options.keyName === 'function') {
                    $item.html(options.keyName(e));
                } else {
                    $item.html(e[options.keyName]);
                }
                $list.append($item);
            });
            $container.append($list);
        })
        .fail(displayFailMessage);
}

function makeXYGraph(container, options) {
    $.getJSON(API_BASE + options.endpoint)
        .done(function (json) {
            data = json.data;
            $(container).highcharts({
                chart: {
                    type: options.type
                },
                title: {
                    text: options.title
                },
                subtitle: {
                    text: options.subtitle
                },
                xAxis: {
                    categories: data.map(function (e) {
                        if (typeof options.keyName === 'function') {
                            return options.keyName(e);
                        }
                        return e[options.keyName];
                    })
                },
                yAxis: {
                    min: 0,
                    title: {
                        text: options.yTitle
                    }
                },
                legend: {
                    enabled: false
                },
                series: [{
                    name: options.label,
                    data: data.map(function (e) {
                        if (typeof options.valueName === 'function') {
                            return options.valueName(e);
                        }
                        return e[options.valueName];
                    })
                }]
            });
        })
        .fail(displayFailMessage);
}

/*
* Populate the User Issues card
*/
function getUserIssues () {

    $this = $('.js-handler--github-username');
    $this.data('timeout-id', '');
    var username = $this.val().trim();
    if (username) {
        $.get(API_BASE + username +'/issues_assigned')
            .success(function (data) {
                console.log(data);
                var source   = $("#user-issues").html();
                var template = Handlebars.compile(source);
                var issues = data.data.length ? data.data.join('') : 'No issues assigned to this user';
                var context = {user: username, list: issues}
                var html    = template(context);
                $('.template-user-issues').empty().append(html);

            })
            .fail(function (data) {
                $('.template-user-issues').empty().text('Failed to retrieve data');
            });
    }

}

function drawGraphs() {
    makeXYGraph('#most-active-people', {
        endpoint: '/most_active_people',
        type: 'bar',
        title: "Most active people",
        keyName: function (e) {
            return makeLink("http://github.com/" + e.term, e.term);
        },
        valueName: 'count',
        yTitle: 'Events',
        label: 'events'
    });

    makeXYGraph('#total-events-monthly', {
        endpoint: '/total_events_monthly',
        type: 'area',
        title: "Activity",
        subtitle: "Total monthly events",
        keyName: 'month',
        valueName: 'value',
        yTitle: 'Events',
        label: 'events'
    });

    makeXYGraph('#most-active-issues', {
        endpoint: '/most_active_issues',
        type: 'bar',
        title: "Most active issues",
        keyName: function (e) {
            return makeLink('http://github.com/' + REPO + '/issues/' + e.term,
                            "#" + e.term);
        },
        valueName: 'count',
        yTitle: 'Events',
        label: 'events'
    });

    /*
    makeList('#issues-without-comments', {
        endpoint: 'gabrielfalcao/lettuce/issues_without_comments',
        title: "Issues without comments",
        keyName: function (e) {
            return makeLink("http://github.com/gabrielfalcao/lettuce/issues/" + e,
                            "#" + e);
        }
    });
   */

    /*
    * Listen for keyup events and fetch data for specified user
    * has a 200ms delay between keyup and actual GET request
    * to prevent firing a cascade of requests
    */
   /*
    $('.js-handler--github-username').off('keyup');
    $('.js-handler--github-username').on('keyup', function () {

        var timeout_id = parseInt($(this).data('timeout-id'), 10) || null;
        if (timeout_id) clearTimeout(timeout_id);
        timeout_id = setTimeout(getUserIssues, 200);
        $(this).data('timeout-id', timeout_id);

    });
   */
}

<commit_msg>Remove stray log call.<commit_after>function makeLink(url, text) {
    var link = $('<a />', {
        href: url,
        text: text,
    });
    return $('<div />').append(link).html();
}

function makeList(container, options) {
    $.getJSON(API_BASE + options.endpoint)
        .done(function (json) {
            $container = $(container);
            $container.append($('<h3 />').text(options.title).addClass('text-center'));
            $list = $('<ul />');

            data = json.data;
            data.forEach(function (e) {
                $item = $('<li />');
                if (typeof options.keyName === 'function') {
                    $item.html(options.keyName(e));
                } else {
                    $item.html(e[options.keyName]);
                }
                $list.append($item);
            });
            $container.append($list);
        })
        .fail(displayFailMessage);
}

function makeXYGraph(container, options) {
    $.getJSON(API_BASE + options.endpoint)
        .done(function (json) {
            data = json.data;
            $(container).highcharts({
                chart: {
                    type: options.type
                },
                title: {
                    text: options.title
                },
                subtitle: {
                    text: options.subtitle
                },
                xAxis: {
                    categories: data.map(function (e) {
                        if (typeof options.keyName === 'function') {
                            return options.keyName(e);
                        }
                        return e[options.keyName];
                    })
                },
                yAxis: {
                    min: 0,
                    title: {
                        text: options.yTitle
                    }
                },
                legend: {
                    enabled: false
                },
                series: [{
                    name: options.label,
                    data: data.map(function (e) {
                        if (typeof options.valueName === 'function') {
                            return options.valueName(e);
                        }
                        return e[options.valueName];
                    })
                }]
            });
        })
        .fail(displayFailMessage);
}

/*
* Populate the User Issues card
*/
function getUserIssues () {

    $this = $('.js-handler--github-username');
    $this.data('timeout-id', '');
    var username = $this.val().trim();
    if (username) {
        $.get(API_BASE + username +'/issues_assigned')
            .success(function (data) {
                var source   = $("#user-issues").html();
                var template = Handlebars.compile(source);
                var issues = data.data.length ? data.data.join('') : 'No issues assigned to this user';
                var context = {user: username, list: issues}
                var html    = template(context);
                $('.template-user-issues').empty().append(html);

            })
            .fail(function (data) {
                $('.template-user-issues').empty().text('Failed to retrieve data');
            });
    }

}

function drawGraphs() {
    makeXYGraph('#most-active-people', {
        endpoint: '/most_active_people',
        type: 'bar',
        title: "Most active people",
        keyName: function (e) {
            return makeLink("http://github.com/" + e.term, e.term);
        },
        valueName: 'count',
        yTitle: 'Events',
        label: 'events'
    });

    makeXYGraph('#total-events-monthly', {
        endpoint: '/total_events_monthly',
        type: 'area',
        title: "Activity",
        subtitle: "Total monthly events",
        keyName: 'month',
        valueName: 'value',
        yTitle: 'Events',
        label: 'events'
    });

    makeXYGraph('#most-active-issues', {
        endpoint: '/most_active_issues',
        type: 'bar',
        title: "Most active issues",
        keyName: function (e) {
            return makeLink('http://github.com/' + REPO + '/issues/' + e.term,
                            "#" + e.term);
        },
        valueName: 'count',
        yTitle: 'Events',
        label: 'events'
    });

    /*
    makeList('#issues-without-comments', {
        endpoint: 'gabrielfalcao/lettuce/issues_without_comments',
        title: "Issues without comments",
        keyName: function (e) {
            return makeLink("http://github.com/gabrielfalcao/lettuce/issues/" + e,
                            "#" + e);
        }
    });
   */

    /*
    * Listen for keyup events and fetch data for specified user
    * has a 200ms delay between keyup and actual GET request
    * to prevent firing a cascade of requests
    */
   /*
    $('.js-handler--github-username').off('keyup');
    $('.js-handler--github-username').on('keyup', function () {

        var timeout_id = parseInt($(this).data('timeout-id'), 10) || null;
        if (timeout_id) clearTimeout(timeout_id);
        timeout_id = setTimeout(getUserIssues, 200);
        $(this).data('timeout-id', timeout_id);

    });
   */
}

<|endoftext|>


<commit_before>module.exports.safe = false;

/*
Purpose:
  Executes a custom javascript function.
  The custom function may take either one parameter (for synchronous invocation)
  or two parameters (for asynchronous invocation).

  The first parameter is the hyperpotamus context. The hyperpotamus context exposes information about the
  current request, response, session, and other utility methods.

  If the function does not take a second parameter, then it will be invoked synchronously. If a synchronous
  function returns void/null, it will be considered successful, otherwise, any returned object will be
  considered an error.

  If the function takes a second parameter, it is the callback function to be invoked upon completion.
  Any parameter passed to the callback will be considered an error and handled by hyperpotamus.

  Error objects returned from custom functions may include a .message property and may also specify a
  .goto property which is the name of the next request to execute, or one of the special jump_keys
  (SELF, END, NEXT). As a special case if a string is returned, it will be treated as the .message.

  The javascript context (this) for the execution of the custom function will be bound to the action element itself, so any additional properties on the action can be
  accessed from inside the function.

  The .imports property is optional, but if present must either be an array or a key/value object. The .imports object/array specifies javascript
  libraries to be imported and made available for the custom function's use. Each value will be imported by hyperpotamus (using require()) and
  the value will be replaced by the imported module.

  NOTE: When defining javascript properties in YAML, tab-indentation is still not allowed.

Examples:
  function: !!js/function >
   function(context) {
     if(context.response.status > 200)
       return "Unexpected status code";
   }

=====

  value_to_hash: <% session_value %>
  imports:
    crypto: crypto
  function: !!js/function >
    function(context, callback) {
      var md5 = this.imports.crypto.createHash("MD5");
      md5.update(this.value_to_hash);
      context.session["hash"] = md5.digest("hex");
      callback();
    }
}
*/

var _ = require("lodash");
var verror = require("verror");
var Promise = require("bluebird");

module.exports.normalize = function normalize(action) {
	if (_.isFunction(action)) {
		action = { function: action };
		return action;
	}
};

module.exports.process = function process(context) {
	if (_.isObject(this.imports)) { // Works for arrays too
		// TODO - Check for relative path and map to be relative to the script
		for (var key in this.imports) {
			module.exports.logger.info("Attempting to import - " + this.imports[key] + " to support 'function' action.");
			this.imports[key] = require(this.imports[key]);
		}
	}
	module.exports.logger.debug("About to invoke custom function");
	var func = this["function"];
	if (func.length == 1) { // Synchronous function
		var result = func.call(this, context);
		if(result && result.then) {
			return result;
		}
	}
	else if (func.length == 2) {
		return Promise.promisify(func).call(this, context);
	}
	else {
		throw new verror.VError({
			name: "InvalidCustomFunction",
			info: {
				path: this.path
			}
		}, "Custom function has unexpected number of arguments. Must take 1 or 2 arguments.");
	}
};
<commit_msg>Updated logging levels for function action<commit_after>module.exports.safe = false;

/*
Purpose:
  Executes a custom javascript function.
  The custom function may take either one parameter (for synchronous invocation)
  or two parameters (for asynchronous invocation).

  The first parameter is the hyperpotamus context. The hyperpotamus context exposes information about the
  current request, response, session, and other utility methods.

  If the function does not take a second parameter, then it will be invoked synchronously. If a synchronous
  function returns void/null, it will be considered successful, otherwise, any returned object will be
  considered an error.

  If the function takes a second parameter, it is the callback function to be invoked upon completion.
  Any parameter passed to the callback will be considered an error and handled by hyperpotamus.

  Error objects returned from custom functions may include a .message property and may also specify a
  .goto property which is the name of the next request to execute, or one of the special jump_keys
  (SELF, END, NEXT). As a special case if a string is returned, it will be treated as the .message.

  The javascript context (this) for the execution of the custom function will be bound to the action element itself, so any additional properties on the action can be
  accessed from inside the function.

  The .imports property is optional, but if present must either be an array or a key/value object. The .imports object/array specifies javascript
  libraries to be imported and made available for the custom function's use. Each value will be imported by hyperpotamus (using require()) and
  the value will be replaced by the imported module.

  NOTE: When defining javascript properties in YAML, tab-indentation is still not allowed.

Examples:
  function: !!js/function >
   function(context) {
     if(context.response.status > 200)
       return "Unexpected status code";
   }

=====

  value_to_hash: <% session_value %>
  imports:
    crypto: crypto
  function: !!js/function >
    function(context, callback) {
      var md5 = this.imports.crypto.createHash("MD5");
      md5.update(this.value_to_hash);
      context.session["hash"] = md5.digest("hex");
      callback();
    }
}
*/

var _ = require("lodash");
var verror = require("verror");
var Promise = require("bluebird");

module.exports.normalize = function normalize(action) {
	if (_.isFunction(action)) {
		action = { function: action };
		return action;
	}
};

module.exports.process = function process(context) {
	if (_.isObject(this.imports)) { // Works for arrays too
		// TODO - Check for relative path and map to be relative to the script
		for (var key in this.imports) {
			module.exports.logger.trace("Attempting to import - " + this.imports[key] + " to support 'function' action.");
			this.imports[key] = require(this.imports[key]);
		}
	}
	module.exports.logger.debug("About to invoke custom function");
	var func = this["function"];
	if (func.length == 1) { // Synchronous function
		var result = func.call(this, context);
		if(result && result.then) {
			return result;
		}
	}
	else if (func.length == 2) {
		return Promise.promisify(func).call(this, context);
	}
	else {
		throw new verror.VError({
			name: "InvalidCustomFunction",
			info: {
				path: this.path
			}
		}, "Custom function has unexpected number of arguments. Must take 1 or 2 arguments.");
	}
};
<|endoftext|>


<commit_before>/* Formatting function for row details*/
function makeChildRow(clicked) {

    var targetRowId = $(clicked).attr("targetRowId");
    var targetRow = $('#' + targetRowId);
    var geneId = $(clicked).attr("geneid");
    var diseaseId = $(clicked).attr("diseaseid");
    var requestPageType = $(clicked).attr("requestpagetype");

    return $(document.createElement('div'))
        .addClass("inner")
        .attr({
            id: targetRowId,
            geneId: geneId,
            diseaseId: diseaseId,
            requestPageType: requestPageType
        }).css({"padding": "0"});
}

function insertDiseaseAssociations(clicked) {

    var targetRowId = $(clicked).attr("targetRowId");
    var targetRow = $('#' + targetRowId);
    var geneId = $(clicked).attr("geneId");
    var diseaseId = $(clicked).attr("diseaseId");
    var requestPageType = $(clicked).attr("requestPageType");

    var uri = baseUrl + '/phenodigm/diseaseGeneAssociations';

    $.get(uri, {
        geneId: geneId,
        diseaseId: diseaseId,
        requestPageType: requestPageType
    }, function (response) {
        //add the response html to the target row
        $(targetRow).remove('#loadingPlaceholder').html(response);
    });
};

function insertPhenogrid(clicked) {

    var targetRowId = $(clicked).attr("targetRowId");
    var targetRow = $('#' + targetRowId);
    var geneId = $(clicked).attr("geneId");
    var diseaseId = $(clicked).attr("diseaseId");
    var requestPageType = $(clicked).attr("requestPageType");

    var gridColumnWidth = 25;
    var gridRowHeight= 20;

    getPhenoGridSkeleton(geneId, diseaseId, requestPageType).done(function (result) {
        Phenogrid.createPhenogridForElement(targetRow, {
            //monarchUrl is a global variable provided via Spring from application.properties
            serverURL: monarchUrl,
            selectedSort: "Frequency and Rarity", // sort method of sources: "Alphabetic", "Frequency and Rarity", "Frequency,
            gridSkeletonDataVendor: 'IMPC',
            gridSkeletonData: result,
            gridSkeletonTargetGroup: {name: "Mus musculus", taxon: "10090"},
            singleTargetModeTargetLengthLimit: gridColumnWidth,
            sourceLengthLimit: gridRowHeight
        });
    });
    $(targetRow).remove('#loadingPlaceholder');
};

function getPhenoGridSkeleton(geneId, diseaseId, requestPageType) {
    return $.getJSON(baseUrl + '/phenodigm/phenogrid',
        {geneId: geneId, diseaseId: diseaseId, requestPageType: requestPageType}
    );
}

/* Adds on-click functionality to the table to insert and show/hide a child row
 for the row clicked.*/
$.fn.addTableClickCallbackHandler = function (tableId, table) {
    console.log(tableId);
    $(tableId + ' tbody').on('click', 'tr', function () {
        var tr = $(this).closest('tr');
        var row = table.row(tr);

        if (row.child.isShown()) {
            // This row is already open - close it
            row.child.hide();
            tr.removeClass('shown');
            tr.find("td#toggleButton i").removeClass("fa-minus-square").addClass("fa-plus-square");
        }
        else {
            // Open this row
            row.child(makeChildRow(tr)).show();
            //row.child(insertDiseaseAssociations(tr)).show();
            row.child(insertPhenogrid(tr)).show();
            tr.addClass('shown');
            tr.find("td#toggleButton i").removeClass("fa-plus-square").addClass("fa-minus-square");
        }
    });
};<commit_msg>Switched back to old disease phenotype comparisons.<commit_after>/* Formatting function for row details*/
function makeChildRow(clicked) {

    var targetRowId = $(clicked).attr("targetRowId");
    var targetRow = $('#' + targetRowId);
    var geneId = $(clicked).attr("geneid");
    var diseaseId = $(clicked).attr("diseaseid");
    var requestPageType = $(clicked).attr("requestpagetype");

    return $(document.createElement('div'))
        .addClass("inner")
        .attr({
            id: targetRowId,
            geneId: geneId,
            diseaseId: diseaseId,
            requestPageType: requestPageType
        }).css({"padding": "0"});
}

function insertDiseaseAssociations(clicked) {

    var targetRowId = $(clicked).attr("targetRowId");
    var targetRow = $('#' + targetRowId);
    var geneId = $(clicked).attr("geneId");
    var diseaseId = $(clicked).attr("diseaseId");
    var requestPageType = $(clicked).attr("requestPageType");

    var uri = baseUrl + '/phenodigm/diseaseGeneAssociations';

    $.get(uri, {
        geneId: geneId,
        diseaseId: diseaseId,
        requestPageType: requestPageType
    }, function (response) {
        //add the response html to the target row
        $(targetRow).remove('#loadingPlaceholder').html(response);
    });
};

function insertPhenogrid(clicked) {

    var targetRowId = $(clicked).attr("targetRowId");
    var targetRow = $('#' + targetRowId);
    var geneId = $(clicked).attr("geneId");
    var diseaseId = $(clicked).attr("diseaseId");
    var requestPageType = $(clicked).attr("requestPageType");

    var gridColumnWidth = 25;
    var gridRowHeight= 20;

    getPhenoGridSkeleton(geneId, diseaseId, requestPageType).done(function (result) {
        Phenogrid.createPhenogridForElement(targetRow, {
            //monarchUrl is a global variable provided via Spring from application.properties
            serverURL: monarchUrl,
            selectedSort: "Frequency and Rarity", // sort method of sources: "Alphabetic", "Frequency and Rarity", "Frequency,
            gridSkeletonDataVendor: 'IMPC',
            gridSkeletonData: result,
            gridSkeletonTargetGroup: {name: "Mus musculus", taxon: "10090"},
            singleTargetModeTargetLengthLimit: gridColumnWidth,
            sourceLengthLimit: gridRowHeight
        });
    });
    $(targetRow).remove('#loadingPlaceholder');
};

function getPhenoGridSkeleton(geneId, diseaseId, requestPageType) {
    return $.getJSON(baseUrl + '/phenodigm/phenogrid',
        {geneId: geneId, diseaseId: diseaseId, requestPageType: requestPageType}
    );
}

/* Adds on-click functionality to the table to insert and show/hide a child row
 for the row clicked.*/
$.fn.addTableClickCallbackHandler = function (tableId, table) {
    console.log(tableId);
    $(tableId + ' tbody').on('click', 'tr', function () {
        var tr = $(this).closest('tr');
        var row = table.row(tr);

        if (row.child.isShown()) {
            // This row is already open - close it
            row.child.hide();
            tr.removeClass('shown');
            tr.find("td#toggleButton i").removeClass("fa-minus-square").addClass("fa-plus-square");
        }
        else {
            // Open this row
            row.child(makeChildRow(tr)).show();
            row.child(insertDiseaseAssociations(tr)).show();
            //row.child(insertPhenogrid(tr)).show();
            tr.addClass('shown');
            tr.find("td#toggleButton i").removeClass("fa-plus-square").addClass("fa-minus-square");
        }
    });
};<|endoftext|>


<commit_before>/*!
 * activity-streams
 *   version 0.2.0
 *   http://github.com/silverbucket/activity-streams
 *
 * Developed and Maintained by:
 *   Nick Jennings <nick@silverbucket.net> copyright 2014
 *
 * activity-streams is released under the MIT (see LICENSE).
 *
 * You don't have to do anything special to choose one license or the other
 * and you don't have to notify anyone which license you are using.
 * Please see the corresponding license file for details of these licenses.
 * You are free to use, modify and distribute this software, but all copyright
 * information must remain.
 *
 */

var EventEmitter = require('wolfy87-eventemitter');
var ArrayKeys    = require('array-keys');

var streams = new ArrayKeys({ identifier: 'id' }),
    objs    = new ArrayKeys({ identifier: 'id' });
    ee      = new EventEmitter();

var Stream = {
  create: function (obj) {
    var result = false;
    try {
      result = streams.addRecord(obj);
    } catch (e) {
      throw new Error(e);
    }

    if (result) {
      ee.emitEvent('activity-stream-create', obj);
    }
    return result;
  },

  delete: function (id) {
    var result = streams.removeRecord(id);
    if (result) {
      ee.emitEvent('activity-stream-delete', id);
    }
    return result;
  },

  get: function (id) {
    return streams.getRecord(id);
  }
};

var _Object = {
  create: function (obj) {
    var result = false;
    try {
      result = objs.addRecord(obj);
    } catch (e) {
      throw new Error(e);
    }

    if (result) {
      ee.emitEvent('activity-object-create', obj);
    }
    return result;
  },

  delete: function (id) {
    var result = objs.removeRecord(id);
    if (result) {
      ee.emitEvent('activity-object-delete', id);
    }
    return result;
  },

  get: function (obj) {
    return objs.getRecord(obj);
  }
};

module.exports = {
  Stream: Stream,
  Object: _Object,
  on: ee.on,
  once: ee.once,
  off: ee.off
};
<commit_msg>switched event emitter, fixing bug with scope<commit_after>/*!
 * activity-streams
 *   version 0.2.1
 *   http://github.com/silverbucket/activity-streams
 *
 * Developed and Maintained by:
 *   Nick Jennings <nick@silverbucket.net> copyright 2014
 *
 * activity-streams is released under the MIT (see LICENSE).
 *
 * You don't have to do anything special to choose one license or the other
 * and you don't have to notify anyone which license you are using.
 * Please see the corresponding license file for details of these licenses.
 * You are free to use, modify and distribute this software, but all copyright
 * information must remain.
 *
 */

var EventEmitter = require('event-emitter');
var ArrayKeys    = require('array-keys');

var streams = new ArrayKeys({ identifier: 'id' }),
    objs    = new ArrayKeys({ identifier: 'id' });
    ee      = EventEmitter();

var Stream = {
  create: function (obj) {
    var result = false;
    try {
      result = streams.addRecord(obj);
    } catch (e) {
      throw new Error(e);
    }

    if (result) {
      ee.emit('activity-stream-create', obj);
    }
    return result;
  },

  delete: function (id) {
    var result = streams.removeRecord(id);
    if (result) {
      ee.emit('activity-stream-delete', id);
    }
    return result;
  },

  get: function (id) {
    return streams.getRecord(id);
  }
};

var _Object = {
  create: function (obj) {
    var result = false;
    try {
      result = objs.addRecord(obj);
    } catch (e) {
      throw new Error(e);
    }

    if (result) {
      ee.emit('activity-object-create', obj);
    }
    return result;
  },

  delete: function (id) {
    var result = objs.removeRecord(id);
    if (result) {
      console.log('emitting event');
      ee.emit('activity-object-delete', id);
    }
    return result;
  },

  get: function (obj) {
    return objs.getRecord(obj);
  }
};

module.exports = {
  Stream: Stream,
  Object: _Object,
  on: function (event, func) {
    return ee.on(event, func);
  },
  once: function (event, func) {
    return ee.once(event, func);
  },
  off: function (event, funcName) {
    return ee.off(event, funcName);
  }
};
<|endoftext|>


<commit_before>/**
 * Draft build config (the fast build).
 *
 * This build is intended to build as fast as possible, while being semantically
 * the same as the production build.
 *
 * The output is not optimised for artifact size, human-readability or to work
 * well with development tools or debuggers. Use the prod or dev config if you
 * want those properties in the output.
 */

var webpack = require('webpack')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var _ = require('lodash')
var defaultConfig = require('./webpack.prod.config.js')

module.exports = _.merge({}, defaultConfig, {
  devtool: 'eval',

  module: {
    // FRAGILE: index must line up to override values correctly
    loaders: [
      {},
      {
        // prevent css optimisation and minification
        loader: ExtractTextPlugin.extract('style', 'css?-minimize!postcss!rework')
      }
    ]
  }
})

// Prevent eslint running (it is the only defined preLoader)
module.exports.module.preLoaders = []

// do not minify/uglify the output
module.exports.plugins = _.filter(module.exports.plugins,
  (plugin) => plugin.constructor.name !== 'UglifyJsPlugin')
<commit_msg>style(eslint): enable eslint check in PR build for zanata-frontend<commit_after>/**
 * Draft build config (the fast build).
 *
 * This build is intended to build as fast as possible, while being semantically
 * the same as the production build.
 *
 * The output is not optimised for artifact size, human-readability or to work
 * well with development tools or debuggers. Use the prod or dev config if you
 * want those properties in the output.
 */

var webpack = require('webpack')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var _ = require('lodash')
var defaultConfig = require('./webpack.prod.config.js')

module.exports = _.merge({}, defaultConfig, {
  devtool: 'eval',

  module: {
    // FRAGILE: index must line up to override values correctly
    loaders: [
      {},
      {
        // prevent css optimisation and minification
        loader: ExtractTextPlugin.extract('style', 'css?-minimize!postcss!rework')
      }
    ]
  }
})

// do not minify/uglify the output
module.exports.plugins = _.filter(module.exports.plugins,
  (plugin) => plugin.constructor.name !== 'UglifyJsPlugin')
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc directive
 * @name gridster:ngGridster
 *
 * @description
 * The `ngGridster` directive instantiates a template once per item from a collection and then instantiates Gridster.js
 * on the instantiated templates. Each template instance gets its own scope, where the given loop variable is set to
 * the current collection item, and `$index` is set to the item index.
 *
 * Special properties are exposed on the local scope of each template instance, including:
 *
 * | Variable  | Type            | Details                                                                     |
 * |-----------|-----------------|-----------------------------------------------------------------------------|
 * | `$index`  | {@type number}  | iterator offset of the repeated element (0..length-1)                       |
 * | `$first`  | {@type boolean} | true if the repeated element is first in the iterator.                      |
 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
 * | `$last`   | {@type boolean} | true if the repeated element is last in the iterator.                       |
 * | `$even`   | {@type boolean} | true if the iterator position `$index` is even (otherwise false).           |
 * | `$odd`    | {@type boolean} | true if the iterator position `$index` is odd (otherwise false).            |
 *
 * Although this works similarly to ngRepeat, this directive does not watch for changes in the collection after
 * it has been added to the DOM. It is expected that all DOM manipulation after the first draw happens
 * via the Gridster.js API.
 *
 * @element ANY
 * @scope
 * @priority 1000
 * @param {repeat_expression} ngGridster The expression indicating how to enumerate a collection.
 *
 *   * `variable in expression` – where variable is the user defined loop variable and `expression`
 *     is a scope expression giving the collection to enumerate.
 *
 *     For example: `album in artist.albums`.
 *
 */

angular.module('gridster', [])
  .directive('ngGridster', ['$parse', '$animate', '$timeout', function($parse, $animate, $timeout) {
    return {
      restrict: 'A',
      transclude: 'element',
      terminal: true,
      controller: function($scope) {
        var gridster = null;

        this.gridster = function(g) {
          if (g) {
            gridster = g;
          } else {
            return gridster;
          }
        }
      },
      link: function(scope, element, attr, controller, transclude) {
        var expression = attr.ngGridster, // Get the repeat expression
            valueExp, collectionExp;

        // Make sure we have a 'item in something' expression
        var match = expression.match(/^\s*(.+)\s+in\s+(.*?)$/);

        if (!match) {
          throw new Error("Expected expression in form of '_item_ in _collection_' but got '" + expression + "'.");
        }

        valueExp = match[1];
        collectionExp = match[2];

        // Make sure we have a 'valid identifer in something' expression (ie: not '! in collection')
        match = valueExp.match(/^(?:([\$\w]+))$/);

        if (!match) {
          throw new Error("'_item_' in '_item_ in _collection_' should be an indentifier but got '" + valueExp + "'.");
        }

        var watcher = scope.$watchCollection(collectionExp, function(collection) {
          if (!collection) { return; }

          // Call the watcher function to stop watching the collection
          // After we finish with this function, gridster is going to handle the rest of the DOM manipulation
          watcher();

          var index = 0,
              length = collection.length,
              previousNode = element,
              childScope, value;

          // Double check that we have an array
          if (!Array.isArray(collection)) {
            throw new Error("'_collection_' in '_item_ in _collection_' should be an array but got '" + collection + "'.");
          }

          for (; index < length; index++) {
            value = collection[index];
            childScope = scope.$new();

            // Populate child scope with ngRepeat-style variables
            childScope[valueExp] = value;
            childScope.$index = index;
            childScope.$first = (index === 0);
            childScope.$last = (index === (length - 1));
            childScope.$middle = !(childScope.$first || childScope.$last);
            childScope.$odd = !(childScope.$even = (index&1) === 0);

            transclude(childScope, function(clone) {
              // Add a comment to the cloned node to denote the end of a repeated DOM fragment
              clone[clone.length++] = document.createComment(' end ngGridster: ' + expression + ' ');

              // Add the cloned node into the DOM via $animate
              $animate.enter(clone, null, previousNode);

              // Move the previousNode to the one we just added
              previousNode = clone;
            });
          }

          // Need to wait until after the current $digest to instantiate gridster
          $timeout(function() {
            var gridster = element.parent().gridster({
              widget_base_dimensions: [110, 110],
              widget_margins: [10, 10],
              min_cols: 10,
              max_cols: 10
            }).data('gridster');

            // Set the gridster object on the controller
            controller.gridster(gridster);
          });
        });
      }
    };
  }]);
<commit_msg>Adding grid id<commit_after>'use strict';

/**
 * @ngdoc directive
 * @name gridster:ngGridster
 *
 * @description
 * The `ngGridster` directive instantiates a template once per item from a collection and then instantiates Gridster.js
 * on the instantiated templates. Each template instance gets its own scope, where the given loop variable is set to
 * the current collection item, and `$index` is set to the item index.
 *
 * Special properties are exposed on the local scope of each template instance, including:
 *
 * | Variable  | Type            | Details                                                                     |
 * |-----------|-----------------|-----------------------------------------------------------------------------|
 * | `$index`  | {@type number}  | iterator offset of the repeated element (0..length-1)                       |
 * | `$first`  | {@type boolean} | true if the repeated element is first in the iterator.                      |
 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. |
 * | `$last`   | {@type boolean} | true if the repeated element is last in the iterator.                       |
 * | `$even`   | {@type boolean} | true if the iterator position `$index` is even (otherwise false).           |
 * | `$odd`    | {@type boolean} | true if the iterator position `$index` is odd (otherwise false).            |
 *
 * Although this works similarly to ngRepeat, this directive does not watch for changes in the collection after
 * it has been added to the DOM. It is expected that all DOM manipulation after the first draw happens
 * via the Gridster.js API.
 *
 * @element ANY
 * @scope
 * @priority 1000
 * @param {repeat_expression} ngGridster The expression indicating how to enumerate a collection.
 *
 *   * `variable in expression` – where variable is the user defined loop variable and `expression`
 *     is a scope expression giving the collection to enumerate.
 *
 *     For example: `album in artist.albums`.
 *
 */

angular.module('gridster', [])
  .directive('ngGridster', ['$parse', '$animate', '$timeout', function($parse, $animate, $timeout) {
    return {
      restrict: 'A',
      transclude: 'element',
      terminal: true,
      controller: function($scope) {
        var gridster = null;

        this.gridster = function(g) {
          if (g) {
            gridster = g;
          } else {
            return gridster;
          }
        }
      },
      link: function(scope, element, attr, controller, transclude) {
        var expression = attr.ngGridster, // Get the repeat expression
            gridId = attr.gridId,
            valueExp, collectionExp;

        // Make sure we have a 'item in something' expression
        var match = expression.match(/^\s*(.+)\s+in\s+(.*?)$/);

        if (!match) {
          throw new Error("Expected expression in form of '_item_ in _collection_' but got '" + expression + "'.");
        }

        valueExp = match[1];
        collectionExp = match[2];

        // Make sure we have a 'valid identifer in something' expression (ie: not '! in collection')
        match = valueExp.match(/^(?:([\$\w]+))$/);

        if (!match) {
          throw new Error("'_item_' in '_item_ in _collection_' should be an indentifier but got '" + valueExp + "'.");
        }

        var watcher = scope.$watchCollection(collectionExp, function(collection) {
          if (!collection) { return; }

          // Call the watcher function to stop watching the collection
          // After we finish with this function, gridster is going to handle the rest of the DOM manipulation
          watcher();

          var index = 0,
              length = collection.length,
              previousNode = element,
              childScope, value;

          // Double check that we have an array
          if (!Array.isArray(collection)) {
            throw new Error("'_collection_' in '_item_ in _collection_' should be an array but got '" + collection + "'.");
          }

          for (; index < length; index++) {
            value = collection[index];
            childScope = scope.$new();

            // Populate child scope with ngRepeat-style variables
            childScope[valueExp] = value;
            childScope.$index = index;
            childScope.$first = (index === 0);
            childScope.$last = (index === (length - 1));
            childScope.$middle = !(childScope.$first || childScope.$last);
            childScope.$odd = !(childScope.$even = (index&1) === 0);

            transclude(childScope, function(clone) {
              // Add a comment to the cloned node to denote the end of a repeated DOM fragment
              clone[clone.length++] = document.createComment(' end ngGridster: ' + expression + ' ');

              // Add the cloned node into the DOM via $animate
              $animate.enter(clone, null, previousNode);

              // Move the previousNode to the one we just added
              previousNode = clone;
            });
          }

          // Need to wait until after the current $digest to instantiate gridster
          $timeout(function() {
            var gridster = element.parent().gridster({
              widget_base_dimensions: [110, 110],
              widget_margins: [10, 10],
              min_cols: 10,
              max_cols: 10
            }).data('gridster');

            // Set the gridster object on the controller
            controller.gridster(gridster);
          });
        });
      }
    };
  }]);
<|endoftext|>


<commit_before>var _ = require('underscore'),
    $ = require('../cheerio'),
    updateDOM = $.parse.update,
    slice = Array.prototype.slice;

var removeChild = function(parent, elem) {
  $.each(parent.children, function(i, child) {
    if (elem === child)
      parent.children.splice(i, 1);
  });
};

/*
  Creates an array of cheerio objects,
  parsing strings if necessary
*/
var makeCheerioArray = function(elems) {
  var dom = [],
      len = elems.length,
      elem;

  for (var i = 0; i < len; i++) {
    elem = elems[i];
    // If a cheerio object
    if (elem.cheerio) {
      dom = dom.concat(elem.toArray());
    } else {
      dom = dom.concat($.parse.eval(elem));
    }
  }

  return dom;
};

var append = exports.append = function() {
  var elems = slice.call(arguments),
      dom = makeCheerioArray(elems);

  this.each(function() {
    if (_.isFunction(elems[0])) {
      // No yet supported
      return this;
    } else {
      if (!this.children) this.children = [];
      this.children = this.children.concat(dom);
      updateDOM(this.children, this);
    }
  });

  return this;
};

/*
  TODO: Refactor, only one line difference between,
  this function and append
*/
var prepend = exports.prepend = function() {
  var elems = slice.call(arguments),
      dom = makeCheerioArray(elems);

  this.each(function() {
    if (_.isFunction(elems[0])) {
      // No yet supported
      return this;
    } else {
      if (!this.children) this.children = [];
      this.children = dom.concat(this.children);
      updateDOM(this.children, this);
    }
  });

  return this;
};

var after = exports.after = function() {
  var elems = slice.call(arguments),
      dom = makeCheerioArray(elems);

  this.each(function() {
    var siblings = this.parent.children,
        index = siblings.indexOf(this);

    // If not found, move on
    if (!~index) return;

    // Add element after `this` element
    siblings.splice.apply(siblings, [++index, 0].concat(dom));

    // Update next, prev, and parent pointers
    updateDOM(siblings, this.parent);
    this.parent.children = siblings;

  });

  return this;
};

var before = exports.before = function() {
  var elems = slice.call(arguments),
      dom = makeCheerioArray(elems);

  this.each(function() {
    var siblings = this.parent.children,
        index = siblings.indexOf(this);

    // If not found, move on
    if (!~index) return;

    // Add element before `this` element
    siblings.splice.apply(siblings, [index, 0].concat(dom));

    // Update next, prev, and parent pointers
    updateDOM(siblings, this.parent);
    this.parent.children = siblings;

  });

  return this;
};

/*
  remove([selector])
*/
var remove = exports.remove = function(selector) {
  var elems = this;

  // Filter if we have selector
  if (selector)
    elems = elems.find(selector);

  elems.each(function() {
    var siblings = this.parent.children,
        index = siblings.indexOf(this);

    if (!~index) return;

    siblings.splice(index, 1);

    // Update next, prev, and parent pointers
    updateDOM(siblings, this.parent);
    this.parent.children = siblings;
  });

  return this;
};

var replaceWith = exports.replaceWith = function(content) {
  content = content.cheerio ? content.toArray() : $.parse.eval(content);

  this.each(function() {
    var siblings = this.parent.children,
        index = siblings.indexOf(this);

    if (!~index) return;

    siblings.splice.apply(siblings, [index, 1].concat(content));

    updateDOM(siblings, this.parent);
    this.parent.children = siblings;
  });

  return this;
};

var empty = exports.empty = function() {
  this.each(function() {
    this.children = [];
  });
  return this;
};

var html = exports.html = function(content) {
  if (content === undefined) {
    if (!this[0] || !this[0].children) return null;
    return $.html(this[0].children);
  }

  content = content.cheerio ? content.toArray() : $.parse.eval(content);

  this.each(function() {
    this.children = content;
    updateDOM(this.children, this);
  });

  return this;
};

var tidy = exports.tidy = function() {
  return $.tidy(this[0].children);
};

var text = exports.text = function(str) {
  // If `str` blank or an object
  if (!str || typeof str === 'object') {
    return $.text(this);
  } else if (_.isFunction(str)) {
    // Function support
    return this.each(function(i) {
      var self = $(this);
      return self.text(str.call(this, i, self.text()));
    });
  }

  var elem = {
    data : $.encode(str),
    type : 'text',
    parent : null,
    prev : null,
    next : null,
    children : []
  };

  // Append text node to each selected elements
  this.each(function() {
    this.children = elem;
    updateDOM(this.children, this);
  });

  return this;
};

var clone = exports.clone = function() {
  return $($.html(this));
};

module.exports = $.fn.extend(exports);
<commit_msg>manipulation: refactor `makeCheerioArray`<commit_after>var _ = require('underscore'),
    $ = require('../cheerio'),
    updateDOM = $.parse.update,
    slice = Array.prototype.slice;

var removeChild = function(parent, elem) {
  $.each(parent.children, function(i, child) {
    if (elem === child)
      parent.children.splice(i, 1);
  });
};

/*
  Creates an array of cheerio objects,
  parsing strings if necessary
*/
var makeCheerioArray = function(elems) {
  return _.reduce(elems, function(dom, elem) {
    return dom.concat(elem.cheerio ? elem.toArray() : $.parse.eval(elem));
  }, []);
};

var append = exports.append = function() {
  var elems = slice.call(arguments),
      dom = makeCheerioArray(elems);

  this.each(function() {
    if (_.isFunction(elems[0])) {
      // No yet supported
      return this;
    } else {
      if (!this.children) this.children = [];
      this.children = this.children.concat(dom);
      updateDOM(this.children, this);
    }
  });

  return this;
};

/*
  TODO: Refactor, only one line difference between,
  this function and append
*/
var prepend = exports.prepend = function() {
  var elems = slice.call(arguments),
      dom = makeCheerioArray(elems);

  this.each(function() {
    if (_.isFunction(elems[0])) {
      // No yet supported
      return this;
    } else {
      if (!this.children) this.children = [];
      this.children = dom.concat(this.children);
      updateDOM(this.children, this);
    }
  });

  return this;
};

var after = exports.after = function() {
  var elems = slice.call(arguments),
      dom = makeCheerioArray(elems);

  this.each(function() {
    var siblings = this.parent.children,
        index = siblings.indexOf(this);

    // If not found, move on
    if (!~index) return;

    // Add element after `this` element
    siblings.splice.apply(siblings, [++index, 0].concat(dom));

    // Update next, prev, and parent pointers
    updateDOM(siblings, this.parent);
    this.parent.children = siblings;

  });

  return this;
};

var before = exports.before = function() {
  var elems = slice.call(arguments),
      dom = makeCheerioArray(elems);

  this.each(function() {
    var siblings = this.parent.children,
        index = siblings.indexOf(this);

    // If not found, move on
    if (!~index) return;

    // Add element before `this` element
    siblings.splice.apply(siblings, [index, 0].concat(dom));

    // Update next, prev, and parent pointers
    updateDOM(siblings, this.parent);
    this.parent.children = siblings;

  });

  return this;
};

/*
  remove([selector])
*/
var remove = exports.remove = function(selector) {
  var elems = this;

  // Filter if we have selector
  if (selector)
    elems = elems.find(selector);

  elems.each(function() {
    var siblings = this.parent.children,
        index = siblings.indexOf(this);

    if (!~index) return;

    siblings.splice(index, 1);

    // Update next, prev, and parent pointers
    updateDOM(siblings, this.parent);
    this.parent.children = siblings;
  });

  return this;
};

var replaceWith = exports.replaceWith = function(content) {
  content = content.cheerio ? content.toArray() : $.parse.eval(content);

  this.each(function() {
    var siblings = this.parent.children,
        index = siblings.indexOf(this);

    if (!~index) return;

    siblings.splice.apply(siblings, [index, 1].concat(content));

    updateDOM(siblings, this.parent);
    this.parent.children = siblings;
  });

  return this;
};

var empty = exports.empty = function() {
  this.each(function() {
    this.children = [];
  });
  return this;
};

var html = exports.html = function(content) {
  if (content === undefined) {
    if (!this[0] || !this[0].children) return null;
    return $.html(this[0].children);
  }

  content = content.cheerio ? content.toArray() : $.parse.eval(content);

  this.each(function() {
    this.children = content;
    updateDOM(this.children, this);
  });

  return this;
};

var tidy = exports.tidy = function() {
  return $.tidy(this[0].children);
};

var text = exports.text = function(str) {
  // If `str` blank or an object
  if (!str || typeof str === 'object') {
    return $.text(this);
  } else if (_.isFunction(str)) {
    // Function support
    return this.each(function(i) {
      var self = $(this);
      return self.text(str.call(this, i, self.text()));
    });
  }

  var elem = {
    data : $.encode(str),
    type : 'text',
    parent : null,
    prev : null,
    next : null,
    children : []
  };

  // Append text node to each selected elements
  this.each(function() {
    this.children = elem;
    updateDOM(this.children, this);
  });

  return this;
};

var clone = exports.clone = function() {
  return $($.html(this));
};

module.exports = $.fn.extend(exports);
<|endoftext|>


<commit_before>/**
 * @fileoverview Fake application to use during testing
 * @author Box
 */

(function() {

	'use strict';

	/*
	 * When testing actual Application, it should be included after to overwrite this stub.
	 */
	Box.Application = (function() {

		var services = {},
			modules = {},
			behaviors = {};

		return {

			/**
			 * Resets the application stub back to a clean state. Will also remove pre-registered components.
			 * @returns {void}
			 */
			reset: function() {
				services = {};
				modules = {};
				behaviors = {};
			},

			/**
			 * Registers a service to the application stub
			 * @param {string} serviceName The name of the service
			 * @param {Function} creator The service creator function
			 * @returns {void}
			 */
			addService: function(serviceName, creator) {
				services[serviceName] = {
					creator: creator
				};
			},

			/**
			 * Registers a module to the application stub
			 * @param {string} moduleName The name of the module
			 * @param {Function} creator The behavior creator function
			 * @returns {void}
			 */
			addModule: function(moduleName, creator) {
				modules[moduleName] = {
					creator: creator
				};
			},

			/**
			 * Registers a behavior to the application stub
			 * @param {string} behaviorName The name of the behavior
			 * @param {Function} creator The behavior creator function
			 * @returns {void}
			 */
			addBehavior: function(behaviorName, creator) {
				behaviors[behaviorName] = {
					creator: creator
				};
			},

			/**
			 * Will create a new instance of a service with the given application context
			 * @param {string} serviceName The name of the service being created
			 * @param {Object} application The application context object (usually a TestServiceProvider)
			 * @returns {?Object} The service object
			 */
			getServiceForTest: function(serviceName, application) {
				var serviceData = services[serviceName];
				if (serviceData) {
					return services[serviceName].creator(application);
				}
				return null;
			},

			/**
			 * Will create a new instance of a module with a given context
			 * @param {string} moduleName The name of the module being created
			 * @param {Object} context The context object (usually a TestServiceProvider)
			 * @returns {?Object} The module object
			 */
			getModuleForTest: function(moduleName, context) {
				var module = modules[moduleName].creator(context);

				if (!context.getElement) {
					// Add in a default getElement function that matches the first module element
					// Developer should stub this out if there are more than one instance of this module
					context.getElement = function() {
						return document.querySelector('[data-module="' + moduleName + '"]');
					};
				}
				return module;
			},

			/**
			 * Will create a new instance of a behavior with a given context
			 * @param {string} behaviorName The name of the behavior being created
			 * @param {Object} context The context object (usually a TestServiceProvider)
			 * @returns {?Object} The behavior object
			 */
			getBehaviorForTest: function(behaviorName, context) {
				var behaviorData = behaviors[behaviorName];
				if (behaviorData) {
					// getElement on behaviors must be stubbed
					if (!context.getElement) {
						context.getElement = function() {
							throw new Error('You must stub `getElement` for behaviors.');
						}
					}
					return behaviors[behaviorName].creator(context);
				}
				return null;
			}

		};

	}());

}());
<commit_msg>Missing semicolon in application-stub.js Fixes #4<commit_after>/**
 * @fileoverview Fake application to use during testing
 * @author Box
 */

(function() {

	'use strict';

	/*
	 * When testing actual Application, it should be included after to overwrite this stub.
	 */
	Box.Application = (function() {

		var services = {},
			modules = {},
			behaviors = {};

		return {

			/**
			 * Resets the application stub back to a clean state. Will also remove pre-registered components.
			 * @returns {void}
			 */
			reset: function() {
				services = {};
				modules = {};
				behaviors = {};
			},

			/**
			 * Registers a service to the application stub
			 * @param {string} serviceName The name of the service
			 * @param {Function} creator The service creator function
			 * @returns {void}
			 */
			addService: function(serviceName, creator) {
				services[serviceName] = {
					creator: creator
				};
			},

			/**
			 * Registers a module to the application stub
			 * @param {string} moduleName The name of the module
			 * @param {Function} creator The behavior creator function
			 * @returns {void}
			 */
			addModule: function(moduleName, creator) {
				modules[moduleName] = {
					creator: creator
				};
			},

			/**
			 * Registers a behavior to the application stub
			 * @param {string} behaviorName The name of the behavior
			 * @param {Function} creator The behavior creator function
			 * @returns {void}
			 */
			addBehavior: function(behaviorName, creator) {
				behaviors[behaviorName] = {
					creator: creator
				};
			},

			/**
			 * Will create a new instance of a service with the given application context
			 * @param {string} serviceName The name of the service being created
			 * @param {Object} application The application context object (usually a TestServiceProvider)
			 * @returns {?Object} The service object
			 */
			getServiceForTest: function(serviceName, application) {
				var serviceData = services[serviceName];
				if (serviceData) {
					return services[serviceName].creator(application);
				}
				return null;
			},

			/**
			 * Will create a new instance of a module with a given context
			 * @param {string} moduleName The name of the module being created
			 * @param {Object} context The context object (usually a TestServiceProvider)
			 * @returns {?Object} The module object
			 */
			getModuleForTest: function(moduleName, context) {
				var module = modules[moduleName].creator(context);

				if (!context.getElement) {
					// Add in a default getElement function that matches the first module element
					// Developer should stub this out if there are more than one instance of this module
					context.getElement = function() {
						return document.querySelector('[data-module="' + moduleName + '"]');
					};
				}
				return module;
			},

			/**
			 * Will create a new instance of a behavior with a given context
			 * @param {string} behaviorName The name of the behavior being created
			 * @param {Object} context The context object (usually a TestServiceProvider)
			 * @returns {?Object} The behavior object
			 */
			getBehaviorForTest: function(behaviorName, context) {
				var behaviorData = behaviors[behaviorName];
				if (behaviorData) {
					// getElement on behaviors must be stubbed
					if (!context.getElement) {
						context.getElement = function() {
							throw new Error('You must stub `getElement` for behaviors.');
						};
					}
					return behaviors[behaviorName].creator(context);
				}
				return null;
			}

		};

	}());

}());
<|endoftext|>