<commit_before>
// Logic to import file, parse it, then append a table
//  to the html document.

var data   = [];
var errors = [];
var errorGroups = [];

var monthNames = ["January", "February", "March", "April", "May", "June",
  "July", "August", "September", "October", "November", "December"
];

// Global graphing variables

var margin = {top: 20, right: 40, bottom: 60, left: 60},
    width  = 450 - margin.left - margin.right,
    height = 350 - margin.top - margin.bottom;

var x = d3.time.scale()
    .range([0, width]);

var y = d3.scale.linear()
    .range([height, 0]);

var color = d3.scale.ordinal()
  .domain(["Not Flagged", "Flagged" ])
  .range(["#000000", "#969696" ]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .ticks(4);

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .ticks(6);

// Loop through each row in the EIM template
//  to find errors
var findErrors = function(data) {

    var i = 0;

    for (i; i<data.length; i++) {

        var dateOfCollection = new Date(data[i]["Field_Collection_Start_Date"]);

        var rowName = ["Site " + data[i]["Location_ID"],
            monthNames[dateOfCollection.getMonth()] + " " + dateOfCollection.getDay() + ", " + dateOfCollection.getFullYear(),
            data[i]["Fraction_Analyzed"] + " " + data[i]["Result_Parameter_Name"] + " in " + data[i]["Sample_Matrix"]
            ].join(" | ");

        // Check for whole-row errors; returns a list [property, error] string pairs.
        //  Example: [["Sample_ID", "Missing Sample ID"],["Field_Collection_Upper_Depth", "Value out of range"]]
        var rowErrs = eimRowValidate(data[i]);

        if (rowErrs.length > 0) {

            for (var j=0; j<rowErrs.length; j++) {

                var tableLocation = "Row " + (i+1) + ", Column " + rowErrs[j][0] + "";

                errors.push({
                    "fieldError":    rowErrs[j][1],
                    "property":      rowErrs[j][0],
                    "tableLocation": tableLocation,
                    "rowName":       rowName
                });
            };
        };

        // Loop through each column in the row
        for (var property in data[i]) {
            if (data[i].hasOwnProperty(property)) {

                // Check a field in a row for errors (see eimValidator.js)
                var fieldError = eimValidValue(property, data[i][property]);

                if (fieldError.length > 0) {

                    var tableLocation = "Row " + (i+1) + ", Column " + property + "";
                    errors.push({
                        "fieldError":    fieldError,
                        "property":      property,
                        "tableLocation": tableLocation,
                        "rowName":       rowName
                    });
                };
            };
        };

        errorGroups = _.chain(errors)
            .countBy("property")
            .pairs()
            .sortBy(0)
            .value();

    };
}

//  Currently relies on global variables;  not great,
//    but a lot easier to debug.
var drawErrors = function() {

    if (data == false) {
        alert("Upload an EIM file to examine first!");
    } else {

        // Clear out the main data panel and add new containers
        $("#sidebar").empty();
        $("#uploadedData").empty();
        $("li#menu-summary").removeClass("pure-menu-selected");

        $("#uploadedData").append("
Details
");
        $("#uploadedData").append("

");

        $("li#menu-errors").addClass("pure-menu-selected");

        // Loop through every identified error
        for (var i=0; i<errors.length; i++) {

            $("#errorList").append("
"
                + errors[i]["tableLocation"] + " - "
                + errors[i]["fieldError"]    + "
"
                + errors[i]["rowName"]       + "

"
            );
        };

        // Update the sidebar

        $("#sidebar").append("
Summary</h2");
        $("#sidebar").append("

");

        $("#errorSummary").append("
" + errors.length + " total errors.
");
        $("#errorSummary").append("
" + data.data.length + " total rows.
");

        $("#sidebar").append("
Error Types
");
        $("#sidebar").append("

");

        for (var i=0; i<errorGroups.length; i++) {
            $("#errorGroups").append("
" +
                errorGroups[i][0].replace(/_/g, " ") + " (" +
                errorGroups[i][1] + ")
"
            );
        };
    };
};

var summarizeData = function() {

    if (data == false) {
        alert("Upload an EIM file to examine first!");
    } else {
        // State changes - empty out divs & change menu
        $("#uploadedData").empty();
        $("#sidebar").empty();
        $("li#menu-errors").removeClass("pure-menu-selected");

        $("#sidebar").append("
Parameters</h2");
        $("#sidebar").append("

");

        $("#uploadedData").append("
Details
"
            + "

"
            + "

Select a parameter to the left.

"

        );

        $("li#menu-summary").addClass("pure-menu-selected");

        var parameterCounts = _.chain(data.data).countBy("fullParameter")
            .pairs()
            .sortBy(0)
            .value();

        for (var i=0; i<parameterCounts.length; i++) {

            $("ul#parameters").append("
"
                + parameterCounts[i][0] + " ("
                + parameterCounts[i][1] + ")
"
            );
        };

        // Set up the graph
        var svg = d3.select("div#summaryGraph").append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
          .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
            .attr("class", "graphingArea");

        svg.append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(0," + height + ")");

        svg.append("g")
          .attr("class", "y axis");

        $("ul#parameters li a").click(function() {
            displayParameterDetails(this);
        });
    };
}

var displayParameterDetails = function(elem) {

    $("div#details").empty();
    $("div#uploadedData h2").empty();

    var theParam = $(elem).attr("data-parameterName");
    var filteredData = _.filter(data.data, 'fullParameter', theParam);

    var getVal = function(result) {
        return parseFloat(result.Result_Value);
    }

    var max = _.max(filteredData, getVal).Result_Value;
    var min = _.min(filteredData, getVal).Result_Value;

    $("div#uploadedData h2").append("Details of " + theParam);
    $("div#details").append("
Range is from "
        + min + " to "
        + max + "
."
    );

    // Fill in the graph

    var svg = d3.select("div#summaryGraph svg g.graphingArea");

    x.domain(d3.extent(filteredData, function(d) { return d.dateJS })).nice();
    y.domain(d3.extent(filteredData, function(d) { return d.value  })).nice();

    d3.select("svg g.x.axis").call(xAxis);
    d3.select("svg g.y.axis").call(yAxis);

    // Data join & update
    var dots = svg.selectAll(".dot")
        .data(filteredData, function(d) { return d.id; })
        .attr("r", 3.5)
        .attr("cx", function(d) { return x(d.dateJS); })
        .attr("cy", function(d) { return y(d.value); });

    // Enter
    dots.enter().append("circle")
        .attr("class", "dot")
        .attr("r", 3.5)
        .attr("cx", function(d) { return x(d.dateJS); })
        .attr("cy", function(d) { return y(d.value); })
        .style("fill-opacity", 1e-6)
        .style("fill", function(d) { return color(d.qualifier); })
      .transition()
        .duration(500)
        .style("fill-opacity", 1);

    // Exit
    dots.exit()
      .transition()
        .style("fill-opacity", 1e-6)
        .remove();

    var legend = svg.selectAll(".legend")
        .data(color.domain())
      .enter().append("g")
        .attr("class", "legend")
        .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

    legend.append("circle")
        .attr("cx", width - 18)
        .attr("cy", 10)
        .attr("r", 3.5)
        .style("fill", color);

    legend.append("text")
        .attr("x", width - 24)
        .attr("y", 9)
        .attr("dy", ".35em")
        .style("text-anchor", "end")
        .text(function(d) { return d; });

};

var parseCSV = function(){

    $("#uploadedData").empty();
    $("#sidebar").empty();

    var fileInput = document.querySelector("#fileItem");
    var file = fileInput.files[0];

    Papa.parse(file, {

        header: true,

        error:  function(err, file) {
            console.log("Error:", err, file);
        },

        complete: function(results) {

            errors  = [];
            data    = results;
            i       = 0;

            // Customize attributes
            data.data.forEach( function(d) {
                d.value     = parseFloat(d.Result_Value);

                try {
                    var theDate = d.Field_Collection_Start_Date.split("/");
                    var theTime = d.Field_Collection_Start_Time.split(":");
                    d.dateJS    = new Date(
                        theDate[2].slice(0, 4),
                        parseInt(theDate[0])-1,
                        theDate[1],
                        theTime[0],
                        theTime[1],
                        theTime[2]
                    );
                } catch(err) {
                    d.dateJS = "";
                };

                d.fullParameter = d.Fraction_Analyzed + " "
                    + d.Result_Parameter_Name + " in "
                    + d.Sample_Matrix + " ("
                    + d.Result_Value_Units + ")";

                try {
                    var flag = d.Result_Data_Qualifier.toUpperCase();

                    if (!!flag) {
                        d.qualifier = "Flagged";
                    } else {
                        d.qualifier = "Not Flagged";
                    };
                } catch(err) {
                    d.qualifier = "Flagged";
                }

                d.id = i;
                i++;
            });

            // Go through each row & check for errors
            findErrors(results.data);
            drawErrors(errors);

        }
    });
};

// Register onclick events & generally set up the document
$("#fileItem").change(parseCSV);
$("#menu-summary a").click(summarizeData);
$("#menu-errors a").click(drawErrors);

<commit_msg>Improved legend placement in summary plots.<commit_after>
// Logic to import file, parse it, then append a table
//  to the html document.

var data   = [];
var errors = [];
var errorGroups = [];

var monthNames = ["January", "February", "March", "April", "May", "June",
  "July", "August", "September", "October", "November", "December"
];

// Global graphing variables

var margin = {top: 20, right: 40, bottom: 60, left: 60},
    width  = 450 - margin.left - margin.right,
    height = 350 - margin.top - margin.bottom;

var x = d3.time.scale()
    .range([0, width]);

var y = d3.scale.linear()
    .range([height, 0]);

var color = d3.scale.ordinal()
  .domain(["Not Flagged", "J", "U", "Other" ])
  .range(["#000000", "#1b9e77", "#d95f02", "#7570b3" ]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .ticks(4);

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .ticks(6);

// Loop through each row in the EIM template
//  to find errors
var findErrors = function(data) {

    var i = 0;

    for (i; i<data.length; i++) {

        var dateOfCollection = new Date(data[i]["Field_Collection_Start_Date"]);

        var rowName = ["Site " + data[i]["Location_ID"],
            monthNames[dateOfCollection.getMonth()] + " " + dateOfCollection.getDay() + ", " + dateOfCollection.getFullYear(),
            data[i]["Fraction_Analyzed"] + " " + data[i]["Result_Parameter_Name"] + " in " + data[i]["Sample_Matrix"]
            ].join(" | ");

        // Check for whole-row errors; returns a list [property, error] string pairs.
        //  Example: [["Sample_ID", "Missing Sample ID"],["Field_Collection_Upper_Depth", "Value out of range"]]
        var rowErrs = eimRowValidate(data[i]);

        if (rowErrs.length > 0) {

            for (var j=0; j<rowErrs.length; j++) {

                var tableLocation = "Row " + (i+1) + ", Column " + rowErrs[j][0] + "";

                errors.push({
                    "fieldError":    rowErrs[j][1],
                    "property":      rowErrs[j][0],
                    "tableLocation": tableLocation,
                    "rowName":       rowName
                });
            };
        };

        // Loop through each column in the row
        for (var property in data[i]) {
            if (data[i].hasOwnProperty(property)) {

                // Check a field in a row for errors (see eimValidator.js)
                var fieldError = eimValidValue(property, data[i][property]);

                if (fieldError.length > 0) {

                    var tableLocation = "Row " + (i+1) + ", Column " + property + "";
                    errors.push({
                        "fieldError":    fieldError,
                        "property":      property,
                        "tableLocation": tableLocation,
                        "rowName":       rowName
                    });
                };
            };
        };

        errorGroups = _.chain(errors)
            .countBy("property")
            .pairs()
            .sortBy(0)
            .value();

    };
}

//  Currently relies on global variables;  not great,
//    but a lot easier to debug.
var drawErrors = function() {

    if (data == false) {
        alert("Upload an EIM file to examine first!");
    } else {

        // Clear out the main data panel and add new containers
        $("#sidebar").empty();
        $("#uploadedData").empty();
        $("li#menu-summary").removeClass("pure-menu-selected");

        $("#uploadedData").append("
Details
");
        $("#uploadedData").append("

");

        $("li#menu-errors").addClass("pure-menu-selected");

        // Loop through every identified error
        for (var i=0; i<errors.length; i++) {

            $("#errorList").append("
"
                + errors[i]["tableLocation"] + " - "
                + errors[i]["fieldError"]    + "
"
                + errors[i]["rowName"]       + "

"
            );
        };

        // Update the sidebar

        $("#sidebar").append("
Summary</h2");
        $("#sidebar").append("

");

        $("#errorSummary").append("
" + errors.length + " total errors.
");
        $("#errorSummary").append("
" + data.data.length + " total rows.
");

        $("#sidebar").append("
Error Types
");
        $("#sidebar").append("

");

        for (var i=0; i<errorGroups.length; i++) {
            $("#errorGroups").append("
" +
                errorGroups[i][0].replace(/_/g, " ") + " (" +
                errorGroups[i][1] + ")
"
            );
        };
    };
};

var summarizeData = function() {

    if (data == false) {
        alert("Upload an EIM file to examine first!");
    } else {
        // State changes - empty out divs & change menu
        $("#uploadedData").empty();
        $("#sidebar").empty();
        $("li#menu-errors").removeClass("pure-menu-selected");

        $("#sidebar").append("
Parameters</h2");
        $("#sidebar").append("

");

        $("#uploadedData").append("
Details
"
            + "

"
            + "

Select a parameter to the left.

"

        );

        $("li#menu-summary").addClass("pure-menu-selected");

        var parameterCounts = _.chain(data.data).countBy("fullParameter")
            .pairs()
            .sortBy(0)
            .value();

        for (var i=0; i<parameterCounts.length; i++) {

            $("ul#parameters").append("
"
                + parameterCounts[i][0] + " ("
                + parameterCounts[i][1] + ")
"
            );
        };

        // Set up the graph
        var svg = d3.select("div#summaryGraph").append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
          .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
            .attr("class", "graphingArea");

        svg.append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(0," + height + ")");

        svg.append("g")
          .attr("class", "y axis");

        $("ul#parameters li a").click(function() {
            displayParameterDetails(this);
        });
    };
}

var displayParameterDetails = function(elem) {

    $("div#details").empty();
    $("div#uploadedData h2").empty();

    var theParam = $(elem).attr("data-parameterName");
    var filteredData = _.filter(data.data, 'fullParameter', theParam);

    var getVal = function(result) {
        return parseFloat(result.Result_Value);
    }

    var max = _.max(filteredData, getVal).Result_Value;
    var min = _.min(filteredData, getVal).Result_Value;

    $("div#uploadedData h2").append("Details of " + theParam);
    $("div#details").append("
Range is from "
        + min + " to "
        + max + "
."
    );

    // Fill in the graph

    var svg = d3.select("div#summaryGraph svg g.graphingArea");

    x.domain(d3.extent(filteredData, function(d) { return d.dateJS })).nice();
    y.domain(d3.extent(filteredData, function(d) { return d.value  })).nice();

    d3.select("svg g.x.axis").call(xAxis);
    d3.select("svg g.y.axis").call(yAxis);

    // Data join & update
    var dots = svg.selectAll(".dot")
        .data(filteredData, function(d) { return d.id; })
        .attr("r", 3.5)
        .attr("cx", function(d) { return x(d.dateJS); })
        .attr("cy", function(d) { return y(d.value); });

    // Enter
    dots.enter().append("circle")
        .attr("class", "dot")
        .attr("r", 3.5)
        .attr("cx", function(d) { return x(d.dateJS); })
        .attr("cy", function(d) { return y(d.value); })
        .style("fill-opacity", 1e-6)
        .style("fill", function(d) { return color(d.qualifier); })
      .transition()
        .duration(500)
        .style("fill-opacity", 1);

    // Exit
    dots.exit()
      .transition()
        .style("fill-opacity", 1e-6)
        .remove();

    var legend = d3.select("div#summaryGraph svg").selectAll(".legend")
        .data(color.domain())
      .enter().append("g")
        .attr("class", "legend")
        .attr("transform", function(d, i) {
            var left = ((width + margin.left + margin.right) / 2) * -1;
            var xLocation = left + (75 * i) + 20;

            return "translate(" + xLocation + "," + (height + margin.top + 35) + ")";
        });

    legend.append("circle")
        .attr("cx", width - 18)
        .attr("cy", 10)
        .attr("r", 3.5)
        .style("fill", color);

    legend.append("text")
        .attr("x", width - 24)
        .attr("y", 9)
        .attr("dy", ".35em")
        .style("text-anchor", "end")
        .style("font-size", "0.8em")
        .style("fill", "gray")
        .text(function(d) { return d; });

};

var parseCSV = function(){

    $("#uploadedData").empty();
    $("#sidebar").empty();

    var fileInput = document.querySelector("#fileItem");
    var file = fileInput.files[0];

    Papa.parse(file, {

        header: true,

        error:  function(err, file) {
            console.log("Error:", err, file);
        },

        complete: function(results) {

            errors  = [];
            data    = results;
            i       = 0;

            // Customize attributes
            data.data.forEach( function(d) {
                d.value     = parseFloat(d.Result_Value);

                try {
                    var theDate = d.Field_Collection_Start_Date.split("/");
                    var theTime = d.Field_Collection_Start_Time.split(":");
                    d.dateJS    = new Date(
                        theDate[2].slice(0, 4),
                        parseInt(theDate[0])-1,
                        theDate[1],
                        theTime[0],
                        theTime[1],
                        theTime[2]
                    );
                } catch(err) {
                    d.dateJS = "";
                };

                d.fullParameter = d.Fraction_Analyzed + " "
                    + d.Result_Parameter_Name + " in "
                    + d.Sample_Matrix + " ("
                    + d.Result_Value_Units + ")";

                try {
                    var flag = d.Result_Data_Qualifier.toUpperCase();

                    if (flag == "J") {
                        d.qualifier = "J";
                    } else if (flag == "U") {
                        d.qualifier = "U";
                    } else if (!!flag) {
                        d.qualifier = "Other";
                    } else {
                        d.qualifier = "Not Flagged";
                    };

                    /*if (!!flag) {
                        d.qualifier = "Flagged";
                    } else {
                        d.qualifier = "Not Flagged";
                    };*/
                } catch(err) {
                    d.qualifier = "Flagged";
                }

                d.id = i;
                i++;
            });

            // Go through each row & check for errors
            findErrors(results.data);
            drawErrors(errors);

        }
    });
};

// Register onclick events & generally set up the document
$("#fileItem").change(parseCSV);
$("#menu-summary a").click(summarizeData);
$("#menu-errors a").click(drawErrors);

<|endoftext|>


<commit_before>globe.graphic = function() {

	var rawData = [];

	var opts = {
		lines: 12, // The number of lines to draw
		length: 11, // The length of each line
		width: 5, // The line thickness
		radius: 14, // The radius of the inner circle
		corners: 1, // Corner roundness (0..1)
		rotate: 0, // The rotation offset
		direction: 1, // 1: clockwise, -1: counterclockwise
		color: '#000', // #rgb or #rrggbb or array of colors
		speed: 1.5, // Rounds per second
		trail: 40, // Afterglow percentage
		shadow: false, // Whether to render a shadow
		hwaccel: true, // Whether to use hardware acceleration
		className: 'spinner', // The CSS class to assign to the spinner
		zIndex: 2e9, // The z-index (defaults to 2000000000)
		top: '50%', // Top position relative to parent in px
		left: '50%' // Left position relative to parent in px
	};
	var spinner = new Spinner(opts).spin($('.loading').get(0));

	// from http://api.jquery.com/jQuery.cssHooks/
	(function( $ ) {

		function styleSupport( prop ) {
			var vendorProp, supportedProp,
				capProp = prop.charAt( 0 ).toUpperCase() + prop.slice( 1 ),
				prefixes = [ "Moz", "Webkit", "O", "ms" ],
				div = document.createElement( "div" );

			if ( prop in div.style ) {
				supportedProp = prop;
			} else {
				for ( var i = 0; i < prefixes.length; i++ ) {
					vendorProp = prefixes[ i ] + capProp;
					if ( vendorProp in div.style ) {
						supportedProp = vendorProp;
						break;
					}
				}
			}

			div = null;
			$.support[ prop ] = supportedProp;
			return supportedProp;
		}

		var transform = styleSupport( "transform" );

		// Set cssHooks only for browsers that support a vendor-prefixed border radius
		if ( transform && transform !== "transform" ) {
			$.cssHooks.transform = {
				get: function( elem, computed, extra ) {
					return $.css( elem, transform );
				},
				set: function( elem, value) {
					elem.style[ transform ] = value;
				}
			};
		}

	})( jQuery );

	var master = $('#gf');
	var stories;
	var isLoaded = false;

	function loadMoreStories(id) {

		// this will hold all the html strings
		var html = [];

		// try finding the story with given id
		var story = $.grep(stories, function(element) {
			return element.id === id;
		});

		var count;

		// did we find a story?
		if (story && story.length) {
			var datum = story[0];
			datum.featured = true;
			html.push(window.JST['story.template'](datum));

			stories = $.grep(stories, function(element) {
				return element.id != id;
			});

			// and then show 3 more
			count = Math.min(3, stories.length);

		} else {
			// show 10 stories, or all, whichever is smaller
			count = Math.min(10, stories.length);
		}

		// process one story at a time
		while (count--) {
			html.push(window.JST['story.template'](stories.pop()));
		}

		// after first call, hide spinner, enable form
		if (!isLoaded) {
			spinner.stop();
			$('.loading').addClass('hidden');
			$('form', master).parent().removeClass('hidden');
			isLoaded = !isLoaded;
		}

		// add stories to dom
		$('.stories', master).append(html.join(''));

		setTimeout(function() {
			$('.story').removeClass('featured');
		}, 1000);

		// do we still have stories to show?
		if (stories.length) {
			$('button.loadMoreStories', master).removeClass('hidden');
			$('p.noMoreStories', master).addClass('hidden');
		} else {
			$('button.loadMoreStories', master).addClass('hidden');
			$('p.noMoreStories', master).removeClass('hidden');
		}

	}

	function slideStoriesDown() {
		var height = $('form', master).outerHeight();
		$('.stories', master).css('transform', 'translateY(' + height + 'px)');
		$('.storiesSpacer').height(height);
	}

	function slideStoriesUp() {
		$('.stories', master).css('transform', 'translateY(0px)');
		$('.storiesSpacer').height(0);
	}

	$('button.why', master).click(function(e) {

		e.preventDefault();

		slideStoriesDown();
		$('button.why', master).parent().find('.why,.title').toggleClass('hidden');

	});

	$('button.cancel').click(function(e) {

		e.preventDefault();

		// clear validation
		$('form', master).parent().find('.error').addClass('hidden');

		// clear form
		$('form input,textarea', master).val('');

	});

	$('button.cancel, button.title').click(function(e) {

		e.preventDefault();

		slideStoriesUp();
		$('button.why', master).parent().find('.why,.title').toggleClass('hidden');

	});

	$('button.loadMoreStories', master).click(loadMoreStories);

	var requiredFields = $('form', master).find('input, textarea').filter(function() {
		return $(this).parent().find('.required').length;
	});

	requiredFields.change(function() {

		// if this isn't empty, remove the error field
		if ($(this).val().length) {
			$(this).parent().find('.error').addClass('hidden');
		}

	});

	// check image size
	var imageInput = $('form', master).find('input[name="image"]');
	imageInput.change(function() {

		var field = $(this).get(0);
		if (window.FileReader && field.files && field.files[0] && field.files[0].size > 1000000) {
			$(this).parent().find('.error').removeClass('hidden');
		} else {
			$(this).parent().find('.error').addClass('hidden');
		}

	});

	$('button.submit', master).click(function(e) {

		e.preventDefault();

		$('form', master).ajaxSubmit({
			beforeSubmit: function(array, $form, options) {

				var isValid = true;

				requiredFields.each(function() {

					// if field is empty, show message and invalidate the entire form
					if (!$(this).val().length) {
						isValid = false;
						$(this).parent().find('.error').removeClass('hidden');
					}

				});

				// check image size
				if (!imageInput.parent().find('.error').hasClass('hidden')) {
					isValid = false;
				}

				return isValid;
			},
			data: {
				video_link: '',
				video_description: '',
				photo_description: imageInput.val().length ? $('form textarea', master).val() : '',
				email: '',
				type: imageInput.val().length ? 3 : 1,
				year: 2014
			},
			success: function() {
				slideStoriesUp();
				$('button.why', master).parent().find('.why,.title').addClass('hidden');
				$('button.why', master).parent().find('.thanks').removeClass('hidden');
			}
		});

	});

	master.on('click', '.twitter', function() {

		// get the id
		var id = $(this).parents('.story').data('id');

		// get the story
		var story = $.grep(rawData, function(element) {
			return element.id === id;
		})[0];

		var href = window.location.href.split('#')[0];

		var url = encodeURIComponent(href + '#' + id);
		var text = encodeURIComponent("Running this year's Boston marathon? Read " + story.name + "'s story at Why I Run via @BostonGlobe");

		var intent = 'https://twitter.com/intent/tweet?text=' + text + '&url=' + url;

		window.open(intent, '', 'width=550px,height=420px');

	});

	$.getJSON('http://www.boston.com/newsprojects/whyirun/get_stories_api.php?year=2014', function(json) {

		for (var i = 0; i < json.length; i++) {
			json[i].featured = false;
			rawData.push(json[i]);
		}

		stories = json.reverse();

		// is there a hash in the url?
		var id = window.location.hash && window.location.hash.length ?
			// yes - assume it's an id, load that one particular story
			+window.location.hash.substring(1) :
			// no
			null;

		loadMoreStories(id);

	});

};<commit_msg>fix #4: when clicking "tell us why you run" scroll the screen up a bit<commit_after>globe.graphic = function() {

	var rawData = [];

	var opts = {
		lines: 12, // The number of lines to draw
		length: 11, // The length of each line
		width: 5, // The line thickness
		radius: 14, // The radius of the inner circle
		corners: 1, // Corner roundness (0..1)
		rotate: 0, // The rotation offset
		direction: 1, // 1: clockwise, -1: counterclockwise
		color: '#000', // #rgb or #rrggbb or array of colors
		speed: 1.5, // Rounds per second
		trail: 40, // Afterglow percentage
		shadow: false, // Whether to render a shadow
		hwaccel: true, // Whether to use hardware acceleration
		className: 'spinner', // The CSS class to assign to the spinner
		zIndex: 2e9, // The z-index (defaults to 2000000000)
		top: '50%', // Top position relative to parent in px
		left: '50%' // Left position relative to parent in px
	};
	var spinner = new Spinner(opts).spin($('.loading').get(0));

	// from http://css-tricks.com/snippets/jquery/smooth-scrolling/
	function smoothScroll(target) {
		$('html,body').animate({
			scrollTop: target.offset().top - 20
		}, 1000);
	}

	// from http://api.jquery.com/jQuery.cssHooks/
	(function( $ ) {

		function styleSupport( prop ) {
			var vendorProp, supportedProp,
				capProp = prop.charAt( 0 ).toUpperCase() + prop.slice( 1 ),
				prefixes = [ "Moz", "Webkit", "O", "ms" ],
				div = document.createElement( "div" );

			if ( prop in div.style ) {
				supportedProp = prop;
			} else {
				for ( var i = 0; i < prefixes.length; i++ ) {
					vendorProp = prefixes[ i ] + capProp;
					if ( vendorProp in div.style ) {
						supportedProp = vendorProp;
						break;
					}
				}
			}

			div = null;
			$.support[ prop ] = supportedProp;
			return supportedProp;
		}

		var transform = styleSupport( "transform" );

		// Set cssHooks only for browsers that support a vendor-prefixed border radius
		if ( transform && transform !== "transform" ) {
			$.cssHooks.transform = {
				get: function( elem, computed, extra ) {
					return $.css( elem, transform );
				},
				set: function( elem, value) {
					elem.style[ transform ] = value;
				}
			};
		}

	})( jQuery );

	var master = $('#gf');
	var stories;
	var isLoaded = false;

	function loadMoreStories(id) {

		// this will hold all the html strings
		var html = [];

		// try finding the story with given id
		var story = $.grep(stories, function(element) {
			return element.id === id;
		});

		var count;

		// did we find a story?
		if (story && story.length) {
			var datum = story[0];
			datum.featured = true;
			html.push(window.JST['story.template'](datum));

			stories = $.grep(stories, function(element) {
				return element.id != id;
			});

			// and then show 3 more
			count = Math.min(3, stories.length);

		} else {
			// show 10 stories, or all, whichever is smaller
			count = Math.min(10, stories.length);
		}

		// process one story at a time
		while (count--) {
			html.push(window.JST['story.template'](stories.pop()));
		}

		// after first call, hide spinner, enable form
		if (!isLoaded) {
			spinner.stop();
			$('.loading').addClass('hidden');
			$('form', master).parent().removeClass('hidden');
			isLoaded = !isLoaded;
		}

		// add stories to dom
		$('.stories', master).append(html.join(''));

		setTimeout(function() {
			$('.story').removeClass('featured');
		}, 1000);

		// do we still have stories to show?
		if (stories.length) {
			$('button.loadMoreStories', master).removeClass('hidden');
			$('p.noMoreStories', master).addClass('hidden');
		} else {
			$('button.loadMoreStories', master).addClass('hidden');
			$('p.noMoreStories', master).removeClass('hidden');
		}

	}

	function slideStoriesDown() {
		var height = $('form', master).outerHeight();
		$('.stories', master).css('transform', 'translateY(' + height + 'px)');
		$('.storiesSpacer').height(height);
	}

	function slideStoriesUp() {
		$('.stories', master).css('transform', 'translateY(0px)');
		$('.storiesSpacer').height(0);
	}

	$('button.why', master).click(function(e) {

		e.preventDefault();

		smoothScroll($(this));

		slideStoriesDown();
		$('button.why', master).parent().find('.why,.title').toggleClass('hidden');

	});

	$('button.cancel').click(function(e) {

		e.preventDefault();

		// clear validation
		$('form', master).parent().find('.error').addClass('hidden');

		// clear form
		$('form input,textarea', master).val('');

	});

	$('button.cancel, button.title').click(function(e) {

		e.preventDefault();

		slideStoriesUp();
		$('button.why', master).parent().find('.why,.title').toggleClass('hidden');

	});

	$('button.loadMoreStories', master).click(loadMoreStories);

	var requiredFields = $('form', master).find('input, textarea').filter(function() {
		return $(this).parent().find('.required').length;
	});

	requiredFields.change(function() {

		// if this isn't empty, remove the error field
		if ($(this).val().length) {
			$(this).parent().find('.error').addClass('hidden');
		}

	});

	// check image size
	var imageInput = $('form', master).find('input[name="image"]');
	imageInput.change(function() {

		var field = $(this).get(0);
		if (window.FileReader && field.files && field.files[0] && field.files[0].size > 1000000) {
			$(this).parent().find('.error').removeClass('hidden');
		} else {
			$(this).parent().find('.error').addClass('hidden');
		}

	});

	$('button.submit', master).click(function(e) {

		e.preventDefault();

		$('form', master).ajaxSubmit({
			beforeSubmit: function(array, $form, options) {

				var isValid = true;

				requiredFields.each(function() {

					// if field is empty, show message and invalidate the entire form
					if (!$(this).val().length) {
						isValid = false;
						$(this).parent().find('.error').removeClass('hidden');
					}

				});

				// check image size
				if (!imageInput.parent().find('.error').hasClass('hidden')) {
					isValid = false;
				}

				return isValid;
			},
			data: {
				video_link: '',
				video_description: '',
				photo_description: imageInput.val().length ? $('form textarea', master).val() : '',
				email: '',
				type: imageInput.val().length ? 3 : 1,
				year: 2014
			},
			success: function() {
				slideStoriesUp();
				$('button.why', master).parent().find('.why,.title').addClass('hidden');
				$('button.why', master).parent().find('.thanks').removeClass('hidden');
			}
		});

	});

	master.on('click', '.twitter', function() {

		// get the id
		var id = $(this).parents('.story').data('id');

		// get the story
		var story = $.grep(rawData, function(element) {
			return element.id === id;
		})[0];

		var href = window.location.href.split('#')[0];

		var url = encodeURIComponent(href + '#' + id);
		var text = encodeURIComponent("Running this year's Boston marathon? Read " + story.name + "'s story at Why I Run via @BostonGlobe");

		var intent = 'https://twitter.com/intent/tweet?text=' + text + '&url=' + url;

		window.open(intent, '', 'width=550px,height=420px');

	});

	$.getJSON('http://www.boston.com/newsprojects/whyirun/get_stories_api.php?year=2014', function(json) {

		for (var i = 0; i < json.length; i++) {
			json[i].featured = false;
			rawData.push(json[i]);
		}

		stories = json.reverse();

		// is there a hash in the url?
		var id = window.location.hash && window.location.hash.length ?
			// yes - assume it's an id, load that one particular story
			+window.location.hash.substring(1) :
			// no
			null;

		loadMoreStories(id);

	});

};<|endoftext|>


<commit_before>var Google_Loader = (function (window, document) {

    that = this;

	Google_Loader = function (opts) {

        this.int_loading_state = 0;

        this.current_loop = 0;

    	this.fn_loading_screen_animation();

		window.addEventListener('load', function() {
		    FastClick.attach(document.body);
		}, false);

		$('.replay-message-button').on('click', this.fn_replay.bind(this));

	};

	Google_Loader.prototype = {

		// ------------------------------------------------------
		// Skimmin Core Functions
		// ------------------------------------------------------

        fn_replay: function() {

        	$('.replay-message-button').addClass('animated rotateOut');

        	setTimeout(function() {
        		$('.replay-message-button').fadeOut(0);
        		$('.replay-message-button').removeClass('animated rotateOut');
        	}, 500);

        	loading_circles = '
' +
					'

' +
					'

' +
				'
' +
				'
' +
					'

' +
					'

' +
				'
' +
				'
' +
					'

' +
					'

' +
				'
' +
				'
' +
					'

' +
					'

' +
				'
';

        	$('#loading-container').fadeIn(0);
        	$('#loading-circles-container').fadeIn(0);
        	$('#loading-circles-container').html('');
        	$('#loading-circles-container').html(loading_circles);

        	this.fn_loading_screen_animation();

        },

        // one full "loop"  -- this function is called in another, high level loading function
        fn_loading_screen_animation: function(int_degrees) {

            if (this.current_loop == 0) {

            	setTimeout(function() {
            		$('#loading-inner-circle-1').fadeIn(0);
            		$('#loading-inner-circle-1').addClass('animated bounceIn');
            	}, 1000);

            	setTimeout(function() {
            		$('#loading-inner-circle-2').fadeIn(0);
            		$('#loading-inner-circle-2').addClass('animated bounceIn');
            	}, 1500);

            	setTimeout(function() {
            		$('#loading-inner-circle-3').fadeIn(0);
            		$('#loading-inner-circle-3').addClass('animated bounceIn');
            	}, 2000);

                setTimeout(function() {
                	$('#loading-inner-circle-4').fadeIn(0);
            		$('#loading-inner-circle-4').addClass('animated bounceIn');
            	}, 2500);

            }

            else {

            	$('#loading-circle-1').fadeIn(0);
            	$('#loading-circle-1').addClass('animated zoomIn');

            	setTimeout(function() {
            		$('#loading-circle-2').fadeIn(0);
            		$('#loading-circle-2').addClass('animated zoomIn');
            	}, 75);

            	setTimeout(function() {
            		$('#loading-circle-3').fadeIn(0);
            		$('#loading-circle-3').addClass('animated zoomIn');
            	}, 150);

            	setTimeout(function() {
            		$('#loading-circle-4').fadeIn(0);
            		$('#loading-circle-4').addClass('animated zoomIn');
            	}, 225);

            	setTimeout(function() {
            		$('#loading-circle-1').removeClass('animated zoomIn');
            		$('#loading-circle-2').removeClass('animated zoomIn');
            		$('#loading-circle-3').removeClass('animated zoomIn');
            		$('#loading-circle-4').removeClass('animated zoomIn');
            	}, 1000);

            	setTimeout(function() {
        			$('#loading-inner-circle-1').fadeIn(0);
        			$('#loading-inner-circle-1').addClass('animated bounceIn');
        		}, 1225);

           		setTimeout(function() {
        			$('#loading-inner-circle-2').fadeIn(0);
        			$('#loading-inner-circle-2').addClass('animated bounceIn');
        		}, 1725);

            	setTimeout(function() {
        			$('#loading-inner-circle-3').fadeIn(0);
        			$('#loading-inner-circle-3').addClass('animated bounceIn');
        		}, 2225);

            	setTimeout(function() {
        			$('#loading-inner-circle-4').fadeIn(0);
        			$('#loading-inner-circle-4').addClass('animated bounceIn');
        		}, 2725);

            }

            setTimeout(function() {
        		$('#loading-circle-1').addClass('animated bounceOut');
        		$('#loading-circle-2').addClass('animated bounceOut');
    			$('#loading-circle-3').addClass('animated bounceOut');
        		$('#loading-circle-4').addClass('animated bounceOut');
        	}, 3175);

        	setTimeout(function() {
        		$('.replay-message-button').fadeIn(0);
        		$('.replay-message-button').addClass('animated bounceIn');
        		$('#loading-container').fadeOut(500);
        	}, 3800);

        	setTimeout(function(){
        		$('.replay-message-button').fadeOut(0);
        		$('.replay-message-button').removeClass('animated bounceIn');
        	}, 4600);

            setTimeout(function() {
        		$('#loading-inner-circle-1').fadeOut(0);
    			$('#loading-inner-circle-2').fadeOut(0);
    			$('#loading-inner-circle-3').fadeOut(0);
        		$('#loading-inner-circle-4').fadeOut(0);
            	$('#loading-circles-container').fadeOut(0);

        		$('#loading-inner-circle-1').fadeOut(0);
        		$('#loading-inner-circle-1').removeClass('animated bounceIn');
        		$('#loading-inner-circle-2').fadeOut(0);
        		$('#loading-inner-circle-2').removeClass('animated bounceIn');
        		$('#loading-inner-circle-3').fadeOut(0);
        		$('#loading-inner-circle-3').removeClass('animated bounceIn');
        		$('#loading-inner-circle-4').fadeOut(0);
        		$('#loading-inner-circle-4').removeClass('animated bounceIn');

        	}, 5125);

            this.current_loop += 1;

        },

	};

	return Google_Loader;

})(window, document);
<commit_msg>Update google-loader.js<commit_after>var Google_Loader = (function (window, document) {

    that = this;

	Google_Loader = function (opts) {

        this.int_loading_state = 0;

        this.current_loop = 0;

    	this.fn_loading_screen_animation();

		window.addEventListener('load', function() {
		    FastClick.attach(document.body);
		}, false);

		$('.replay-message-button').on('click', this.fn_replay.bind(this));

	};

	Google_Loader.prototype = {

		// ------------------------------------------------------
		// Skimmin Core Functions
		// ------------------------------------------------------

        fn_replay: function() {

        	$('.replay-message-button').removeClass('animated bounceIn');
        	$('.replay-message-button').addClass('animated rotateOut');

        	setTimeout(function() {
        		$('.replay-message-button').fadeOut(0);
        		$('.replay-message-button').removeClass('animated rotateOut');
        	}, 500);

        	loading_circles = '
' +
					'

' +
					'

' +
				'
' +
				'
' +
					'

' +
					'

' +
				'
' +
				'
' +
					'

' +
					'

' +
				'
' +
				'
' +
					'

' +
					'

' +
				'
';

        	$('#loading-container').fadeIn(0);
        	$('#loading-circles-container').fadeIn(0);
        	$('#loading-circles-container').html('');
        	$('#loading-circles-container').html(loading_circles);

        	this.fn_loading_screen_animation();

        },

        // one full "loop"  -- this function is called in another, high level loading function
        fn_loading_screen_animation: function(int_degrees) {

            if (this.current_loop == 0) {

            	setTimeout(function() {
            		$('#loading-inner-circle-1').fadeIn(0);
            		$('#loading-inner-circle-1').addClass('animated bounceIn');
            	}, 1000);

            	setTimeout(function() {
            		$('#loading-inner-circle-2').fadeIn(0);
            		$('#loading-inner-circle-2').addClass('animated bounceIn');
            	}, 1500);

            	setTimeout(function() {
            		$('#loading-inner-circle-3').fadeIn(0);
            		$('#loading-inner-circle-3').addClass('animated bounceIn');
            	}, 2000);

                setTimeout(function() {
                	$('#loading-inner-circle-4').fadeIn(0);
            		$('#loading-inner-circle-4').addClass('animated bounceIn');
            	}, 2500);

            }

            else {

            	$('#loading-circle-1').fadeIn(0);
            	$('#loading-circle-1').addClass('animated zoomIn');

            	setTimeout(function() {
            		$('#loading-circle-2').fadeIn(0);
            		$('#loading-circle-2').addClass('animated zoomIn');
            	}, 75);

            	setTimeout(function() {
            		$('#loading-circle-3').fadeIn(0);
            		$('#loading-circle-3').addClass('animated zoomIn');
            	}, 150);

            	setTimeout(function() {
            		$('#loading-circle-4').fadeIn(0);
            		$('#loading-circle-4').addClass('animated zoomIn');
            	}, 225);

            	setTimeout(function() {
            		$('#loading-circle-1').removeClass('animated zoomIn');
            		$('#loading-circle-2').removeClass('animated zoomIn');
            		$('#loading-circle-3').removeClass('animated zoomIn');
            		$('#loading-circle-4').removeClass('animated zoomIn');
            	}, 1000);

            	setTimeout(function() {
        			$('#loading-inner-circle-1').fadeIn(0);
        			$('#loading-inner-circle-1').addClass('animated bounceIn');
        		}, 1225);

           		setTimeout(function() {
        			$('#loading-inner-circle-2').fadeIn(0);
        			$('#loading-inner-circle-2').addClass('animated bounceIn');
        		}, 1725);

            	setTimeout(function() {
        			$('#loading-inner-circle-3').fadeIn(0);
        			$('#loading-inner-circle-3').addClass('animated bounceIn');
        		}, 2225);

            	setTimeout(function() {
        			$('#loading-inner-circle-4').fadeIn(0);
        			$('#loading-inner-circle-4').addClass('animated bounceIn');
        		}, 2725);

            }

            setTimeout(function() {
        		$('#loading-circle-1').addClass('animated bounceOut');
        		$('#loading-circle-2').addClass('animated bounceOut');
    			$('#loading-circle-3').addClass('animated bounceOut');
        		$('#loading-circle-4').addClass('animated bounceOut');
        	}, 3175);

        	setTimeout(function() {
        		$('.replay-message-button').fadeIn(0);
        		$('.replay-message-button').addClass('animated bounceIn');
        		$('#loading-container').fadeOut(500);
        	}, 3800);

        	/*setTimeout(function(){
        		$('.replay-message-button').fadeOut(0);
        		$('.replay-message-button').removeClass('animated bounceIn');
        	}, 4600);*/

            setTimeout(function() {
        		$('#loading-inner-circle-1').fadeOut(0);
    			$('#loading-inner-circle-2').fadeOut(0);
    			$('#loading-inner-circle-3').fadeOut(0);
        		$('#loading-inner-circle-4').fadeOut(0);
            	$('#loading-circles-container').fadeOut(0);

        		$('#loading-inner-circle-1').fadeOut(0);
        		$('#loading-inner-circle-1').removeClass('animated bounceIn');
        		$('#loading-inner-circle-2').fadeOut(0);
        		$('#loading-inner-circle-2').removeClass('animated bounceIn');
        		$('#loading-inner-circle-3').fadeOut(0);
        		$('#loading-inner-circle-3').removeClass('animated bounceIn');
        		$('#loading-inner-circle-4').fadeOut(0);
        		$('#loading-inner-circle-4').removeClass('animated bounceIn');

        	}, 5125);

            this.current_loop += 1;

        },

	};

	return Google_Loader;

})(window, document);
<|endoftext|>


<commit_before>Gratipay.team = (function() {
    function init() {
        var $team = $('#team');

        var $indicator = $('
');
        $indicator.appendTo($('#page'));

        $('#lookup-container form').submit(add);
        $('#query').focus().keyup(lookup);

        $team.css('opacity', 0);

        jQuery.get("index.json").success(function(members) {
            $team.css('opacity', 1);
            $indicator.remove();
            drawRows(members);
        });
    }

    // Draw Rows
    // =========

    function num(n) { return n.toFixed(2); }
    function perc(n) { return (n * 100).toFixed(1); }

    function drawMemberTake(member) {
        var take = num(member.take);

        if (member.editing_allowed)
            return [ 'form', {'id': 'take'}
                         , ['input', { 'value': take
                                                 , 'data-username': member.username
                                                 , 'data-take': take // useful to reset form
                                                 , 'tabindex': '1'
                                                    }]
                            ];

        if (member.removal_allowed)
            return [ 'span', { 'class': 'remove'
                                             , 'data-username': member.username
                                                }, take];

        return take;
    }

    function drawRows(members) {
        nmembers = members.length - 1; // includes the team itself, which we don't
                                                                     // want to enumerate
        var rows = [];

        if (nmembers === 0) {
            rows.push(Gratipay.jsonml(
                [ 'tr'
                , ['td', {'colspan': '6', 'class': 'no-members'}, "No members"]
                 ]
            ));
        }

        for (var i=0, len=members.length; i<len; i++) {
            var member = members[i];
            var increase = '';

            if (member.take > member.last_week)
                increase = 'moderate';
            if (member.take > (member.last_week * 1.25))
                increase = 'high';
            if (member.take === member.max_this_week)
                increase = 'max';

            if (i < nmembers)
                rows.push(Gratipay.jsonml(
                    [ 'tr'
                    , ['td', {'class': 'n'}, (i === nmembers ? '' : nmembers - i)]
                    , ['td', ['a', {'href': '/'+member.username+'/'}, member.username]]
                    , ['td', {'class': 'figure last_week'}, num(member.last_week)]
                    , ['td', {'class': 'figure take ' + increase}, drawMemberTake(member)]
                    , ['td', {'class': 'figure balance'}, num(member.balance)]
                    , ['td', {'class': 'figure percentage'}, perc(member.percentage)]
                     ]
                ));
            else if (nmembers > 0)
                rows.push(Gratipay.jsonml(
                    [ 'tr'
                    , ['td']
                    , ['td']
                    , ['td']
                    , ['td', {'class': 'figure take'}, num(member.take)]
                    , ['td', {'class': 'figure balance'}, num(member.balance)]
                    , ['td', {'class': 'figure percentage'}, perc(member.percentage)]
                     ]
                ));
        }
        $('#members').html(rows);
        $('#take').submit(doTake);
        $('#take input').focus().keyup(maybeCancelTake);
        $('#members .remove').click(remove);
    }

    // Add
    // ===

    function lookup() {
        var query = $('#query').val();
        if (query === '')
            $('#lookup-results').empty();
        else
            jQuery.get("/lookup.json", {query: query}).success(drawLookupResults);
    }

    function drawLookupResults(results) {
        var items = [];
        for (var i=0, len=results.length; i<len; i++) {
            var result = results[i];
            items.push(Gratipay.jsonml(
                ['li', {"data-id": result.id}, result.username]
            ));
        }
        $('#lookup-results').html(items);
    }

    function add(e) {
        e.preventDefault();
        e.stopPropagation();
        var query = $('#query').val();
        setTake(query, '0.01', function() { Gratipay.notification('Member added!', 'success'); });
        $('#lookup-results').empty();
        $('#query').val('').focus();
        return false;
    }

    function remove(e) {
        e.preventDefault();
        e.stopPropagation();
        var membername = $(e.target).attr('data-username');
        if (confirm("Remove " + membername + " from this team?"))
            setTake(membername, '0.00', function() { Gratipay.notification('Member removed!'); });
        return false;
    }

    // Take
    // ====

    function maybeCancelTake(e) {
        if (e.which === 27) {
            resetTake();
        }
    }

    function resetTake() {
        var _ = $('#take input');
        _.val(_.attr('data-take')).blur();
    }

    function doTake(e) {
        e.preventDefault();
        e.stopPropagation();
        var frm = $('#take'), _ = $('input', frm);
        var username = _.attr('data-username'),
                take = _.val();
        if (take.search(/^\d+\.?\d*$/) !== 0)
            Gratipay.notification("Bad input! Must be a number.", 'error');
        else
        {
            var callback = function(d) {
                var newTake = $.grep(d, function(row) { return row.username == username })[0].take;
                if ( take == newTake)
                    Gratipay.notification('Updated your take!', 'success');
                else
                    Gratipay.notification('You cannot exceed double of last week. Updated your take to ' + newTake + '.', 'error');

                // Have a little fun if updating the user's take results in the team balance
                // equaling $0.01 or $1.00
                var balance = $('.figure.balance').last().text();
                if (localStorage && !localStorage.lastSushi && (balance == '0.01' || balance == '1.00')) {
                    Gratipay.notification('Achievement Unlocked: The Last Sushi Roll', 'success');
                    localStorage.lastSushi = true;
                }
            };
            if (parseFloat(take) === 0) {
                if (!confirm("Remove yourself from this team?")) {
                    resetTake();
                    return false;
                }
                callback = function() { Gratipay.notification('Removed!'); };
            }
            setTake(username, take, callback);
        }
        return false;
    }

    function setTake(username, take, callback) {
        callback = callback || function() {};

        // The members.json endpoint takes a list of member objects so that it
        // can be updated programmatically in bulk (as with tips.json. From
        // this UI we only ever change one at a time, however.

        jQuery.ajax(
                { type: 'POST'
                , url: username + ".json"
                , data: {take: take}
                , success: function(d) { drawRows(d); callback(d); }
                , error: function(xhr) {
                        switch (xhr.status) {
                            case 404: Gratipay.notification("Unknown user!", 'error'); break;
                            default: Gratipay.notification("Problem! " + xhr.status, 'error');
                        }
                    }
                 });
    }

    // Export
    // ======

    return {init: init};
})();
<commit_msg>"Updating..." indicator when take is updated. fixes #1416<commit_after>Gratipay.team = (function() {
    function init() {
        var $team = $('#team');

        var $indicator = $('
');
        $indicator.appendTo($('#page'));

        $('#lookup-container form').submit(add);
        $('#query').focus().keyup(lookup);

        $team.css('opacity', 0);

        jQuery.get("index.json").success(function(members) {
            $team.css('opacity', 1);
            $indicator.remove();
            drawRows(members);
        });
    }

    // Draw Rows
    // =========

    function num(n) { return n.toFixed(2); }
    function perc(n) { return (n * 100).toFixed(1); }

    function drawMemberTake(member) {
        var take = num(member.take);

        if (member.editing_allowed)
            return [ 'form', {'id': 'take'}
                         , ['input', { 'value': take
                                                 , 'data-username': member.username
                                                 , 'data-take': take // useful to reset form
                                                 , 'tabindex': '1'
                                                    }]
                            ];

        if (member.removal_allowed)
            return [ 'span', { 'class': 'remove'
                                             , 'data-username': member.username
                                                }, take];

        return take;
    }

    function drawRows(members) {
        nmembers = members.length - 1; // includes the team itself, which we don't
                                                                     // want to enumerate
        var rows = [];

        if (nmembers === 0) {
            rows.push(Gratipay.jsonml(
                [ 'tr'
                , ['td', {'colspan': '6', 'class': 'no-members'}, "No members"]
                 ]
            ));
        }

        for (var i=0, len=members.length; i<len; i++) {
            var member = members[i];
            var increase = '';

            if (member.take > member.last_week)
                increase = 'moderate';
            if (member.take > (member.last_week * 1.25))
                increase = 'high';
            if (member.take === member.max_this_week)
                increase = 'max';

            if (i < nmembers)
                rows.push(Gratipay.jsonml(
                    [ 'tr'
                    , ['td', {'class': 'n'}, (i === nmembers ? '' : nmembers - i)]
                    , ['td', ['a', {'href': '/'+member.username+'/'}, member.username]]
                    , ['td', {'class': 'figure last_week'}, num(member.last_week)]
                    , ['td', {'class': 'figure take ' + increase}, drawMemberTake(member)]
                    , ['td', {'class': 'figure balance'}, num(member.balance)]
                    , ['td', {'class': 'figure percentage'}, perc(member.percentage)]
                     ]
                ));
            else if (nmembers > 0)
                rows.push(Gratipay.jsonml(
                    [ 'tr'
                    , ['td']
                    , ['td']
                    , ['td']
                    , ['td', {'class': 'figure take'}, num(member.take)]
                    , ['td', {'class': 'figure balance'}, num(member.balance)]
                    , ['td', {'class': 'figure percentage'}, perc(member.percentage)]
                     ]
                ));
        }
        $('#members').html(rows);
        $('#take').submit(doTake);
        $('#take input').focus().keyup(maybeCancelTake);
        $('#members .remove').click(remove);
    }

    // Add
    // ===

    function lookup() {
        var query = $('#query').val();
        if (query === '')
            $('#lookup-results').empty();
        else
            jQuery.get("/lookup.json", {query: query}).success(drawLookupResults);
    }

    function drawLookupResults(results) {
        var items = [];
        for (var i=0, len=results.length; i<len; i++) {
            var result = results[i];
            items.push(Gratipay.jsonml(
                ['li', {"data-id": result.id}, result.username]
            ));
        }
        $('#lookup-results').html(items);
    }

    function add(e) {
        e.preventDefault();
        e.stopPropagation();
        var query = $('#query').val();
        setTake(query, '0.01', function() { Gratipay.notification('Member added!', 'success'); });
        $('#lookup-results').empty();
        $('#query').val('').focus();
        return false;
    }

    function remove(e) {
        e.preventDefault();
        e.stopPropagation();
        var membername = $(e.target).attr('data-username');
        if (confirm("Remove " + membername + " from this team?"))
            setTake(membername, '0.00', function() { Gratipay.notification('Member removed!'); });
        return false;
    }

    // Take
    // ====

    function maybeCancelTake(e) {
        if (e.which === 27) {
            resetTake();
        }
    }

    function resetTake() {
        var _ = $('#take input');
        _.val(_.attr('data-take')).blur();
    }

    function doTake(e) {
        e.preventDefault();
        e.stopPropagation();
        var frm = $('#take'), _ = $('input', frm);
        var username = _.attr('data-username'),
                take = _.val();
        if (take.search(/^\d+\.?\d*$/) !== 0)
            Gratipay.notification("Bad input! Must be a number.", 'error');
        else
        {
            var callback = function(d) {
                var newTake = $.grep(d, function(row) { return row.username == username })[0].take;
                if ( take == newTake)
                    Gratipay.notification('Updated your take!', 'success');
                else
                    Gratipay.notification('You cannot exceed double of last week. Updated your take to ' + newTake + '.', 'error');

                // Have a little fun if updating the user's take results in the team balance
                // equaling $0.01 or $1.00
                var balance = $('.figure.balance').last().text();
                if (localStorage && !localStorage.lastSushi && (balance == '0.01' || balance == '1.00')) {
                    Gratipay.notification('Achievement Unlocked: The Last Sushi Roll', 'success');
                    localStorage.lastSushi = true;
                }
            };
            if (parseFloat(take) === 0) {
                if (!confirm("Remove yourself from this team?")) {
                    resetTake();
                    return false;
                }
                callback = function() { Gratipay.notification('Removed!'); };
            }
            var $updating = $('Updating...');
            $('#take').parent().html($updating)
            setTake(username, take, callback);
        }
        return false;
    }

    function setTake(username, take, callback) {
        callback = callback || function() {};

        // The members.json endpoint takes a list of member objects so that it
        // can be updated programmatically in bulk (as with tips.json. From
        // this UI we only ever change one at a time, however.

        jQuery.ajax(
                { type: 'POST'
                , url: username + ".json"
                , data: {take: take}
                , success: function(d) { drawRows(d); callback(d); }
                , error: function(xhr) {
                        switch (xhr.status) {
                            case 404: Gratipay.notification("Unknown user!", 'error'); break;
                            default: Gratipay.notification("Problem! " + xhr.status, 'error');
                        }
                    }
                 });
    }

    // Export
    // ======

    return {init: init};
})();
<|endoftext|>


<commit_before>var PDP = (function ( pdp ) {

  'use strict';

  // The Query Object
  // ----------------
  // The `query` object is used to cache filter values and store methods
  // that manipulate filter values.
  var query = {};

  // If debug is set to true, dummy data will be used.
  query.debug = false;

  // Set a default format for the data download.
  query.format = query.debug ? 'json' : 'jsonp?$callback=';

  // Set a default endpoint for AJAX requests.
  query.endpoint = query.debug ? 'static/js/static_data/' : 'https://qu2.demo.cfpb.gov/data/hmda/';

  // Seconds to wait on a response from the API before giving up.
  query.secondsToWait = 180;

  // Whether or not they want codes in their downloaded file.
  query.codes = false;

  // `query`'s `params` stores filter values.
  query.params = {};

  // The `reset` method empties the `params` object.
  // If defaults: true is passed, some defaults will be set.
  query.reset = function( options ) {

    options = options || {};

    if ( options.defaults ) {
      this.params = {
        as_of_year: {
          values: [2012],
          comparator: '='
        },
        property_type: {
          values: [1,2],
          comparator: '='
        },
        owner_occupancy: {
          values: [1],
          comparator: '='
        },
        lien_status: {
          values: [1],
          comparator: '='
        },
        action_taken: {
          values: [1],
          comparator: '='
        }
      };
    } else {
      this.params = {};
    }

    return this;

  };

  // The `updateAll` method runs through all the filter field values the user has selected
  // and stores them in the `params` object.
  query.updateAll = function( options ) {

    var fields,
        opts = options || {};

    switch( opts.source ) {
      case 'url':
        fields = pdp.app.getUrlValues();
        break;
      case 'session':
        fields = pdp.query.getCookie();
        break;
      default:
        fields = pdp.form.getFields();
    }

    this.reset();

    // Iterate over all the filter field values and push them into `query.params`.
    function _processField( field ) {

      if ( field.name && field.values ) {

        // Initalize an empty param object if need be.
        if ( typeof query.params[ field.name ] === 'undefined' ) {
          query.params[ field.name ] = {
            values: [],
            comparator: '='
          };
        }

        // If the value is a string from a text box we don't want to iterate
        // over it because it will split up the characters.
        if ( field.type === 'text' ) {
          query.params[ field.name ].values = [ field.values ];
        } else {
          _.forEach( field.values, function( val, name ){

            var values = query.params[ field.name ].values;

            // Only push the value if it's not already in there.
            if ( !_.contains( values, val ) ) {
              values.push( val );
            }

          });
        }

        query.params[ field.name ].comparator = field.comparator;

      }

    }

    _.forEach( fields, _processField );

    pdp.observer.emitEvent( 'params:updated' );

  };

  // The `setCookie` method stores the param object in a cookie.
  query.setCookie = function() {

    $.cookie( '_hmda', pdp.form.getFields() );

  };

  // The `getCookie` method retrives the param object from a cookie.
  query.getCookie = function() {

    return $.cookie( '_hmda' );

  };

  // The `generateUrlHash` method builds and returns a URL hash from `query`'s `params`.
  query.generateUrlHash = function() {
    var hash,
        hashParams = [];

    // Loop through params, stringify them and push them into the temp array.
    function buildHashParam( param, name ) {

      if ( !param.values[0] ) {
        return;
      }

      // If it's not a number, add quotes around the params.
      hashParams.push( name + param.comparator + param.values.join(',') );

    }
    _.forEach( query.params, buildHashParam );

    hash = '!/' + hashParams.join('&') + '&section=' + pdp.app.currentSection;
    return hash;

  };

  query.removeSelectParam = function (params) {
    //using a copy of the params means that the select obj
    //is still available on query.params for share url generation
    var paramsCopy = $.extend(true, {}, params);
    try {
      delete paramsCopy.clauses.where.select;
    } catch (e) {
      //nested property doesn't exist
    }
    delete paramsCopy.select;
    return paramsCopy;
  };

  // The `generateApiUrl` method builds and returns a Qu URL from `query`'s `params`.
  query.generateApiUrl = function( format, codes, params ) {

    var url,
        apiCallParams = params || this.params,
        showCodes = codes || this.codes,
        downloadFormat = format || this.format;
    //remove 'select' from params so it won't be added to where clause
    apiCallParams = query.removeSelectParam(apiCallParams);

    // Set a base url to append params to
    url = this.endpoint + 'slice/hmda_lar.' + downloadFormat + '?';

    if ( !showCodes ) {
      apiCallParams = {
        clauses: {
          where: apiCallParams,
          select: ['action_taken_name','agency_abbr','agency_name','applicant_ethnicity_name','applicant_race_name_1','applicant_race_name_2','applicant_race_name_3','applicant_race_name_4','applicant_race_name_5','applicant_sex_name','application_date_indicator','as_of_year','census_tract_number','co_applicant_ethnicity_name','co_applicant_race_name_1','co_applicant_race_name_2','co_applicant_race_name_3','co_applicant_race_name_4','co_applicant_race_name_5','co_applicant_sex_name','county_name','denial_reason_name_1','denial_reason_name_2','denial_reason_name_3','edit_status_name','hoepa_status_name','lien_status_name','loan_purpose_name','loan_type_name','msamd_name','owner_occupancy_name','preapproval_name','property_type_name','purchaser_type_name','respondent_id','sequence_number','state_abbr','state_name','applicant_income_000s','hud_median_family_income','loan_amount_000s','number_of_1_to_4_family_units','number_of_owner_occupied_units','minority_population','population','rate_spread','tract_to_msamd_income']
        }
      };
    }

    // fetch, compile queries
    url += this._buildApiQuery( apiCallParams );

    return url;

  };

  // builds the query string to append to api url
  // arg: params, object. if you only need a 'where' clause, passing in
  // query.params will do just fine
  // if you need to use the 'select' and 'group' clauses, pass an object
  // that has a property 'clauses' with an array of objects that correspond to
  // each clause. example:
  // params = {
  //   clauses: {
  //     select: ['var_one', 'var_two'],
  //     group: ['var_one', 'var_two']
  //   }
  // }
  query._buildApiQuery = function( params ) {
    var url = '', key;

    if ( params.hasOwnProperty('clauses') ) {
      for ( key in params.clauses ) {
        if ( params.clauses.hasOwnProperty( key ) ) {
          url += this._buildClause[key]( params.clauses[key] );
        }
      }
    } else {
      url = this._buildClause.where( params );
    }

    return url;

  };

  // methods correspond to each type of clause that the API takes
  // builds part of api call query string pertaining to clause
  query._buildClause = {

  // Convert each param to a proper [`$where` clause](http://cfpb.github.io/qu/articles/queries.html#where_in_detail).
    where: function( params ) {

      var _params = {},
          queryVals = [];

      // In order to compensate for enumerated location fields (state_code-1, county_name-1, etc.)
      // we have to go through and consolidate all enumerated params into unified objects.
      _.forEach( params, function( param, paramName ) {

        if ( !param.values || !param.values[0] ) {
          return;
        }

        var consolidatedName;

        if ( paramName.match(/\-\d+$/) ) {
          consolidatedName = paramName.replace(/\-\d+$/, '');

          // Initalize an empty param object if need be.
          if ( typeof _params[ consolidatedName ] === 'undefined' ) {
            _params[ consolidatedName ] = {
              values: [],
              comparator: '='
            };
          }

          _.forEach( param.values, function( value ){
            _params[ consolidatedName ].values.push( value );
          });

        } else {
          _params[ paramName ] = param;
        }

      });

      // We can now get back to business and generate that WERECLAWS
      _.forEach( _params, function( param, paramName ) {

        var paramVals;

        // Strip `-min/max` from the end of the param. This is mainly done for the loan_amount_000s fields.
        paramName = paramName.replace( /\-(min|max)$/, '' );
        paramVals = this._formatComparisonValues( param, paramName );
        queryVals.push( paramVals );

      }.bind( this ));

      // Join all the params with `AND` operators and append it to the base url,
      // replacing spaces with plus signs.
      return '&$where=' + encodeURI( queryVals.join(' AND ') ).replace( /%20/g, '+' );

    },

    // select and group clauses are formatted the same way sans name
    select: function( param ) {
      return '&$select=' + this._listVals( param );
    },

    group: function( param ) {
      return '&$group=' + this._listVals( param ) + '&$orderBy=' + this._listVals( _.clone( param ).reverse() );
    },

    // formats single value clauses
    // returns string of comma-delimited values
    _listVals: function( param ) {

      var i = param.length,

        str = '';

      while( i-- ) {
        if ( typeof param[i] !== 'undefined' ) {
          str += param[i];
          // if this is not the last value, add comma
          if ( i > 0 ) {
            str += ',';
          }
        }
      }

      return str;
    },

    // formats api call values that have a comparison operator
    _formatComparisonValues: function( param, paramName ){
      var paramVal,
          paramVals = [];

      // If there's only value for the param, meaning they only selected one item or
      // it's a radio button that only allows once value, add the stringified
      // param to the `queryVals` array.
      if ( param.values.length === 1 ) {

        // Rate spread specialness
        if ( param.values[0] == 'null' ) {
          paramVal = paramName + param.comparator + '""';
        // Strings
        } else if ( isNaN( param.values[0] ) || paramName === 'msamd' || paramName === 'respondent_id' || paramName === 'census_tract_number' ) {
          paramVal = paramName + param.comparator + '"' + param.values[0] + '"';
        // Numbers
        } else {
          paramVal = paramName + param.comparator + param.values[0];
        }

        return paramVal;

      // If there are multiple values for a single parameter, we iterate over them and
      // put an `OR` operator between them. We then then [group them](http://cfpb.github.io/qu/articles/queries.html#boolean_operators)
      // with parens and add the grouping to the `params` array.
      } else {

        _.forEach( param.values, function( val, key ){

          if ( isNaN( val ) || paramName === 'msamd' || paramName === 'respondent_id' || paramName === 'census_tract_number' ) {
            paramVals.push( paramName + param.comparator + '"' + val + '"' );
          } else {
            paramVals.push( paramName + param.comparator + val );
          }

        });

        return '(' + paramVals.join(' OR ') + ')';

      }

    }

  };

  // The `fetch` method requests and returns JSON from Qu matching the
  // user's filter and grouping clauses
  query.fetch = function() {

  };

  // Export the public API.
  pdp.query = query;

  return pdp;

}( PDP || {} ));
<commit_msg>Point front-end to production API, #370 #383 #388<commit_after>var PDP = (function ( pdp ) {

  'use strict';

  // The Query Object
  // ----------------
  // The `query` object is used to cache filter values and store methods
  // that manipulate filter values.
  var query = {};

  // If debug is set to true, dummy data will be used.
  query.debug = false;

  // Set a default format for the data download.
  query.format = query.debug ? 'json' : 'jsonp?$callback=';

  // Set a default endpoint for AJAX requests.
  query.endpoint = query.debug ? 'static/js/static_data/' : 'https://api.consumerfinance.gov/data/hmda/';

  // Seconds to wait on a response from the API before giving up.
  query.secondsToWait = 180;

  // Whether or not they want codes in their downloaded file.
  query.codes = false;

  // `query`'s `params` stores filter values.
  query.params = {};

  // The `reset` method empties the `params` object.
  // If defaults: true is passed, some defaults will be set.
  query.reset = function( options ) {

    options = options || {};

    if ( options.defaults ) {
      this.params = {
        as_of_year: {
          values: [2012],
          comparator: '='
        },
        property_type: {
          values: [1,2],
          comparator: '='
        },
        owner_occupancy: {
          values: [1],
          comparator: '='
        },
        lien_status: {
          values: [1],
          comparator: '='
        },
        action_taken: {
          values: [1],
          comparator: '='
        }
      };
    } else {
      this.params = {};
    }

    return this;

  };

  // The `updateAll` method runs through all the filter field values the user has selected
  // and stores them in the `params` object.
  query.updateAll = function( options ) {

    var fields,
        opts = options || {};

    switch( opts.source ) {
      case 'url':
        fields = pdp.app.getUrlValues();
        break;
      case 'session':
        fields = pdp.query.getCookie();
        break;
      default:
        fields = pdp.form.getFields();
    }

    this.reset();

    // Iterate over all the filter field values and push them into `query.params`.
    function _processField( field ) {

      if ( field.name && field.values ) {

        // Initalize an empty param object if need be.
        if ( typeof query.params[ field.name ] === 'undefined' ) {
          query.params[ field.name ] = {
            values: [],
            comparator: '='
          };
        }

        // If the value is a string from a text box we don't want to iterate
        // over it because it will split up the characters.
        if ( field.type === 'text' ) {
          query.params[ field.name ].values = [ field.values ];
        } else {
          _.forEach( field.values, function( val, name ){

            var values = query.params[ field.name ].values;

            // Only push the value if it's not already in there.
            if ( !_.contains( values, val ) ) {
              values.push( val );
            }

          });
        }

        query.params[ field.name ].comparator = field.comparator;

      }

    }

    _.forEach( fields, _processField );

    pdp.observer.emitEvent( 'params:updated' );

  };

  // The `setCookie` method stores the param object in a cookie.
  query.setCookie = function() {

    $.cookie( '_hmda', pdp.form.getFields() );

  };

  // The `getCookie` method retrives the param object from a cookie.
  query.getCookie = function() {

    return $.cookie( '_hmda' );

  };

  // The `generateUrlHash` method builds and returns a URL hash from `query`'s `params`.
  query.generateUrlHash = function() {
    var hash,
        hashParams = [];

    // Loop through params, stringify them and push them into the temp array.
    function buildHashParam( param, name ) {

      if ( !param.values[0] ) {
        return;
      }

      // If it's not a number, add quotes around the params.
      hashParams.push( name + param.comparator + param.values.join(',') );

    }
    _.forEach( query.params, buildHashParam );

    hash = '!/' + hashParams.join('&') + '&section=' + pdp.app.currentSection;
    return hash;

  };

  query.removeSelectParam = function (params) {
    //using a copy of the params means that the select obj
    //is still available on query.params for share url generation
    var paramsCopy = $.extend(true, {}, params);
    try {
      delete paramsCopy.clauses.where.select;
    } catch (e) {
      //nested property doesn't exist
    }
    delete paramsCopy.select;
    return paramsCopy;
  };

  // The `generateApiUrl` method builds and returns a Qu URL from `query`'s `params`.
  query.generateApiUrl = function( format, codes, params ) {

    var url,
        apiCallParams = params || this.params,
        showCodes = codes || this.codes,
        downloadFormat = format || this.format;
    //remove 'select' from params so it won't be added to where clause
    apiCallParams = query.removeSelectParam(apiCallParams);

    // Set a base url to append params to
    url = this.endpoint + 'slice/hmda_lar.' + downloadFormat + '?';

    if ( !showCodes ) {
      apiCallParams = {
        clauses: {
          where: apiCallParams,
          select: ['action_taken_name','agency_abbr','agency_name','applicant_ethnicity_name','applicant_race_name_1','applicant_race_name_2','applicant_race_name_3','applicant_race_name_4','applicant_race_name_5','applicant_sex_name','application_date_indicator','as_of_year','census_tract_number','co_applicant_ethnicity_name','co_applicant_race_name_1','co_applicant_race_name_2','co_applicant_race_name_3','co_applicant_race_name_4','co_applicant_race_name_5','co_applicant_sex_name','county_name','denial_reason_name_1','denial_reason_name_2','denial_reason_name_3','edit_status_name','hoepa_status_name','lien_status_name','loan_purpose_name','loan_type_name','msamd_name','owner_occupancy_name','preapproval_name','property_type_name','purchaser_type_name','respondent_id','sequence_number','state_abbr','state_name','applicant_income_000s','hud_median_family_income','loan_amount_000s','number_of_1_to_4_family_units','number_of_owner_occupied_units','minority_population','population','rate_spread','tract_to_msamd_income']
        }
      };
    }

    // fetch, compile queries
    url += this._buildApiQuery( apiCallParams );

    return url;

  };

  // builds the query string to append to api url
  // arg: params, object. if you only need a 'where' clause, passing in
  // query.params will do just fine
  // if you need to use the 'select' and 'group' clauses, pass an object
  // that has a property 'clauses' with an array of objects that correspond to
  // each clause. example:
  // params = {
  //   clauses: {
  //     select: ['var_one', 'var_two'],
  //     group: ['var_one', 'var_two']
  //   }
  // }
  query._buildApiQuery = function( params ) {
    var url = '', key;

    if ( params.hasOwnProperty('clauses') ) {
      for ( key in params.clauses ) {
        if ( params.clauses.hasOwnProperty( key ) ) {
          url += this._buildClause[key]( params.clauses[key] );
        }
      }
    } else {
      url = this._buildClause.where( params );
    }

    return url;

  };

  // methods correspond to each type of clause that the API takes
  // builds part of api call query string pertaining to clause
  query._buildClause = {

  // Convert each param to a proper [`$where` clause](http://cfpb.github.io/qu/articles/queries.html#where_in_detail).
    where: function( params ) {

      var _params = {},
          queryVals = [];

      // In order to compensate for enumerated location fields (state_code-1, county_name-1, etc.)
      // we have to go through and consolidate all enumerated params into unified objects.
      _.forEach( params, function( param, paramName ) {

        if ( !param.values || !param.values[0] ) {
          return;
        }

        var consolidatedName;

        if ( paramName.match(/\-\d+$/) ) {
          consolidatedName = paramName.replace(/\-\d+$/, '');

          // Initalize an empty param object if need be.
          if ( typeof _params[ consolidatedName ] === 'undefined' ) {
            _params[ consolidatedName ] = {
              values: [],
              comparator: '='
            };
          }

          _.forEach( param.values, function( value ){
            _params[ consolidatedName ].values.push( value );
          });

        } else {
          _params[ paramName ] = param;
        }

      });

      // We can now get back to business and generate that WERECLAWS
      _.forEach( _params, function( param, paramName ) {

        var paramVals;

        // Strip `-min/max` from the end of the param. This is mainly done for the loan_amount_000s fields.
        paramName = paramName.replace( /\-(min|max)$/, '' );
        paramVals = this._formatComparisonValues( param, paramName );
        queryVals.push( paramVals );

      }.bind( this ));

      // Join all the params with `AND` operators and append it to the base url,
      // replacing spaces with plus signs.
      return '&$where=' + encodeURI( queryVals.join(' AND ') ).replace( /%20/g, '+' );

    },

    // select and group clauses are formatted the same way sans name
    select: function( param ) {
      return '&$select=' + this._listVals( param );
    },

    group: function( param ) {
      return '&$group=' + this._listVals( param ) + '&$orderBy=' + this._listVals( _.clone( param ).reverse() );
    },

    // formats single value clauses
    // returns string of comma-delimited values
    _listVals: function( param ) {

      var i = param.length,

        str = '';

      while( i-- ) {
        if ( typeof param[i] !== 'undefined' ) {
          str += param[i];
          // if this is not the last value, add comma
          if ( i > 0 ) {
            str += ',';
          }
        }
      }

      return str;
    },

    // formats api call values that have a comparison operator
    _formatComparisonValues: function( param, paramName ){
      var paramVal,
          paramVals = [];

      // If there's only value for the param, meaning they only selected one item or
      // it's a radio button that only allows once value, add the stringified
      // param to the `queryVals` array.
      if ( param.values.length === 1 ) {

        // Rate spread specialness
        if ( param.values[0] == 'null' ) {
          paramVal = paramName + param.comparator + '""';
        // Strings
        } else if ( isNaN( param.values[0] ) || paramName === 'msamd' || paramName === 'respondent_id' || paramName === 'census_tract_number' ) {
          paramVal = paramName + param.comparator + '"' + param.values[0] + '"';
        // Numbers
        } else {
          paramVal = paramName + param.comparator + param.values[0];
        }

        return paramVal;

      // If there are multiple values for a single parameter, we iterate over them and
      // put an `OR` operator between them. We then then [group them](http://cfpb.github.io/qu/articles/queries.html#boolean_operators)
      // with parens and add the grouping to the `params` array.
      } else {

        _.forEach( param.values, function( val, key ){

          if ( isNaN( val ) || paramName === 'msamd' || paramName === 'respondent_id' || paramName === 'census_tract_number' ) {
            paramVals.push( paramName + param.comparator + '"' + val + '"' );
          } else {
            paramVals.push( paramName + param.comparator + val );
          }

        });

        return '(' + paramVals.join(' OR ') + ')';

      }

    }

  };

  // The `fetch` method requests and returns JSON from Qu matching the
  // user's filter and grouping clauses
  query.fetch = function() {

  };

  // Export the public API.
  pdp.query = query;

  return pdp;

}( PDP || {} ));
<|endoftext|>