<commit_before>/**
 * Copyright 2013 IBM Corp.
 *
 * 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 RED = require("../../red/red");
var util = require("util");

function HTTPIn(n) {
	RED.nodes.createNode(this,n);
	this.url = n.url;
	this.method = n.method;

	var node = this;
	this.callback = function(req,res) {
		node.send({req:req,res:res});
	}
	if (this.method == "get") {
		RED.app.get(this.url,this.callback);
	} else if (this.method == "post") {
		RED.app.post(this.url,this.callback);
	} else if (this.method == "put") {
		RED.app.put(this.url,this.callback);
	} else if (this.method == "delete") {
		RED.app.delete(this.url,this.callback);
	}
}

HTTPIn.prototype.close = function() {
	var routes = redUI.app.routes[this.method];
	for (var i in routes) {
		if (routes[i].path == this.url) {
			routes.splice(i,1);
			break;
		}
	}
}

RED.nodes.registerType("http in",HTTPIn);
<commit_msg>HTTP node not closeing properly<commit_after>/**
 * Copyright 2013 IBM Corp.
 *
 * 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 RED = require("../../red/red");
var util = require("util");

function HTTPIn(n) {
	RED.nodes.createNode(this,n);
	this.url = n.url;
	this.method = n.method;

	var node = this;
	this.callback = function(req,res) {
		node.send({req:req,res:res});
	}
	if (this.method == "get") {
		RED.app.get(this.url,this.callback);
	} else if (this.method == "post") {
		RED.app.post(this.url,this.callback);
	} else if (this.method == "put") {
		RED.app.put(this.url,this.callback);
	} else if (this.method == "delete") {
		RED.app.delete(this.url,this.callback);
	}
}

RED.nodes.registerType("http in",HTTPIn);

HTTPIn.prototype.close = function() {
    console.log(RED.app.routes[this.method]);
	var routes = RED.app.routes[this.method];
	for (var i in routes) {
		if (routes[i].path == this.url) {
			routes.splice(i,1);
			break;
		}
	}
    console.log(RED.app.routes[this.method]);
}

<|endoftext|>


<commit_before>/**
 * @file
 *
 * Rewrites XMLHttpRequest to automatically send CSRF token with it. In theory
 * plays nice with other JavaScript libraries, needs testing though.
 */

// Here are the basic overloaded method definitions
// The wrapper must be set BEFORE onreadystatechange is written to, since
// a bug in ActiveXObject prevents us from properly testing for it.
CsrfMagic = function(real) {
    // try to make it ourselves, if you didn't pass it
    if (!real) try { real = new XMLHttpRequest; } catch (e) {;}
    if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) {;}
    if (!real) try { real = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {;}
    if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP.4.0'); } catch (e) {;}
    this.csrf = real;
    // properties
    var csrfMagic = this;
    real.onreadystatechange = function() {
        csrfMagic._updateProps();
        return csrfMagic.onreadystatechange ? csrfMagic.onreadystatechange() : null;
    };
    csrfMagic._updateProps();
}

CsrfMagic.prototype = {

    open: function(method, url, async, username, password) {
        if (method == 'POST') this.csrf_isPost = true;
        // deal with Opera bug, thanks jQuery
        if (username) return this.csrf_open(method, url, async, username, password);
        else return this.csrf_open(method, url, async);
    },
    csrf_open: function(method, url, async, username, password) {
        if (username) return this.csrf.open(method, url, async, username, password);
        else return this.csrf.open(method, url, async);
    },

    send: function(data) {
        if (!this.csrf_isPost) this.csrf_send(data);
        prepend = csrfMagicName + '=' + csrfMagicToken + '&';
        if (this.csrf_purportedLength === undefined) {
            this.csrf_setRequestHeader("Content-length", this.csrf_purportedLength + prepend.length);
            delete this.csrf_purportedLength;
        }
        delete this.csrf_isPost;
        return this.csrf_send(prepend + data);
    },
    csrf_send: function(data) {
        return this.csrf.send(data);
    },

    setRequestHeader: function(header, value) {
        // We have to auto-set this at the end, since we don't know how long the
        // nonce is when added to the data.
        if (this.csrf_isPost && header == "Content-length") {
            this.csrf_purportedLength = value;
            return;
        }
        return this.csrf_setRequestHeader(header, value);
    },
    csrf_setRequestHeader: function(header, value) {
        return this.csrf.setRequestHeader(header, value);
    },

    abort: function() {
        return this.csrf.abort();
    },
    getAllResponseHeaders: function() {
        return this.csrf.getAllResponseHeaders();
    },
    getResponseHeader: function(header) {
        return this.csrf.getResponseHeader(header);
    } // ,
}

// proprietary
CsrfMagic.prototype._updateProps = function() {
    this.readyState = this.csrf.readyState;
    if (this.readyState == 4) {
        this.responseText = this.csrf.responseText;
        this.responseXML  = this.csrf.responseXML;
        this.status       = this.csrf.status;
        this.statusText   = this.csrf.statusText;
    }
}
CsrfMagic.process = function(base) {
    var prepend = csrfMagicName + '=' + csrfMagicToken;
    if (base) return prepend + '&' + base;
    return prepend;
}
// callback function for when everything on the page has loaded
CsrfMagic.end = function() {
    // This rewrites forms AGAIN, so in case buffering didn't work this
    // certainly will.
    forms = document.getElementsByTagName('form');
    for (var i = 0; i < forms.length; i++) {
        form = forms[i];
        if (form.method.toUpperCase() !== 'POST') continue;
        if (form.elements[csrfMagicName]) continue;
        var input = document.createElement('input');
        input.setAttribute('name',  csrfMagicName);
        input.setAttribute('value', csrfMagicToken);
        input.setAttribute('type',  'hidden');
        form.appendChild(input);
    }
}

// Sets things up for Mozilla/Opera/nice browsers
if (window.XMLHttpRequest && window.XMLHttpRequest.prototype) {
    var x = XMLHttpRequest.prototype;
    var c = CsrfMagic.prototype;

    // Save the original functions
    x.csrf_open = x.open;
    x.csrf_send = x.send;
    x.csrf_setRequestHeader = x.setRequestHeader;

    // Notice that CsrfMagic is itself an instantiatable object, but only
    // open, send and setRequestHeader are necessary as decorators.
    x.open = c.open;
    x.send = c.send;
    x.setRequestHeader = c.setRequestHeader;
} else {
    // The only way we can do this is by modifying a library you have been
    // using. We support YUI, script.aculo.us, prototype, MooTools,
    // jQuery, Ext and Dojo.
    if (window.jQuery) {
        // jQuery didn't implement a new XMLHttpRequest function, so we have
        // to do this the hard way.
        jQuery.csrf_ajax = jQuery.ajax;
        jQuery.ajax = function( s ) {
            if (s.type && s.type.toUpperCase() == 'POST') {
                s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
                if ( s.data && s.processData && typeof s.data != "string" ) {
                    s.data = jQuery.param(s.data);
                }
                s.data = CsrfMagic.process(s.data);
            }
            return jQuery.csrf_ajax( s );
        }
    } else if (window.Prototype) {
        // This works for script.aculo.us too
        Ajax.csrf_getTransport = Ajax.getTransport;
        Ajax.getTransport = function() {
            return new CsrfMagic(Ajax.csrf_getTransport());
        }
    } else if (window.MooTools) {
        Browser.csrf_Request = Browser.Request;
        Browser.Request = function () {
            return new CsrfMagic(Browser.csrf_Request());
        }
    } else if (window.YAHOO) {
        YAHOO.util.Connect.csrf_createXhrObject = YAHOO.util.Connect.createXhrObject;
        YAHOO.util.Connect.createXhrObject = function (transaction) {
            obj = YAHOO.util.Connect.csrf_createXhrObject(transaction);
            obj.conn = new CsrfMagic(obj.conn);
            return obj;
        }
    } else if (window.Ext) {
        // Ext can use other js libraries as loaders, so it has to come last
        // Ext's implementation is pretty identical to Yahoo's, but we duplicate
        // it for comprehensiveness's sake.
        Ext.lib.Ajax.csrf_createXhrObject = Ext.lib.Ajax.createXhrObject;
        Ext.lib.Ajax.createXhrObject = function (transaction) {
            obj = Ext.lib.Ajax.csrf_createXhrObject(transaction);
            obj.conn = new CsrfMagic(obj.conn);
            return obj;
        }
    } else if (window.dojo) {
        dojo.csrf__xhrObj = dojo._xhrObj;
        dojo._xhrObj = function () {
            return new CsrfMagic(dojo.csrf__xhrObj());
        }
    }
}
<commit_msg>Fix bug in CsrfMagic.send() function, resulting in double submission of GET requests.<commit_after>/**
 * @file
 *
 * Rewrites XMLHttpRequest to automatically send CSRF token with it. In theory
 * plays nice with other JavaScript libraries, needs testing though.
 */

// Here are the basic overloaded method definitions
// The wrapper must be set BEFORE onreadystatechange is written to, since
// a bug in ActiveXObject prevents us from properly testing for it.
CsrfMagic = function(real) {
    // try to make it ourselves, if you didn't pass it
    if (!real) try { real = new XMLHttpRequest; } catch (e) {;}
    if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) {;}
    if (!real) try { real = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {;}
    if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP.4.0'); } catch (e) {;}
    this.csrf = real;
    // properties
    var csrfMagic = this;
    real.onreadystatechange = function() {
        csrfMagic._updateProps();
        return csrfMagic.onreadystatechange ? csrfMagic.onreadystatechange() : null;
    };
    csrfMagic._updateProps();
}

CsrfMagic.prototype = {

    open: function(method, url, async, username, password) {
        if (method == 'POST') this.csrf_isPost = true;
        // deal with Opera bug, thanks jQuery
        if (username) return this.csrf_open(method, url, async, username, password);
        else return this.csrf_open(method, url, async);
    },
    csrf_open: function(method, url, async, username, password) {
        if (username) return this.csrf.open(method, url, async, username, password);
        else return this.csrf.open(method, url, async);
    },

    send: function(data) {
        if (!this.csrf_isPost) return this.csrf_send(data);
        prepend = csrfMagicName + '=' + csrfMagicToken + '&';
        if (this.csrf_purportedLength === undefined) {
            this.csrf_setRequestHeader("Content-length", this.csrf_purportedLength + prepend.length);
            delete this.csrf_purportedLength;
        }
        delete this.csrf_isPost;
        return this.csrf_send(prepend + data);
    },
    csrf_send: function(data) {
        return this.csrf.send(data);
    },

    setRequestHeader: function(header, value) {
        // We have to auto-set this at the end, since we don't know how long the
        // nonce is when added to the data.
        if (this.csrf_isPost && header == "Content-length") {
            this.csrf_purportedLength = value;
            return;
        }
        return this.csrf_setRequestHeader(header, value);
    },
    csrf_setRequestHeader: function(header, value) {
        return this.csrf.setRequestHeader(header, value);
    },

    abort: function() {
        return this.csrf.abort();
    },
    getAllResponseHeaders: function() {
        return this.csrf.getAllResponseHeaders();
    },
    getResponseHeader: function(header) {
        return this.csrf.getResponseHeader(header);
    } // ,
}

// proprietary
CsrfMagic.prototype._updateProps = function() {
    this.readyState = this.csrf.readyState;
    if (this.readyState == 4) {
        this.responseText = this.csrf.responseText;
        this.responseXML  = this.csrf.responseXML;
        this.status       = this.csrf.status;
        this.statusText   = this.csrf.statusText;
    }
}
CsrfMagic.process = function(base) {
    var prepend = csrfMagicName + '=' + csrfMagicToken;
    if (base) return prepend + '&' + base;
    return prepend;
}
// callback function for when everything on the page has loaded
CsrfMagic.end = function() {
    // This rewrites forms AGAIN, so in case buffering didn't work this
    // certainly will.
    forms = document.getElementsByTagName('form');
    for (var i = 0; i < forms.length; i++) {
        form = forms[i];
        if (form.method.toUpperCase() !== 'POST') continue;
        if (form.elements[csrfMagicName]) continue;
        var input = document.createElement('input');
        input.setAttribute('name',  csrfMagicName);
        input.setAttribute('value', csrfMagicToken);
        input.setAttribute('type',  'hidden');
        form.appendChild(input);
    }
}

// Sets things up for Mozilla/Opera/nice browsers
if (window.XMLHttpRequest && window.XMLHttpRequest.prototype) {
    var x = XMLHttpRequest.prototype;
    var c = CsrfMagic.prototype;

    // Save the original functions
    x.csrf_open = x.open;
    x.csrf_send = x.send;
    x.csrf_setRequestHeader = x.setRequestHeader;

    // Notice that CsrfMagic is itself an instantiatable object, but only
    // open, send and setRequestHeader are necessary as decorators.
    x.open = c.open;
    x.send = c.send;
    x.setRequestHeader = c.setRequestHeader;
} else {
    // The only way we can do this is by modifying a library you have been
    // using. We support YUI, script.aculo.us, prototype, MooTools,
    // jQuery, Ext and Dojo.
    if (window.jQuery) {
        // jQuery didn't implement a new XMLHttpRequest function, so we have
        // to do this the hard way.
        jQuery.csrf_ajax = jQuery.ajax;
        jQuery.ajax = function( s ) {
            if (s.type && s.type.toUpperCase() == 'POST') {
                s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
                if ( s.data && s.processData && typeof s.data != "string" ) {
                    s.data = jQuery.param(s.data);
                }
                s.data = CsrfMagic.process(s.data);
            }
            return jQuery.csrf_ajax( s );
        }
    } else if (window.Prototype) {
        // This works for script.aculo.us too
        Ajax.csrf_getTransport = Ajax.getTransport;
        Ajax.getTransport = function() {
            return new CsrfMagic(Ajax.csrf_getTransport());
        }
    } else if (window.MooTools) {
        Browser.csrf_Request = Browser.Request;
        Browser.Request = function () {
            return new CsrfMagic(Browser.csrf_Request());
        }
    } else if (window.YAHOO) {
        YAHOO.util.Connect.csrf_createXhrObject = YAHOO.util.Connect.createXhrObject;
        YAHOO.util.Connect.createXhrObject = function (transaction) {
            obj = YAHOO.util.Connect.csrf_createXhrObject(transaction);
            obj.conn = new CsrfMagic(obj.conn);
            return obj;
        }
    } else if (window.Ext) {
        // Ext can use other js libraries as loaders, so it has to come last
        // Ext's implementation is pretty identical to Yahoo's, but we duplicate
        // it for comprehensiveness's sake.
        Ext.lib.Ajax.csrf_createXhrObject = Ext.lib.Ajax.createXhrObject;
        Ext.lib.Ajax.createXhrObject = function (transaction) {
            obj = Ext.lib.Ajax.csrf_createXhrObject(transaction);
            obj.conn = new CsrfMagic(obj.conn);
            return obj;
        }
    } else if (window.dojo) {
        dojo.csrf__xhrObj = dojo._xhrObj;
        dojo._xhrObj = function () {
            return new CsrfMagic(dojo.csrf__xhrObj());
        }
    }
}
<|endoftext|>


<commit_before>module.exports = {
    ACTIONS: {
        SEARCH: 'search',
        DETAILS: 'details',
        STATS: 'stats',
        INJURIES: 'injuries',
        TRANSACTIONS: 'transactions',
        NEWS: 'news',
    },
    TEAM_ACTIONS: {
        LIST: 'list',
        DETAILS: 'details',
        COACHES: 'coaches',
        AFFILIATES: 'affiliates',
        LEADERS: 'leaders',
        ROSTER: 'roster',
        INJURIES: 'injuries',
        TRANSACTIONS: 'transactions',
    },
    ENDPOINTS: {
        WSFB: {
            NEWS: 'wsfb_news_browse',
            INJURIES: 'wsfb_news_injury',
        },
        PUBLIC_LOOKUP: {
            SEARCH: 'search_player_all',
            STATS: {
                HITTING: {
                    TEAM: 'sport_hitting_tm',
                    LEAGUE: 'sport_career_hitting_lg',
                    CAREER: 'sport_career_hitting',
                },
                PITCHING: {
                    TEAM: 'sport_pitching_tm',
                    LEAGUE: 'sport_career_pitching_lg',
                    CAREER: 'sport_career_pitching',
                },
                FIELDING: {
                    TEAM: 'sport_fielding_tm',
                    LEAGUE: 'sport_career_fielding_lg',
                    CAREER: 'sport_career_fielding',
                },
            },
            ROSTER: {
                FORTY: 'roster_40',
                ALL: 'roster_all',
            },
            TRANSACTIONS: 'transaction_all',
        },
    },
    BASE_URLS: {
        PUBLIC_LOOKUP: 'http://lookup-service-prod.mlb.com/json/named.',
        STATS_API: `http://statsapi.mlb.com/api/v1`,
        WSFB: `http://mlb.mlb.com/fantasylookup/json/named.`,
    },
    OPTION_TYPES: {
        QUERY: 'string',
        ACTIVE: 'boolean',
        ID: 'string',
        STAT_TYPE: 'string',
        STAT_ROLE: 'string',
        STAT_GAME_TYPE: 'string',
        STAT_SEASON: 'string',
        START_DATE: 'string',
        END_DATE: 'string',
        PRUNE: 'boolean',
    },
};
<commit_msg>rename actions to player_actions<commit_after>module.exports = {
    PLAYER_ACTIONS: {
        SEARCH: 'search',
        DETAILS: 'details',
        STATS: 'stats',
        INJURIES: 'injuries',
        TRANSACTIONS: 'transactions',
        NEWS: 'news',
    },
    TEAM_ACTIONS: {
        LIST: 'list',
        DETAILS: 'details',
        COACHES: 'coaches',
        AFFILIATES: 'affiliates',
        LEADERS: 'leaders',
        ROSTER: 'roster',
        INJURIES: 'injuries',
        TRANSACTIONS: 'transactions',
    },
    ENDPOINTS: {
        WSFB: {
            NEWS: 'wsfb_news_browse',
            INJURIES: 'wsfb_news_injury',
        },
        PUBLIC_LOOKUP: {
            SEARCH: 'search_player_all',
            STATS: {
                HITTING: {
                    TEAM: 'sport_hitting_tm',
                    LEAGUE: 'sport_career_hitting_lg',
                    CAREER: 'sport_career_hitting',
                },
                PITCHING: {
                    TEAM: 'sport_pitching_tm',
                    LEAGUE: 'sport_career_pitching_lg',
                    CAREER: 'sport_career_pitching',
                },
                FIELDING: {
                    TEAM: 'sport_fielding_tm',
                    LEAGUE: 'sport_career_fielding_lg',
                    CAREER: 'sport_career_fielding',
                },
            },
            ROSTER: {
                FORTY: 'roster_40',
                ALL: 'roster_all',
            },
            TRANSACTIONS: 'transaction_all',
        },
    },
    BASE_URLS: {
        PUBLIC_LOOKUP: 'http://lookup-service-prod.mlb.com/json/named.',
        STATS_API: `http://statsapi.mlb.com/api/v1`,
        WSFB: `http://mlb.mlb.com/fantasylookup/json/named.`,
    },
    OPTION_TYPES: {
        QUERY: 'string',
        ACTIVE: 'boolean',
        ID: 'string',
        STAT_TYPE: 'string',
        STAT_ROLE: 'string',
        STAT_GAME_TYPE: 'string',
        STAT_SEASON: 'string',
        START_DATE: 'string',
        END_DATE: 'string',
        PRUNE: 'boolean',
    },
};
<|endoftext|>


<commit_before>//
// The OpenDiamond Platform for Interactive Search
//
// Copyright (c) 2012 Carnegie Mellon University
// All rights reserved.
//
// This software is distributed under the terms of the Eclipse Public
// License, Version 1.0 which can be found in the file named LICENSE.
// ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS SOFTWARE CONSTITUTES
// RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT
//

function AutoPause(blasters, max_unexposed, tag_class) {
  // Make sure "new" was used
  if (!(this instanceof arguments.callee)) {
    throw new Error('Constructor called as a function');
  }

  // Normalize a single blaster to an array
  if (!(blasters instanceof Array)) {
    blasters = [blasters];
  }

  // Default parameters
  if (typeof max_unexposed === 'undefined') {
    max_unexposed = 50;
  }
  if (typeof tag_class === 'undefined') {
    tag_class = 'unexposed';
  }

  // Private members
  var selector = '.' + tag_class;

  // Private methods
  function is_unexposed(el) {
    return $(el).offset().top > $(document).scrollTop() + $(window).height();
  }

  function update_exposed() {
    $(selector).each(function(i, el) {
      if (!is_unexposed(el)) {
        $(el).removeClass(tag_class);
      }
    });
    if ($(selector).length < max_unexposed) {
      $.each(blasters, function(i, blaster) {
        blaster.resume();
      });
    }
  }

  // Connect to window events
  $(window).resize(update_exposed);
  $(window).scroll(update_exposed);

  // Public methods
  this.element_added = function(el) {
    if (is_unexposed(el)) {
      $(el).addClass(tag_class);
    }
    if ($(selector).length >= max_unexposed) {
      $.each(blasters, function(i, blaster) {
        blaster.pause();
      });
    }
  };
}
<commit_msg>json-blaster-autopause.js: Support tabbed search applications<commit_after>//
// The OpenDiamond Platform for Interactive Search
//
// Copyright (c) 2012 Carnegie Mellon University
// All rights reserved.
//
// This software is distributed under the terms of the Eclipse Public
// License, Version 1.0 which can be found in the file named LICENSE.
// ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS SOFTWARE CONSTITUTES
// RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT
//

function AutoPause(blasters, max_unexposed, tag_class) {
  // Make sure "new" was used
  if (!(this instanceof arguments.callee)) {
    throw new Error('Constructor called as a function');
  }

  // Normalize a single blaster to an array
  if (!(blasters instanceof Array)) {
    blasters = [blasters];
  }

  // Default parameters
  if (typeof max_unexposed === 'undefined') {
    max_unexposed = 50;
  }
  if (typeof tag_class === 'undefined') {
    tag_class = 'unexposed';
  }

  // Private members
  var selector = '.' + tag_class;

  // Private methods
  function is_unexposed(el) {
    return !$(el).is(':visible') ||
        $(el).offset().top > $(document).scrollTop() + $(window).height();
  }

  function update_exposed() {
    $(selector).each(function(i, el) {
      if (!is_unexposed(el)) {
        $(el).removeClass(tag_class);
      }
    });
    if ($(selector).length < max_unexposed) {
      $.each(blasters, function(i, blaster) {
        blaster.resume();
      });
    }
  }

  // Connect to window events
  $(window).resize(update_exposed);
  $(window).scroll(update_exposed);

  // Public methods
  this.element_added = function(el) {
    if (is_unexposed(el)) {
      $(el).addClass(tag_class);
    }
    if ($(selector).length >= max_unexposed) {
      $.each(blasters, function(i, blaster) {
        blaster.pause();
      });
    }
  };

  this.refresh = function() {
    update_exposed();
  };
}
<|endoftext|>


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

require.paths.unshift(path.join(__dirname, 'vendor'),
                      path.join(__dirname, 'cradle'));

var sys = require("sys"),
   http = require("http"),
 events = require('events'),
     fs = require("fs"),
    url = require('url');

var querystring = require('querystring');

var cradle = exports;

cradle.host = '127.0.0.1';
cradle.port = 5984;
cradle.options = {
    cache: true,
    raw: false,
};

cradle.setup = function (settings) {
    this.host = settings.host;
    this.port = parseInt(settings.port);
    process.mixin(this.options, settings);

    return this;
};

//
// Holds arguments to the public interfaces.
// It helps us deal with variable arguments,
// or ommitted callbacks.
//
function Args(arguments) {
    this.array = Array.prototype.slice.call(arguments);
    this.__defineGetter__('length', function () {
        if (this.callbackGiven()) {
            return this.array.length - 1;
        } else {
            return this.array.length;
        }
    });
    this.callbackGiven = function () {
        return typeof(this.at(-1)) === 'function';
    };
    this.at = function (n) {
        if (n < 0) {
            return this.array[this.array.length + n];
        } else {
            return this.array[n];
        }
    };
    this.__defineGetter__('all', function () {
        if (this.callbackGiven()) {
            return this.array.slice(0, -1);
        } else {
            return this.array;
        }
    });
    this.__defineGetter__('last', function () {
        if (typeof(this.at(-1)) === 'function') {
            return this.at(-2);
        } else {
            return this.at(-1);
        }
    });
    this.__defineGetter__('first', function () {
        return this.array[0];
    });
    this.callback = this.callbackGiven() ? this.at(-1)
                                         : function () {};
}
Args.last = function (args) {
    return args[args.length - 1];
};

cradle.Connection = function Connection(/* variable args */) {
    var args = Array.prototype.slice.call(arguments),
        host, port, remote, options = {};

    if (typeof(args[0]) === 'string') {
        remote = args[0].replace('http://', '').split(':');
        host = remote[0];
        port = parseInt(remote[1]);
    }

    // An options hash was passed
    if (args.length === 1 && typeof(args[0]) === 'object') {
        options = args[0];
        host = options.host;
        port = parseInt(options.port);
    // The host and port were passed separately
    } else if (args.length >= 2) {
        host = args[0];
        port = parseInt(args[1]);
        options = args[2] || {};
    }

    this.host = host || cradle.host;
    this.port = port || cradle.port;
    this.options = process.mixin({}, cradle.options, options);

    this.socket = http.createClient(this.port, this.host);
};

//
// Connection.request()
//
//      This is the entry point for all requests to CouchDB, at this point,
//      the database name has been embed in the url, by one of the wrappers.
//
cradle.Connection.prototype.request = function (method, path, options, data, headers) {
    var promise = new(events.EventEmitter), request, that = this;

    path = (path || '/').replace('http://', '').replace(/\/{2,}/g, '/');
    if (path.charAt(0) !== '/') { path = '/' + path }

    // HTTP Headers
    headers = process.mixin({ host: this.host }, headers || {});

    //
    // Handle POST/PUT data. We also convert functions to strings,
    // so they can be used in _design documents.
    //
    if (data) {
        data = JSON.stringify(data, function (k, val) {
            if (typeof(val) === 'function') {
                return val.toString();
            } else { return val }
        });
        headers["Content-Length"] = data.length;
        headers["Content-Type"] = "application/json";
    }

    // Query string
    if (options) {
        for (var k in options) {
            if (typeof(options[k]) === 'boolean') {
                options[k] = String(options[k]);
            }
        }
        path += '?' + querystring.stringify(options);
    }

    //
    // Initialize the request, send the body, and finally,
    // dispatch the request.
    //
    request = this.socket.request(method.toUpperCase(), path, headers);

    if (data) { request.write(data, 'utf8') }

    request.addListener('response', function (res) {
        var body = '';

        res.setBodyEncoding('utf8');
        res.addListener('data', function (chunk) {
            body += (chunk || '');
        }).addListener('end', function () {
            var obj, response;

            try { obj = JSON.parse(body) }
            catch (e) { return promise.emitError(e) }

            // If the `raw` option was set, we return the parsed
            // body as-is. If not, we wrap it in a `Response` object.
            if (that.options.raw) {
                response = obj;
            } else {
                response = new(cradle.Response)(res, obj);
            }

            promise.emit("done", response.error || null, response);

            if (response.error) {
                promise.emit("error", response.error);
            } else {
                promise.emit("success", response);
            }
        });
    });
    request.close();

    return promise;
};

//
// The database object
//
//      We return an object with database functions,
//      closing around the `name` argument.
//
cradle.Connection.prototype.database = function (name) {
    var that = this;

    return {
        name: name,
        //
        // The database query cache.
        //
        // Each database object has its own cache store.
        // The cache.* methods are all wrappers around
        // `cache.query`, which transparently checks if
        // caching is enabled, before performing any action.
        //
        cache: {
            store: {},
            get: function (id) {
                return this.query('get', id);
            },
            save: function (id, doc) {
                return this.query('save', id, doc);
            },
            purge: function (id) {
                return this.query('purge', id);
            },
            has: function (id) {
                return this.query('has', id);
            },
            query: function (op, id, doc) {
                if (that.options.cache) {
                    switch (op) {
                        case 'has'  : return id in this.store;
                        case 'get'  : return this.store[id];
                        case 'save' : return this.store[id] = doc;
                        case 'purge': if (id) { delete(this.store[id]) }
                                      else { this.store = {} };
                                      break;
                    }
                } else { return false }
            }
        },

        // A wrapper around `Connection.request`,
        // which prepends the database name.
        query: function (method, path, options, data, headers) {
            return that.request(
                method, [name, path].join('/'), options, data, headers
            );
        },
        exists: function () {
            var promise = new(events.EventEmitter),
                args = new(Args)(arguments);

            this.query('GET', '/').addListener('done', function (err, res) {
                if (err) {
                    if (res._headers.status === 404) {
                        args.callback(null, false);
                        promise.emit("success", false);
                    } else {
                        args.callback(err, res);
                        promise.emit("error", res);
                    }
                } else {
                    args.callback(null, true);
                    promise.emit("success", true);
                }
            });
            return promise;
        },

        // Fetch a single document from the database, or cache.
        // If it's from the db, attempt to save it to the cache.
        get: function (id, rev) {
            var that = this, options = null,
                args = new(Args)(arguments);

            if (rev && args.length === 2) {
                if (typeof(rev) === 'string') { options = {rev: rev} }
                else if (typeof(rev) === 'object') { options = rev }
            } else if (this.cache.has(id)) {
                args.callback(null, this.cache.get(id));
            }

            return this.query('GET', id, options).addListener('done', function (err, res) {
                if (! err) that.cache.save(res.id, res.doc);
                args.callback(err, res);
            });
        },

        // A wrapper around POST, PUT and _bulk_docs.
        // It handles an optional '_rev', as well as saving '_design' docs.
        // After the document was saved successfully, it's written through the cache,
        // via a callback. The returned '_rev' is also saved.
        save: function (/* [id], [rev], doc, ... */) {
            var id, doc = {}, that = this, args = new(Args)(arguments);

            // PUT a single document, with an id
            if (typeof(args.first) === 'string' && args.length > 1) {
                id = args.first;
                // Design document
                if (/^_design\/\w+$/.test(id)) {
                    doc.language = "javascript";
                    doc.views = args.last;
                } else {
                    doc = args.last;
                }
                // Try to set the '_rev' attribute of the document.
                // If it wasn't passed, attempt to retrieve it from the cache.
                if (typeof(args.at(1)) === 'string') { doc._rev = args.at(1) }
                else if (!doc._rev && this.cache.has(id)) {
                    doc._rev = this.cache.get(id)._rev;
                }

                return this.query('PUT', id, null, doc).addListener('done', writeThrough);

            // POST a single document
            } else if (args.length === 1 && !Array.isArray(args.first)) {
                doc = args.first;
                return this.query('POST', '/', null, doc).addListener('done', writeThrough);

            // Bulk insert
            } else {
                if (Array.isArray(args.first)) {
                    doc = {docs: args.first};
                    if (args.at(1) === true) { doc.all_or_nothing = true }
                } else {
                    doc = {docs: args.all};
                }
                return this.query('POST', '/_bulk_docs', {}, doc)
                           .addListener('done', function (err, res) {
                    args.callback(err, res);
                });
            }

            function writeThrough(err, res) {
                if (! err) that.cache.save(id, process.mixin({}, doc, { _rev: res.rev }));
                args.callback(err, res);
            }
        },
        update: function (id, obj) {
            var doc = this.cache.get(id);
            if (doc) {
                return this.save(id, process.mixin(true, {}, doc, obj), Args.last(arguments));
            } else {
                throw new(Error)(id + " wasn't found in cache-store, couldn't update");
            }
        },

        // Destroys a database with 'DELETE'
        // we raise an exception if arguments were supplied,
        // as we don't want users to confuse this function with `remove`.
        destroy: function (callback) {
            if (arguments.length > 1) {
                throw new(Error)("destroy() doesn't take any additional arguments");
            } else {
                return this.query('DELETE', '/').addListener('done', callback);
            }
        },

        // Delete a document
        // if the _rev wasn't supplied, we attempt to retrieve it from the
        // cache. If the deletion was successful, we purge the cache.
        remove: function (id, rev) {
            var that = this, doc, args = new(Args)(arguments);

            if (typeof(rev) !== 'string') {
                if (doc = this.cache.get(id)) { rev = doc._rev }
                else { throw new(Error)("rev needs to be supplied") }
            }
            return this.query('DELETE', id, {rev: rev})
                       .addListener('done', function (err, res) {
                if (! err) that.cache.purge(id);
                args.callback(err, res);
            });
        },
        create: function (c) {
            return this.query('PUT', '/').addListener('done', c);
        },
        info: function (c) {
            return this.query('GET', '/').addListener('done', c);
        },
        all: function (options) {
            return this.query('GET', '/_all_docs', typeof(options) === 'object' ? options : {})
                       .addListener('done', Args.last(arguments));
        },
        compact: function (design) {
            return this.query('POST', '/_compact' + (typeof(design) === 'string' ? '/' + design : ''))
                       .addListener('done', Args.last(arguments));
        },
        viewCleanup: function (c) {
            return this.query('POST', '/_view_cleanup').addListener('done', c);
        },
        allBySeq: function (options) {
            return this.query('GET', '/_all_docs_by_seq', options).addListener('done', c);
        },

        // Query a view, passing any options to the query string.
        // Some query string parameters' values have to be JSON-encoded.
        view: function (path, options) {
            var args = new(Args)(arguments);
            path = path.split('/');

            if (typeof(options) === 'object') {
                ['key', 'startkey', 'endkey'].forEach(function (k) {
                    if (k in options) { options[k] = JSON.stringify(options[k]) }
                });
            }
            return this.query(
                'GET', ['_design', path[0], '_view', path[1]].join('/'), options
            ).addListener('done', args.callback);
        },

        push: function (doc) {}
    }

};

//
// Wrapper functions for the server API
//
cradle.Connection.prototype.databases = function (c) {
    return this.request('GET', '/_all_dbs').addListener('done', c);
};
cradle.Connection.prototype.config = function (c) {
    return this.request('GET', '/_config').addListener('done', c);
};
cradle.Connection.prototype.info = function (c) {
    return this.request('GET', '/').addListener('done', c);
};
cradle.Connection.prototype.stats = function (c) {
    return this.request('GET', '/_stats').addListener('done', c);
};
cradle.Connection.prototype.activeTasks = function (c) {
    return this.request('GET', '/_active_tasks').addListener('done', c);
};
cradle.Connection.prototype.uuids = function (count) {
    return this.request('GET', '/_uuids', count ? {count: count} : {});
};

//
// HTTP response wrapper
//
//      It allows us to call array-like methods on documents
//      with a 'row' attribute.
//
cradle.Response = function Response(response, json) {
    var that = this;

    this._headers = {
        status: response.statusCode
    };

    this.doc = json;

    if (json instanceof Array) {
        return json;
    }

    Object.keys(json).forEach(function (k) {
        that.__defineGetter__(k, function () { return json[k] });
    });

    if (!this.id && this._id) this.id = this._id;
    if (!this.rev && this._rev) this.rev = this._rev;

    if (this.rows) {
        this.forEach = function (f) { return that.rows.forEach(f) };
        this.map = function (f) { return that.rows.map(f) };
    }
    this.toString = function () { return sys.inspect(this.doc) };
};

<commit_msg>removed update(). save() is for modifying documents, insert() is for creating documents.<commit_after>var path = require('path');

require.paths.unshift(path.join(__dirname, 'vendor'),
                      path.join(__dirname, 'cradle'));

var sys = require("sys"),
   http = require("http"),
 events = require('events'),
     fs = require("fs"),
    url = require('url');

var querystring = require('querystring');

var cradle = exports;

cradle.host = '127.0.0.1';
cradle.port = 5984;
cradle.options = {
    cache: true,
    raw: false,
};

cradle.setup = function (settings) {
    this.host = settings.host;
    this.port = parseInt(settings.port);
    process.mixin(this.options, settings);

    return this;
};

//
// Holds arguments to the public interfaces.
// It helps us deal with variable arguments,
// or ommitted callbacks.
//
function Args(arguments) {
    this.array = Array.prototype.slice.call(arguments);
    this.__defineGetter__('length', function () {
        if (this.callbackGiven()) {
            return this.array.length - 1;
        } else {
            return this.array.length;
        }
    });
    this.callbackGiven = function () {
        return typeof(this.at(-1)) === 'function';
    };
    this.at = function (n) {
        if (n < 0) {
            return this.array[this.array.length + n];
        } else {
            return this.array[n];
        }
    };
    this.__defineGetter__('all', function () {
        if (this.callbackGiven()) {
            return this.array.slice(0, -1);
        } else {
            return this.array;
        }
    });
    this.__defineGetter__('last', function () {
        if (typeof(this.at(-1)) === 'function') {
            return this.at(-2);
        } else {
            return this.at(-1);
        }
    });
    this.__defineGetter__('first', function () {
        return this.array[0];
    });
    this.callback = this.callbackGiven() ? this.at(-1)
                                         : function () {};
}
Args.last = function (args) {
    return args[args.length - 1];
};

cradle.Connection = function Connection(/* variable args */) {
    var args = Array.prototype.slice.call(arguments),
        host, port, remote, options = {};

    if (typeof(args[0]) === 'string') {
        remote = args[0].replace('http://', '').split(':');
        host = remote[0];
        port = parseInt(remote[1]);
    }

    // An options hash was passed
    if (args.length === 1 && typeof(args[0]) === 'object') {
        options = args[0];
        host = options.host;
        port = parseInt(options.port);
    // The host and port were passed separately
    } else if (args.length >= 2) {
        host = args[0];
        port = parseInt(args[1]);
        options = args[2] || {};
    }

    this.host = host || cradle.host;
    this.port = port || cradle.port;
    this.options = process.mixin({}, cradle.options, options);

    this.socket = http.createClient(this.port, this.host);
};

//
// Connection.request()
//
//      This is the entry point for all requests to CouchDB, at this point,
//      the database name has been embed in the url, by one of the wrappers.
//
cradle.Connection.prototype.request = function (method, path, options, data, headers) {
    var promise = new(events.EventEmitter), request, that = this;

    path = (path || '/').replace('http://', '').replace(/\/{2,}/g, '/');
    if (path.charAt(0) !== '/') { path = '/' + path }

    // HTTP Headers
    headers = process.mixin({ host: this.host }, headers || {});

    //
    // Handle POST/PUT data. We also convert functions to strings,
    // so they can be used in _design documents.
    //
    if (data) {
        data = JSON.stringify(data, function (k, val) {
            if (typeof(val) === 'function') {
                return val.toString();
            } else { return val }
        });
        headers["Content-Length"] = data.length;
        headers["Content-Type"] = "application/json";
    }

    // Query string
    if (options) {
        for (var k in options) {
            if (typeof(options[k]) === 'boolean') {
                options[k] = String(options[k]);
            }
        }
        path += '?' + querystring.stringify(options);
    }

    //
    // Initialize the request, send the body, and finally,
    // dispatch the request.
    //
    request = this.socket.request(method.toUpperCase(), path, headers);

    if (data) { request.write(data, 'utf8') }

    request.addListener('response', function (res) {
        var body = '';

        res.setBodyEncoding('utf8');
        res.addListener('data', function (chunk) {
            body += (chunk || '');
        }).addListener('end', function () {
            var obj, response;

            try { obj = JSON.parse(body) }
            catch (e) { return promise.emitError(e) }

            // If the `raw` option was set, we return the parsed
            // body as-is. If not, we wrap it in a `Response` object.
            if (that.options.raw) {
                response = obj;
            } else {
                response = new(cradle.Response)(res, obj);
            }

            promise.emit("done", response.error || null, response);

            if (response.error) {
                promise.emit("error", response.error);
            } else {
                promise.emit("success", response);
            }
        });
    });
    request.close();

    return promise;
};

//
// The database object
//
//      We return an object with database functions,
//      closing around the `name` argument.
//
cradle.Connection.prototype.database = function (name) {
    var that = this;

    return {
        name: name,
        //
        // The database query cache.
        //
        // Each database object has its own cache store.
        // The cache.* methods are all wrappers around
        // `cache.query`, which transparently checks if
        // caching is enabled, before performing any action.
        //
        cache: {
            store: {},
            get: function (id) {
                return this.query('get', id);
            },
            save: function (id, doc) {
                return this.query('save', id, doc);
            },
            purge: function (id) {
                return this.query('purge', id);
            },
            has: function (id) {
                return this.query('has', id);
            },
            query: function (op, id, doc) {
                if (that.options.cache) {
                    switch (op) {
                        case 'has'  : return id in this.store;
                        case 'get'  : return this.store[id];
                        case 'save' : return this.store[id] = doc;
                        case 'purge': if (id) { delete(this.store[id]) }
                                      else { this.store = {} };
                                      break;
                    }
                } else { return false }
            }
        },

        // A wrapper around `Connection.request`,
        // which prepends the database name.
        query: function (method, path, options, data, headers) {
            return that.request(
                method, [name, path].join('/'), options, data, headers
            );
        },
        exists: function () {
            var promise = new(events.EventEmitter),
                args = new(Args)(arguments);

            this.query('GET', '/').addListener('done', function (err, res) {
                if (err) {
                    if (res._headers.status === 404) {
                        args.callback(null, false);
                        promise.emit("success", false);
                    } else {
                        args.callback(err, res);
                        promise.emit("error", res);
                    }
                } else {
                    args.callback(null, true);
                    promise.emit("success", true);
                }
            });
            return promise;
        },

        // Fetch a single document from the database, or cache.
        // If it's from the db, attempt to save it to the cache.
        get: function (id, rev) {
            var that = this, options = null,
                args = new(Args)(arguments);

            if (rev && args.length === 2) {
                if (typeof(rev) === 'string') { options = {rev: rev} }
                else if (typeof(rev) === 'object') { options = rev }
            } else if (this.cache.has(id)) {
                args.callback(null, this.cache.get(id));
            }

            return this.query('GET', id, options).addListener('done', function (err, res) {
                if (! err) that.cache.save(res.id, res.doc);
                args.callback(err, res);
            });
        },

        save: function (/* [id], [rev], doc */) {
            var id, doc = {}, cached, that = this, args = new(Args)(arguments);

            // PUT a single document, with an id
            if (typeof(args.first) === 'string') {
                doc = args.last;
                doc._id = id = args.first;

                // Try to set the '_rev' attribute of the document.
                // If it wasn't passed, attempt to retrieve it from the cache.
                if (typeof(args.at(1)) === 'string') {
                    doc._rev = args.at(1);
                } else if (doc._rev || this.cache.has(id)) {
                    doc = process.mixin(true, {}, this.cache.get(id), doc);
                } else {
                    sys.puts(sys.inspect(doc))
                    throw new(Error)("Couldn't save without a _rev");
                }
            } else {
               this.insert.apply(arguments);
            }
            return this.insert(doc, args.callback);
        },

        insert: function (/* [id], doc, ... */) {
            var id, doc = {}, that = this, args = new(Args)(arguments);

            // PUT a single document, with an id
            if (typeof(args.first) === 'string') {
                id = args.first;
                // Design document
                if (/^_design\/\w+$/.test(id)) {
                    doc.language = "javascript";
                    doc.views = args.last;
                } else {
                    doc = args.last;
                }
                return this.query('PUT', id, null, doc).addListener('done', writeThrough);

            // PUT or POST a single document
            } else if (args.length === 1 && !Array.isArray(args.first)) {
                doc = args.first;
                id = doc._id;
                if (id) {
                    return this.query('PUT', id, null, doc).addListener('done', writeThrough);
                } else {
                    return this.query('POST', '/', null, doc).addListener('done', writeThrough);
                }

            // Bulk insert
            } else {
                if (Array.isArray(args.first)) {
                    doc = {docs: args.first};
                    if (args.at(1) === true) { doc.all_or_nothing = true }
                } else {
                    doc = {docs: args.all};
                }
                return this.query('POST', '/_bulk_docs', {}, doc)
                           .addListener('done', function (err, res) {
                    args.callback(err, res);
                });
            }

            function writeThrough(err, res) {
                if (! err) that.cache.save(id, process.mixin({}, doc, { _rev: res.rev }));
                args.callback(err, res);
            }
        },

        // Destroys a database with 'DELETE'
        // we raise an exception if arguments were supplied,
        // as we don't want users to confuse this function with `remove`.
        destroy: function (callback) {
            if (arguments.length > 1) {
                throw new(Error)("destroy() doesn't take any additional arguments");
            } else {
                return this.query('DELETE', '/').addListener('done', callback);
            }
        },

        // Delete a document
        // if the _rev wasn't supplied, we attempt to retrieve it from the
        // cache. If the deletion was successful, we purge the cache.
        remove: function (id, rev) {
            var that = this, doc, args = new(Args)(arguments);

            if (typeof(rev) !== 'string') {
                if (doc = this.cache.get(id)) { rev = doc._rev }
                else { throw new(Error)("rev needs to be supplied") }
            }
            return this.query('DELETE', id, {rev: rev})
                       .addListener('done', function (err, res) {
                if (! err) that.cache.purge(id);
                args.callback(err, res);
            });
        },
        create: function (c) {
            return this.query('PUT', '/').addListener('done', c);
        },
        info: function (c) {
            return this.query('GET', '/').addListener('done', c);
        },
        all: function (options) {
            return this.query('GET', '/_all_docs', typeof(options) === 'object' ? options : {})
                       .addListener('done', Args.last(arguments));
        },
        compact: function (design) {
            return this.query('POST', '/_compact' + (typeof(design) === 'string' ? '/' + design : ''))
                       .addListener('done', Args.last(arguments));
        },
        viewCleanup: function (c) {
            return this.query('POST', '/_view_cleanup').addListener('done', c);
        },
        allBySeq: function (options) {
            return this.query('GET', '/_all_docs_by_seq', options).addListener('done', c);
        },

        // Query a view, passing any options to the query string.
        // Some query string parameters' values have to be JSON-encoded.
        view: function (path, options) {
            var args = new(Args)(arguments);
            path = path.split('/');

            if (typeof(options) === 'object') {
                ['key', 'startkey', 'endkey'].forEach(function (k) {
                    if (k in options) { options[k] = JSON.stringify(options[k]) }
                });
            }
            return this.query(
                'GET', ['_design', path[0], '_view', path[1]].join('/'), options
            ).addListener('done', args.callback);
        },

        push: function (doc) {}
    }

};

//
// Wrapper functions for the server API
//
cradle.Connection.prototype.databases = function (c) {
    return this.request('GET', '/_all_dbs').addListener('done', c);
};
cradle.Connection.prototype.config = function (c) {
    return this.request('GET', '/_config').addListener('done', c);
};
cradle.Connection.prototype.info = function (c) {
    return this.request('GET', '/').addListener('done', c);
};
cradle.Connection.prototype.stats = function (c) {
    return this.request('GET', '/_stats').addListener('done', c);
};
cradle.Connection.prototype.activeTasks = function (c) {
    return this.request('GET', '/_active_tasks').addListener('done', c);
};
cradle.Connection.prototype.uuids = function (count) {
    return this.request('GET', '/_uuids', count ? {count: count} : {});
};

//
// HTTP response wrapper
//
//      It allows us to call array-like methods on documents
//      with a 'row' attribute.
//
cradle.Response = function Response(response, json) {
    var that = this;

    this._headers = {
        status: response.statusCode
    };

    this.doc = json;

    if (json instanceof Array) {
        return json;
    }

    Object.keys(json).forEach(function (k) {
        that.__defineGetter__(k, function () { return json[k] });
    });

    if (!this.id && this._id) this.id = this._id;
    if (!this.rev && this._rev) this.rev = this._rev;

    if (this.rows) {
        this.forEach = function (f) { return that.rows.forEach(f) };
        this.map = function (f) { return that.rows.map(f) };
    }
    this.toString = function () { return sys.inspect(this.doc) };
};

<|endoftext|>


<commit_before>// dependencies
//
import React from 'react';
import classnames from 'classnames';

class TextInput extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            value:   props.value,
            isValid: true,
        };

        this.validationMessage = props.validationMessage || `${this.props.description} is required`;

        this.onBlur = this.onBlur.bind(this);
        this.onChange = this.onChange.bind(this);
    }

    onBlur(event) {
        const value = event.target.value;

        const isValid = !this.props.required || !!value ;

        this.setState({ isValid });

        if (this.props.onChildValidationEvent) {
            this.props.onChildValidationEvent(this.props.validationKey, isValid ? null : this.validationMessage);
        }
    }

    onChange(event) {
        const value = event.target.value;

        this.setState({ value });
    }

    render() {
        return (

                <input
                    type="text"
                    value={this.state.value}
                    onBlur={this.onBlur}
                    onChange={this.onChange}
                />
                {!this.state.isValid
                    ? {this.validationMessage}
                    : null
                }

        );
    }
}

// set the property types for the component
//
TextInput.propTypes = {
    required:               React.PropTypes.bool,
    value:                  React.PropTypes.string,
    description:            React.PropTypes.string,
    validationMessage:      React.PropTypes.string,
    validationKey:          React.PropTypes.string.isRequired,
    onChildValidationEvent: React.PropTypes.func.isRequired,
};

export default TextInput;
<commit_msg>code cleanup<commit_after>// dependencies
//
import React from 'react';
import classnames from 'classnames';

class TextInput extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            value:   props.value,
            isValid: true,
        };

        this.validationMessage = props.validationMessage || `${this.props.description} is required`;

        this.onBlur = this.onBlur.bind(this);
        this.onChange = this.onChange.bind(this);
    }

    onBlur(event) {
        const value = event.target.value;

        const isValid = !this.props.required || !!value;

        this.setState({ isValid });

        if (this.props.onChildValidationEvent) {
            this.props.onChildValidationEvent(this.props.validationKey, isValid ? null : this.validationMessage);
        }
    }

    onChange(event) {
        const value = event.target.value;

        this.setState({ value });
    }

    render() {
        return (

                <input
                    type="text"
                    value={this.state.value}
                    onBlur={this.onBlur}
                    onChange={this.onChange}
                />
                {!this.state.isValid
                    ? {this.validationMessage}
                    : null
                }

        );
    }
}

// set the property types for the component
//
TextInput.propTypes = {
    required:               React.PropTypes.bool,
    value:                  React.PropTypes.string,
    description:            React.PropTypes.string,
    validationMessage:      React.PropTypes.string,
    validationKey:          React.PropTypes.string.isRequired,
    onChildValidationEvent: React.PropTypes.func.isRequired,
};

export default TextInput;
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

/* @flow */
import StackFrame from './stack-frame';
import { getSourceMap } from './getSourceMap';
import { getLinesAround } from './getLinesAround';
import path from 'path';

function count(search: string, string: string): number {
  // Count starts at -1 becuse a do-while loop always runs at least once
  let count = -1,
    index = -1;
  do {
    // First call or the while case evaluated true, meaning we have to make
    // count 0 or we found a character
    ++count;
    // Find the index of our search string, starting after the previous index
    index = string.indexOf(search, index + 1);
  } while (index !== -1);
  return count;
}

/**
 * Turns a set of mapped StackFrames back into their generated code position and enhances them with code.
 * @param {string} fileUri The URI of the bundle.js file.
 * @param {StackFrame[]} frames A set of StackFrames which are already mapped and missing their generated positions.
 * @param {number} [fileContents=3] The number of lines to provide before and after the line specified in the StackFrame.
 */
async function unmap(
  _fileUri: string | { uri: string, contents: string },
  frames: StackFrame[],
  contextLines: number = 3
): Promise<StackFrame[]> {
  let fileContents = typeof _fileUri === 'object' ? _fileUri.contents : null;
  let fileUri = typeof _fileUri === 'object' ? _fileUri.uri : _fileUri;
  if (fileContents == null) {
    fileContents = await fetch(fileUri).then(res => res.text());
  }
  const map = await getSourceMap(fileUri, fileContents);
  return frames.map(frame => {
    const {
      functionName,
      lineNumber,
      columnNumber,
      _originalLineNumber,
    } = frame;
    if (_originalLineNumber != null) {
      return frame;
    }
    let { fileName } = frame;
    if (fileName) {
      fileName = path.normalize(fileName);
    }
    if (fileName == null) {
      return frame;
    }
    const fN: string = fileName;
    const source = map
      .getSources()
      .map(s => s.replace(/[\\]+/g, '/'))
      .filter(p => {
        p = path.normalize(p);
        const i = p.lastIndexOf(fN);
        return i !== -1 && i === p.length - fN.length;
      })
      .map(p => ({
        token: p,
        seps: count(path.sep, path.normalize(p)),
        penalties: count('node_modules', p) + count('~', p),
      }))
      .sort((a, b) => {
        const s = Math.sign(a.seps - b.seps);
        if (s !== 0) {
          return s;
        }
        return Math.sign(a.penalties - b.penalties);
      });
    if (source.length < 1 || lineNumber == null) {
      return new StackFrame(
        null,
        null,
        null,
        null,
        null,
        functionName,
        fN,
        lineNumber,
        columnNumber,
        null
      );
    }
    const sourceT = source[0].token;
    const { line, column } = map.getGeneratedPosition(
      sourceT,
      lineNumber,
      // $FlowFixMe
      columnNumber
    );
    const originalSource = map.getSource(sourceT);
    return new StackFrame(
      functionName,
      fileUri,
      line,
      column || null,
      getLinesAround(line, contextLines, fileContents || []),
      functionName,
      fN,
      lineNumber,
      columnNumber,
      getLinesAround(lineNumber, contextLines, originalSource)
    );
  });
}

export { unmap };
export default unmap;
<commit_msg>Unmapper Windows compatibility (#3079)<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

/* @flow */
import StackFrame from './stack-frame';
import { getSourceMap } from './getSourceMap';
import { getLinesAround } from './getLinesAround';
import path from 'path';

function count(search: string, string: string): number {
  // Count starts at -1 becuse a do-while loop always runs at least once
  let count = -1,
    index = -1;
  do {
    // First call or the while case evaluated true, meaning we have to make
    // count 0 or we found a character
    ++count;
    // Find the index of our search string, starting after the previous index
    index = string.indexOf(search, index + 1);
  } while (index !== -1);
  return count;
}

/**
 * Turns a set of mapped StackFrames back into their generated code position and enhances them with code.
 * @param {string} fileUri The URI of the bundle.js file.
 * @param {StackFrame[]} frames A set of StackFrames which are already mapped and missing their generated positions.
 * @param {number} [fileContents=3] The number of lines to provide before and after the line specified in the StackFrame.
 */
async function unmap(
  _fileUri: string | { uri: string, contents: string },
  frames: StackFrame[],
  contextLines: number = 3
): Promise<StackFrame[]> {
  let fileContents = typeof _fileUri === 'object' ? _fileUri.contents : null;
  let fileUri = typeof _fileUri === 'object' ? _fileUri.uri : _fileUri;
  if (fileContents == null) {
    fileContents = await fetch(fileUri).then(res => res.text());
  }
  const map = await getSourceMap(fileUri, fileContents);
  return frames.map(frame => {
    const {
      functionName,
      lineNumber,
      columnNumber,
      _originalLineNumber,
    } = frame;
    if (_originalLineNumber != null) {
      return frame;
    }
    let { fileName } = frame;
    if (fileName) {
      // The web version of this module only provides POSIX support, so Windows
      // paths like C:\foo\\baz\..\\bar\ cannot be normalized.
      // A simple solution to this is to replace all `\` with `/`, then
      // normalize afterwards.
      fileName = path.normalize(fileName.replace(/[\\]+/g, '/'));
    }
    if (fileName == null) {
      return frame;
    }
    const fN: string = fileName;
    const source = map
      .getSources()
      // Prepare path for normalization; see comment above for reasoning.
      .map(s => s.replace(/[\\]+/g, '/'))
      .filter(p => {
        p = path.normalize(p);
        const i = p.lastIndexOf(fN);
        return i !== -1 && i === p.length - fN.length;
      })
      .map(p => ({
        token: p,
        seps: count(path.sep, path.normalize(p)),
        penalties: count('node_modules', p) + count('~', p),
      }))
      .sort((a, b) => {
        const s = Math.sign(a.seps - b.seps);
        if (s !== 0) {
          return s;
        }
        return Math.sign(a.penalties - b.penalties);
      });
    if (source.length < 1 || lineNumber == null) {
      return new StackFrame(
        null,
        null,
        null,
        null,
        null,
        functionName,
        fN,
        lineNumber,
        columnNumber,
        null
      );
    }
    const sourceT = source[0].token;
    const { line, column } = map.getGeneratedPosition(
      sourceT,
      lineNumber,
      // $FlowFixMe
      columnNumber
    );
    const originalSource = map.getSource(sourceT);
    return new StackFrame(
      functionName,
      fileUri,
      line,
      column || null,
      getLinesAround(line, contextLines, fileContents || []),
      functionName,
      fN,
      lineNumber,
      columnNumber,
      getLinesAround(lineNumber, contextLines, originalSource)
    );
  });
}

export { unmap };
export default unmap;
<|endoftext|>


<commit_before>'use strict';

/*
 * Copyright 2016 Next Century Corporation
 * 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.
 *
 */

/**
 * This visualization shows aggregated data in a table.
 * @namespace neonDemo.controllers
 * @class aggregationTableController
 * @constructor
 */
angular.module('neonDemo.controllers').controller('aggregationTableController', ['$scope', '$timeout', function($scope, $timeout) {
    // Unique field name used for the SlickGrid column containing the URLs for the external apps.
    // This name should be one that is highly unlikely to be a column name in a real database.
    var EXTERNAL_APP_FIELD_NAME = "neonExternalApps";

    $scope.active.aggregation = $scope.bindings.aggregationType || "count";
    $scope.active.aggregationField = {};
    $scope.active.groupField = {};
    $scope.active.limit = $scope.bindings.limit || 100;

    var handleRowClick = function(cell) {
        if($scope.active.gridOptions.api.getSelectedNodes()[0] && $scope.active.gridOptions.api.getSelectedNodes()[0].id === cell.rowIndex) {
            $scope.active.gridOptions.api.deselectIndex(cell.rowIndex);
        } else {
            $scope.active.gridOptions.api.selectIndex(cell.rowIndex, false);
        }

        $scope.filter = cell.node.data[$scope.active.groupField.columnName];
        selectRow($scope.filter);
        $scope.functions.updateNeonFilter();
    };

    $scope.active.gridOptions = {
        columnDefs: [],
        rowData: [],
        enableColResize: true,
        enableSorting: true,
        showToolPanel: false,
        toolPanelSuppressPivot: true,
        toolPanelSuppressValues: true,
        suppressRowClickSelection: true
    };

    $scope.functions.onUpdateFields = function() {
        $scope.active.groupField = $scope.functions.findFieldObject("groupField", neonMappings.AGGREGATE);
        $scope.active.aggregationField = $scope.functions.findFieldObject("aggregationField");
    };

    $scope.functions.onChangeOption = function() {
        updateColumns();
    };

    var updateColumns = function() {
        var columnDefinitions = [];

        if($scope.functions.areExternalServicesActive()) {
            var externalAppColumn = {
                field: EXTERNAL_APP_FIELD_NAME,
                headerName: "",
                suppressSizeToFit: false,
                cellClass: 'centered',
                width: 30
            };

            columnDefinitions.push(externalAppColumn);
        }

        columnDefinitions.push({
            headerName: $scope.active.groupField.prettyName,
            field: $scope.active.groupField.columnName,
            suppressSizeToFit: false,
            onCellClicked: handleRowClick
        });

        var columnName = $scope.active.aggregation;
        if($scope.active.aggregation !== 'count') {
            columnName += ' ' + $scope.active.aggregationField.prettyName;
        }

        columnDefinitions.push({
            headerName: columnName,
            field: $scope.active.aggregation,
            suppressSizeToFit: false,
            onCellClicked: handleRowClick
        });

        $scope.active.gridOptions.api.setColumnDefs(columnDefinitions);
        $scope.active.gridOptions.api.sizeColumnsToFit();
    };

    $scope.functions.areDataFieldsValid = function() {
        return $scope.functions.isFieldValid($scope.active.groupField) && ($scope.active.aggregation === "count" || $scope.functions.isFieldValid($scope.active.aggregationField));
    };

    $scope.functions.createNeonQueryWhereClause = function() {
        return neon.query.where($scope.active.groupField.columnName, "!=", null);
    };

    $scope.functions.addToQuery = function(query) {
        if($scope.functions.isFilterSet()) {
            var filterClause = $scope.functions.createNeonFilterClause({
                database: $scope.active.database.name,
                table: $scope.active.table.name
            }, $scope.active.groupField.columnName);
            query.ignoreFilters([$scope.functions.getFilterKey(filterClause)]);
        }

        if($scope.active.aggregation === "count") {
            query.aggregate(neon.query.COUNT, '*', 'count');
            query.sortBy('count', neon.query.DESCENDING);
        }

        if($scope.active.aggregation === "min") {
            query.aggregate(neon.query.MIN, $scope.active.aggregationField.columnName, $scope.active.aggregation);
            query.sortBy($scope.active.aggregation, neon.query.ASCENDING);
        }

        if($scope.active.aggregation === "max") {
            query.aggregate(neon.query.MAX, $scope.active.aggregationField.columnName, $scope.active.aggregation);
            query.sortBy($scope.active.aggregation, neon.query.DESCENDING);
        }

        if($scope.active.limit) {
            query.limit($scope.active.limit);
        }

        return query.groupBy($scope.active.groupField.columnName);
    };

    $scope.functions.updateData = function(data) {
        var tableData = data || [];

        if($scope.functions.areExternalServicesActive()) {
            tableData = addExternalLinksToColumnData(tableData);
        }

        tableData = _.map(tableData, function(row) {
            delete row._id;
            return row;
        });

        $scope.active.showTooMuchDataError = false;
        $scope.active.dataLength = tableData.length;
        $scope.active.queryLimit = $scope.active.limit;
        $scope.active.gridOptions.api.setRowData(tableData);

        if($scope.functions.isFilterSet() && tableData.length) {
            selectRow($scope.filter);
        }
    };

    /**
     * Selects the row in the table containing the given value in the global group field.
     * @param {String} value
     * @method selectRow
     * @private
     */
    var selectRow = function(value) {
        var selected = _.findWhere($scope.active.gridOptions.api.getRenderedNodes(), function(node) {
            return node.data[$scope.active.groupField.columnName] === value;
        });
        if(selected) {
            $scope.active.gridOptions.api.selectNode(selected);
        }
    };

    $scope.functions.isFilterSet = function() {
        return $scope.filter;
    };

    $scope.functions.getFilterFields = function() {
        return [$scope.active.groupField];
    };

    $scope.functions.createNeonFilterClause = function(databaseAndTableName, fieldName) {
        return neon.query.where(fieldName, "=", $scope.filter);
    };

    $scope.functions.createFilterTrayText = function() {
        return $scope.filter ? $scope.active.groupField.columnName + " = " + $scope.filter : "";
    };

    $scope.functions.updateFilterValues = function(neonFilter) {
        if($scope.functions.getNumberOfFilterClauses(neonFilter) === 1) {
            $scope.filter = neonFilter.filter.whereClause.rhs;
            selectRow($scope.filter);
        }
    };

    $scope.functions.removeFilterValues = function() {
        $scope.filter = undefined;
        $scope.active.gridOptions.api.deselectAll();
    };

    /**
     * Removes the filter set on the given value.
     * @param {String} value
     * @method removeFilter
     */
    $scope.removeFilter = function(value) {
        $scope.functions.removeNeonFilter();
    };

    $scope.functions.onError = function(response, errorCodes) {
        if(response.responseJSON.error === errorCodes.TOO_MUCH_DATA_ERROR) {
            $scope.$apply(function() {
                $scope.active.showTooMuchDataError = true;
            });
        }
    };

    $scope.functions.onThemeChanged = function(theme) {
        $scope.active.themeType = theme.type;
        return true;
    };

    $scope.functions.createExportDataObject = function(exportId, query) {
        var finalObject = {
            name: "Aggregation_Table",
            data: [{
                query: query,
                name: "aggregationTable-" + exportId,
                fields: [],
                ignoreFilters: query.ignoreFilters_,
                selectionOnly: query.selectionOnly_,
                ignoredFilterIds: query.ignoredFilterIds_,
                type: "query"
            }]
        };
        finalObject.data[0].fields.push({
            query: (query.groupByClauses[0]).field,
            pretty: (query.groupByClauses[0]).field
        });
        var op = '';
        if($scope.active.aggregation === 'min') {
            op = 'Min of ';
        } else if($scope.active.aggregation === 'max') {
            op = 'Max of ';
        }
        finalObject.data[0].fields.push({
            query: (query.aggregates[0]).name,
            pretty: op + (query.aggregates[0]).name
        });
        return finalObject;
    };

    /**
     * Creates and adds the external links to the given data and returns the data.
     * @param {Array} data
     * @method addExternalLinksToColumnData
     * @private
     * @return {Array}
     */
    var addExternalLinksToColumnData = function(data) {
        var buttons = $scope.functions.createLinkButtons($scope.active.groupField, data);
        data.forEach(function(row, index) {
            row[EXTERNAL_APP_FIELD_NAME] = buttons[index];
        });
        return data;
    };

    $scope.handleChangeGroupField = function() {
        $scope.functions.logChangeAndUpdate("groupField", $scope.active.groupField.columnName);
    };

    $scope.handleChangeAggregation = function() {
        $scope.functions.logChangeAndUpdate("aggregation", $scope.active.aggregation);
    };

    $scope.handleChangeAggregationField = function() {
        $scope.functions.logChangeAndUpdate("aggregationField", $scope.active.aggregationField.columnName);
    };

    $scope.handleChangeLimit = function() {
        $scope.functions.logChangeAndUpdate("limit", $scope.active.limit, "button");
    };

    $scope.functions.createMenuText = function() {
        if($scope.active.showTooMuchDataError) {
            return "Error";
        }
        return ($scope.active.dataLength >= $scope.active.queryLimit ? "Limited to " : "") + ($scope.active.dataLength || "No") + " Groups";
    };

    $scope.functions.showMenuText = function() {
        return true;
    };

    $scope.getFilterData = function() {
        return $scope.filter ? [$scope.filter] : [];
    };

    $scope.createFilterDesc = function(value) {
        return $scope.active.groupField.columnName + " = " + value;
    };

    $scope.functions.hideHeaders = function() {
        return !$scope.functions.isFilterSet() && !$scope.active.showTooMuchDataError;
    };

    $scope.functions.addToBindings = function(bindings) {
        bindings.aggregationType = $scope.active.aggregation || undefined;
        var hasAggField = $scope.active.aggregation && $scope.active.aggregation !== 'count' && $scope.functions.isFieldValid($scope.active.aggregationField);
        bindings.aggregationField = hasAggField ? $scope.active.aggregationField.columnName : undefined;
        bindings.groupField = $scope.functions.isFieldValid($scope.active.groupField) ? $scope.active.groupField.columnName : undefined;
        bindings.limit = $scope.active.limit;
        return bindings;
    };

    $scope.functions.onResize = function() {
        // Force the grid to update its size so that when we tell it to calculate the column
        // widths it is using an up-to-date width.
        $scope.active.gridOptions.api.doLayout();
        $scope.active.gridOptions.api.sizeColumnsToFit();
    };
}]);
<commit_msg>NEON-1296 Make aggregation table escape special characters<commit_after>'use strict';

/*
 * Copyright 2016 Next Century Corporation
 * 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.
 *
 */

/**
 * This visualization shows aggregated data in a table.
 * @namespace neonDemo.controllers
 * @class aggregationTableController
 * @constructor
 */
angular.module('neonDemo.controllers').controller('aggregationTableController', ['$scope', '$timeout', function($scope, $timeout) {
    // Unique field name used for the SlickGrid column containing the URLs for the external apps.
    // This name should be one that is highly unlikely to be a column name in a real database.
    var EXTERNAL_APP_FIELD_NAME = "neonExternalApps";

    $scope.active.aggregation = $scope.bindings.aggregationType || "count";
    $scope.active.aggregationField = {};
    $scope.active.groupField = {};
    $scope.active.limit = $scope.bindings.limit || 100;

    var handleRowClick = function(cell) {
        if($scope.active.gridOptions.api.getSelectedNodes()[0] && $scope.active.gridOptions.api.getSelectedNodes()[0].id === cell.rowIndex) {
            $scope.active.gridOptions.api.deselectIndex(cell.rowIndex);
        } else {
            $scope.active.gridOptions.api.selectIndex(cell.rowIndex, false);
        }

        // The data in the grid has been HTML-escaped, so unescape it before creating a filter from it.
        $scope.filter = _.unescape(cell.node.data[$scope.active.groupField.columnName]);
        selectRow($scope.filter);
        $scope.functions.updateNeonFilter();
    };

    $scope.active.gridOptions = {
        columnDefs: [],
        rowData: [],
        enableColResize: true,
        enableSorting: true,
        showToolPanel: false,
        toolPanelSuppressPivot: true,
        toolPanelSuppressValues: true,
        suppressRowClickSelection: true
    };

    $scope.functions.onUpdateFields = function() {
        $scope.active.groupField = $scope.functions.findFieldObject("groupField", neonMappings.AGGREGATE);
        $scope.active.aggregationField = $scope.functions.findFieldObject("aggregationField");
    };

    $scope.functions.onChangeOption = function() {
        updateColumns();
    };

    var updateColumns = function() {
        var columnDefinitions = [];

        if($scope.functions.areExternalServicesActive()) {
            var externalAppColumn = {
                field: EXTERNAL_APP_FIELD_NAME,
                headerName: "",
                suppressSizeToFit: false,
                cellClass: 'centered',
                width: 30
            };

            columnDefinitions.push(externalAppColumn);
        }

        columnDefinitions.push({
            headerName: $scope.active.groupField.prettyName,
            field: $scope.active.groupField.columnName,
            suppressSizeToFit: false,
            onCellClicked: handleRowClick
        });

        var columnName = $scope.active.aggregation;
        if($scope.active.aggregation !== 'count') {
            columnName += ' ' + $scope.active.aggregationField.prettyName;
        }

        columnDefinitions.push({
            headerName: columnName,
            field: $scope.active.aggregation,
            suppressSizeToFit: false,
            onCellClicked: handleRowClick
        });

        $scope.active.gridOptions.api.setColumnDefs(columnDefinitions);
        $scope.active.gridOptions.api.sizeColumnsToFit();
    };

    $scope.functions.areDataFieldsValid = function() {
        return $scope.functions.isFieldValid($scope.active.groupField) && ($scope.active.aggregation === "count" || $scope.functions.isFieldValid($scope.active.aggregationField));
    };

    $scope.functions.createNeonQueryWhereClause = function() {
        return neon.query.where($scope.active.groupField.columnName, "!=", null);
    };

    $scope.functions.addToQuery = function(query) {
        if($scope.functions.isFilterSet()) {
            var filterClause = $scope.functions.createNeonFilterClause({
                database: $scope.active.database.name,
                table: $scope.active.table.name
            }, $scope.active.groupField.columnName);
            query.ignoreFilters([$scope.functions.getFilterKey(filterClause)]);
        }

        if($scope.active.aggregation === "count") {
            query.aggregate(neon.query.COUNT, '*', 'count');
            query.sortBy('count', neon.query.DESCENDING);
        }

        if($scope.active.aggregation === "min") {
            query.aggregate(neon.query.MIN, $scope.active.aggregationField.columnName, $scope.active.aggregation);
            query.sortBy($scope.active.aggregation, neon.query.ASCENDING);
        }

        if($scope.active.aggregation === "max") {
            query.aggregate(neon.query.MAX, $scope.active.aggregationField.columnName, $scope.active.aggregation);
            query.sortBy($scope.active.aggregation, neon.query.DESCENDING);
        }

        if($scope.active.limit) {
            query.limit($scope.active.limit);
        }

        return query.groupBy($scope.active.groupField.columnName);
    };

    $scope.functions.updateData = function(data) {
        var tableData = data || [];

        // Escape the data so that special characters render correctly
        tableData = neon.helpers.escapeDataRecursively(tableData);

        if($scope.functions.areExternalServicesActive()) {
            tableData = addExternalLinksToColumnData(tableData);
        }

        tableData = _.map(tableData, function(row) {
            delete row._id;
            return row;
        });

        $scope.active.showTooMuchDataError = false;
        $scope.active.dataLength = tableData.length;
        $scope.active.queryLimit = $scope.active.limit;
        $scope.active.gridOptions.api.setRowData(tableData);

        if($scope.functions.isFilterSet() && tableData.length) {
            selectRow($scope.filter);
        }
    };

    /**
     * Selects the row in the table containing the given value in the global group field.
     * @param {String} value
     * @method selectRow
     * @private
     */
    var selectRow = function(value) {
        // The data in the grid has been HTML-escaped, so escape the value so that we can compare
        // apples to apples.
        var escapedValue = _.escape(value);
        var selected = _.findWhere($scope.active.gridOptions.api.getRenderedNodes(), function(node) {
            return node.data[$scope.active.groupField.columnName] === escapedValue;
        });
        if(selected) {
            $scope.active.gridOptions.api.selectNode(selected);
        }
    };

    $scope.functions.isFilterSet = function() {
        return $scope.filter;
    };

    $scope.functions.getFilterFields = function() {
        return [$scope.active.groupField];
    };

    $scope.functions.createNeonFilterClause = function(databaseAndTableName, fieldName) {
        return neon.query.where(fieldName, "=", $scope.filter);
    };

    $scope.functions.createFilterTrayText = function() {
        return $scope.filter ? $scope.active.groupField.columnName + " = " + $scope.filter : "";
    };

    $scope.functions.updateFilterValues = function(neonFilter) {
        if($scope.functions.getNumberOfFilterClauses(neonFilter) === 1) {
            $scope.filter = neonFilter.filter.whereClause.rhs;
            selectRow($scope.filter);
        }
    };

    $scope.functions.removeFilterValues = function() {
        $scope.filter = undefined;
        $scope.active.gridOptions.api.deselectAll();
    };

    /**
     * Removes the filter set on the given value.
     * @param {String} value
     * @method removeFilter
     */
    $scope.removeFilter = function(value) {
        $scope.functions.removeNeonFilter();
    };

    $scope.functions.onError = function(response, errorCodes) {
        if(response.responseJSON.error === errorCodes.TOO_MUCH_DATA_ERROR) {
            $scope.$apply(function() {
                $scope.active.showTooMuchDataError = true;
            });
        }
    };

    $scope.functions.onThemeChanged = function(theme) {
        $scope.active.themeType = theme.type;
        return true;
    };

    $scope.functions.createExportDataObject = function(exportId, query) {
        var finalObject = {
            name: "Aggregation_Table",
            data: [{
                query: query,
                name: "aggregationTable-" + exportId,
                fields: [],
                ignoreFilters: query.ignoreFilters_,
                selectionOnly: query.selectionOnly_,
                ignoredFilterIds: query.ignoredFilterIds_,
                type: "query"
            }]
        };
        finalObject.data[0].fields.push({
            query: (query.groupByClauses[0]).field,
            pretty: (query.groupByClauses[0]).field
        });
        var op = '';
        if($scope.active.aggregation === 'min') {
            op = 'Min of ';
        } else if($scope.active.aggregation === 'max') {
            op = 'Max of ';
        }
        finalObject.data[0].fields.push({
            query: (query.aggregates[0]).name,
            pretty: op + (query.aggregates[0]).name
        });
        return finalObject;
    };

    /**
     * Creates and adds the external links to the given data and returns the data.
     * @param {Array} data
     * @method addExternalLinksToColumnData
     * @private
     * @return {Array}
     */
    var addExternalLinksToColumnData = function(data) {
        var buttons = $scope.functions.createLinkButtons($scope.active.groupField, data);
        data.forEach(function(row, index) {
            row[EXTERNAL_APP_FIELD_NAME] = buttons[index];
        });
        return data;
    };

    $scope.handleChangeGroupField = function() {
        $scope.functions.logChangeAndUpdate("groupField", $scope.active.groupField.columnName);
    };

    $scope.handleChangeAggregation = function() {
        $scope.functions.logChangeAndUpdate("aggregation", $scope.active.aggregation);
    };

    $scope.handleChangeAggregationField = function() {
        $scope.functions.logChangeAndUpdate("aggregationField", $scope.active.aggregationField.columnName);
    };

    $scope.handleChangeLimit = function() {
        $scope.functions.logChangeAndUpdate("limit", $scope.active.limit, "button");
    };

    $scope.functions.createMenuText = function() {
        if($scope.active.showTooMuchDataError) {
            return "Error";
        }
        return ($scope.active.dataLength >= $scope.active.queryLimit ? "Limited to " : "") + ($scope.active.dataLength || "No") + " Groups";
    };

    $scope.functions.showMenuText = function() {
        return true;
    };

    $scope.getFilterData = function() {
        return $scope.filter ? [$scope.filter] : [];
    };

    $scope.createFilterDesc = function(value) {
        return $scope.active.groupField.columnName + " = " + value;
    };

    $scope.functions.hideHeaders = function() {
        return !$scope.functions.isFilterSet() && !$scope.active.showTooMuchDataError;
    };

    $scope.functions.addToBindings = function(bindings) {
        bindings.aggregationType = $scope.active.aggregation || undefined;
        var hasAggField = $scope.active.aggregation && $scope.active.aggregation !== 'count' && $scope.functions.isFieldValid($scope.active.aggregationField);
        bindings.aggregationField = hasAggField ? $scope.active.aggregationField.columnName : undefined;
        bindings.groupField = $scope.functions.isFieldValid($scope.active.groupField) ? $scope.active.groupField.columnName : undefined;
        bindings.limit = $scope.active.limit;
        return bindings;
    };

    $scope.functions.onResize = function() {
        // Force the grid to update its size so that when we tell it to calculate the column
        // widths it is using an up-to-date width.
        $scope.active.gridOptions.api.doLayout();
        $scope.active.gridOptions.api.sizeColumnsToFit();
    };
}]);
<|endoftext|>