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

/**
 * Smoothie Charts - http://smoothiecharts.org/
 * (c) 2010, Joe Walnes
 *
 * v1.0: Main charting library, by Joe Walnes
 * v1.1: Auto scaling of axis, by Neil Dunn
 * v1.2: fps (frames per second) option, by Mathias Petterson
 * v1.3: Fix for divide by zero, by Paul Nikitochkin
 */

function TimeSeries() {
  this.data = [];
  /**
   * The maximum value ever seen in this time series.
   */
  this.max = Number.NaN;
  /**
   * The minimum value ever seen in this time series.
   */
  this.min = Number.NaN;
}

TimeSeries.prototype.append = function(timestamp, value) {
  this.data.push([timestamp, value]);
  this.maxValue = !isNaN(this.maxValue) ? Math.max(this.maxValue, value) : value;
  this.minValue = !isNaN(this.minValue) ? Math.min(this.minValue, value) : value;
};

function SmoothieChart(options) {
  // Defaults
  options = options || {};
  options.grid = options.grid || { fillStyle:'#000000', strokeStyle: '#777777', lineWidth: 1, millisPerLine: 1000, verticalSections: 2 };
  options.millisPerPixel = options.millisPerPixel || 20;
  options.fps = options.fps || 20;
  options.maxValueScale = options.maxValueScale || 1;
  options.minValue = options.minValue;
  options.labels = options.labels || { fillStyle:'#ffffff' }
  this.options = options;
  this.seriesSet = [];
}

SmoothieChart.prototype.addTimeSeries = function(timeSeries, options) {
  this.seriesSet.push({timeSeries: timeSeries, options: options || {}});
};

SmoothieChart.prototype.removeTimeSeries = function(timeSeries) {
	this.seriesSet.splice(this.seriesSet.indexOf(timeSeries), 1);
};

SmoothieChart.prototype.streamTo = function(canvas, delay) {
  var self = this;
  (function render() {
    self.render(canvas, new Date().getTime() - (delay || 0));
    setTimeout(render, 1000/self.options.fps);
  })()
};

SmoothieChart.prototype.render = function(canvas, time) {
  var canvasContext = canvas.getContext("2d");
  var options = this.options;
  var dimensions = {top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight};

  // Save the state of the canvas context, any transformations applied in this method
  // will get removed from the stack at the end of this method when .restore() is called.
  canvasContext.save();

  // Round time down to pixel granularity, so motion appears smoother.
  time = time - time % options.millisPerPixel;

  // Move the origin.
  canvasContext.translate(dimensions.left, dimensions.top);

  // Create a clipped rectangle - anything we draw will be constrained to this rectangle.
  // This prevents the occasional pixels from curves near the edges overrunning and creating
  // screen cheese (that phrase should neeed no explanation).
  canvasContext.beginPath();
  canvasContext.rect(0, 0, dimensions.width, dimensions.height);
  canvasContext.clip();

  // Clear the working area.
  canvasContext.save();
  canvasContext.fillStyle = options.grid.fillStyle;
  canvasContext.fillRect(0, 0, dimensions.width, dimensions.height);
  canvasContext.restore();

  // Grid lines....
  canvasContext.save();
  canvasContext.lineWidth = options.grid.lineWidth || 1;
  canvasContext.strokeStyle = options.grid.strokeStyle || '#ffffff';
  // Vertical (time) dividers.
  if (options.grid.millisPerLine > 0) {
    for (var t = time - (time % options.grid.millisPerLine); t >= time - (dimensions.width * options.millisPerPixel); t -= options.grid.millisPerLine) {
      canvasContext.beginPath();
      var gx = Math.round(dimensions.width - ((time - t) / options.millisPerPixel));
      canvasContext.moveTo(gx, 0);
      canvasContext.lineTo(gx, dimensions.height);
      canvasContext.stroke();
      canvasContext.closePath();
    }
  }

  // Horizontal (value) dividers.
  for (var v = 1; v < options.grid.verticalSections; v++) {
    var gy = Math.round(v * dimensions.height / options.grid.verticalSections);
    canvasContext.beginPath();
    canvasContext.moveTo(0, gy);
    canvasContext.lineTo(dimensions.width, gy);
    canvasContext.stroke();
    canvasContext.closePath();
  }
  // Bounding rectangle.
  canvasContext.beginPath();
  canvasContext.strokeRect(0, 0, dimensions.width, dimensions.height);
  canvasContext.closePath();
  canvasContext.restore();

  // Calculate the current scale of the chart, from all time series.
  var maxValue = Number.NaN;
  var minValue = Number.NaN;

  for (var d = 0; d < this.seriesSet.length; d++) {
      // TODO(ndunn): We could calculate / track these values as they stream in.
      var timeSeries = this.seriesSet[d].timeSeries;
      if (!isNaN(timeSeries.maxValue)) {
          maxValue = !isNaN(maxValue) ? Math.max(maxValue, timeSeries.maxValue) : timeSeries.maxValue;
      }

      if (!isNaN(timeSeries.minValue)) {
          minValue = !isNaN(minValue) ? Math.min(minValue, timeSeries.minValue) : timeSeries.minValue;
      }
  }

  if (isNaN(maxValue) && isNaN(minValue)) {
      return;
  }

  // Scale the maxValue to add padding at the top if required
  maxValue = maxValue * options.maxValueScale;
  // Set the minimum if we've specified one
  if (options.minValue != null)
    minValue = options.minValue;
  var valueRange = maxValue - minValue;

  // For each data set...
  for (var d = 0; d < this.seriesSet.length; d++) {
    canvasContext.save();
    var timeSeries = this.seriesSet[d].timeSeries;
    var dataSet = timeSeries.data;
    var seriesOptions = this.seriesSet[d].options;

    // Delete old data that's moved off the left of the chart.
    // We must always keep the last expired data point as we need this to draw the
    // line that comes into the chart, but any points prior to that can be removed.
    while (dataSet.length >= 2 && dataSet[1][0] < time - (dimensions.width * options.millisPerPixel)) {
      dataSet.splice(0, 1);
    }

    // Set style for this dataSet.
    canvasContext.lineWidth = seriesOptions.lineWidth || 1;
    canvasContext.fillStyle = seriesOptions.fillStyle;
    canvasContext.strokeStyle = seriesOptions.strokeStyle || '#ffffff';
    // Draw the line...
    canvasContext.beginPath();
    // Retain lastX, lastY for calculating the control points of bezier curves.
    var firstX = 0, lastX = 0, lastY = 0;
    for (var i = 0; i < dataSet.length; i++) {
      // TODO: Deal with dataSet.length < 2.
      var x = Math.round(dimensions.width - ((time - dataSet[i][0]) / options.millisPerPixel));
      var value = dataSet[i][1];
      var offset = maxValue - value;
      var scaledValue = valueRange ? Math.round((offset / valueRange) * dimensions.height) : 0;
      var y = Math.max(Math.min(scaledValue, dimensions.height - 1), 1); // Ensure line is always on chart.

      if (i == 0) {
        firstX = x;
        canvasContext.moveTo(x, y);
      }
      // Great explanation of Bezier curves: http://en.wikipedia.org/wiki/B�zier_curve#Quadratic_curves
      //
      // Assuming A was the last point in the line plotted and B is the new point,
      // we draw a curve with control points P and Q as below.
      //
      // A---P
      //     |
      //     |
      //     |
      //     Q---B
      //
      // Importantly, A and P are at the same y coordinate, as are B and Q. This is
      // so adjacent curves appear to flow as one.
      //
      else {
        canvasContext.bezierCurveTo( // startPoint (A) is implicit from last iteration of loop
          Math.round((lastX + x) / 2), lastY, // controlPoint1 (P)
          Math.round((lastX + x)) / 2, y, // controlPoint2 (Q)
          x, y); // endPoint (B)
      }

      lastX = x, lastY = y;
    }
    if (dataSet.length > 0 && seriesOptions.fillStyle) {
      // Close up the fill region.
      canvasContext.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY);
      canvasContext.lineTo(dimensions.width + seriesOptions.lineWidth + 1, dimensions.height + seriesOptions.lineWidth + 1);
      canvasContext.lineTo(firstX, dimensions.height + seriesOptions.lineWidth);
      canvasContext.fill();
    }
    canvasContext.stroke();
    canvasContext.closePath();
    canvasContext.restore();
  }

  // Draw the axis values on the chart.
  if (!options.labels.disabled) {
      canvasContext.fillStyle = options.labels.fillStyle;
      var maxValueString = maxValue.toFixed(2);
      var minValueString = minValue.toFixed(2);
      canvasContext.fillText(maxValueString, dimensions.width - canvasContext.measureText(maxValueString).width - 2, 10);
      canvasContext.fillText(minValueString, dimensions.width - canvasContext.measureText(minValueString).width - 2, dimensions.height - 2);
  }

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

/**
 * Smoothie Charts - http://smoothiecharts.org/
 * (c) 2010, Joe Walnes
 *
 * v1.0: Main charting library, by Joe Walnes
 * v1.1: Auto scaling of axis, by Neil Dunn
 * v1.2: fps (frames per second) option, by Mathias Petterson
 * v1.3: Fix for divide by zero, by Paul Nikitochkin
 * v1.4: Set minimum, top-scale padding, remove timeseries, add optional timer to reset bounds, by Kelley Reynolds
 */

function TimeSeries(options) {
  options = options || {};
  options.resetBoundsInterval = options.resetBoundsInterval || 3000; // Reset the max/min bounds after this many milliseconds
  options.resetBounds = options.resetBounds || true; // Enable or disable the resetBounds timer
  this.options = options;
  this.data = [];

  this.maxValue = Number.NaN; // The maximum value ever seen in this time series.
  this.minValue = Number.NaN; // The minimum value ever seen in this time series.

  // Start a resetBounds Interval timer desired
  if (options.resetBounds) {
    this.boundsTimer = setInterval(function(thisObj) { thisObj.resetBounds(); }, options.resetBoundsInterval, this);
  }
}

// Reset the min and max for this timeseries so the graph rescales itself
TimeSeries.prototype.resetBounds = function() {
  this.maxValue = Number.NaN;
  this.minValue = Number.NaN;
  for (var i = 0; i < this.data.length; i++) {
    this.maxValue = !isNaN(this.maxValue) ? Math.max(this.maxValue, this.data[i][1]) : this.data[i][1];
    this.minValue = !isNaN(this.minValue) ? Math.min(this.minValue, this.data[i][1]) : this.data[i][1];
  }
};

TimeSeries.prototype.append = function(timestamp, value) {
  this.data.push([timestamp, value]);
  this.maxValue = !isNaN(this.maxValue) ? Math.max(this.maxValue, value) : value;
  this.minValue = !isNaN(this.minValue) ? Math.min(this.minValue, value) : value;
};

function SmoothieChart(options) {
  // Defaults
  options = options || {};
  options.grid = options.grid || { fillStyle:'#000000', strokeStyle: '#777777', lineWidth: 1, millisPerLine: 1000, verticalSections: 2 };
  options.millisPerPixel = options.millisPerPixel || 20;
  options.fps = options.fps || 20;
  options.maxValueScale = options.maxValueScale || 1;
  options.minValue = options.minValue;
  options.labels = options.labels || { fillStyle:'#ffffff' }
  this.options = options;
  this.seriesSet = [];
}

SmoothieChart.prototype.addTimeSeries = function(timeSeries, options) {
  this.seriesSet.push({timeSeries: timeSeries, options: options || {}});
};

SmoothieChart.prototype.removeTimeSeries = function(timeSeries) {
	this.seriesSet.splice(this.seriesSet.indexOf(timeSeries), 1);
};

SmoothieChart.prototype.streamTo = function(canvas, delay) {
  var self = this;
  (function render() {
    self.render(canvas, new Date().getTime() - (delay || 0));
    setTimeout(render, 1000/self.options.fps);
  })()
};

SmoothieChart.prototype.render = function(canvas, time) {
  var canvasContext = canvas.getContext("2d");
  var options = this.options;
  var dimensions = {top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight};

  // Save the state of the canvas context, any transformations applied in this method
  // will get removed from the stack at the end of this method when .restore() is called.
  canvasContext.save();

  // Round time down to pixel granularity, so motion appears smoother.
  time = time - time % options.millisPerPixel;

  // Move the origin.
  canvasContext.translate(dimensions.left, dimensions.top);

  // Create a clipped rectangle - anything we draw will be constrained to this rectangle.
  // This prevents the occasional pixels from curves near the edges overrunning and creating
  // screen cheese (that phrase should neeed no explanation).
  canvasContext.beginPath();
  canvasContext.rect(0, 0, dimensions.width, dimensions.height);
  canvasContext.clip();

  // Clear the working area.
  canvasContext.save();
  canvasContext.fillStyle = options.grid.fillStyle;
  canvasContext.fillRect(0, 0, dimensions.width, dimensions.height);
  canvasContext.restore();

  // Grid lines....
  canvasContext.save();
  canvasContext.lineWidth = options.grid.lineWidth || 1;
  canvasContext.strokeStyle = options.grid.strokeStyle || '#ffffff';
  // Vertical (time) dividers.
  if (options.grid.millisPerLine > 0) {
    for (var t = time - (time % options.grid.millisPerLine); t >= time - (dimensions.width * options.millisPerPixel); t -= options.grid.millisPerLine) {
      canvasContext.beginPath();
      var gx = Math.round(dimensions.width - ((time - t) / options.millisPerPixel));
      canvasContext.moveTo(gx, 0);
      canvasContext.lineTo(gx, dimensions.height);
      canvasContext.stroke();
      canvasContext.closePath();
    }
  }

  // Horizontal (value) dividers.
  for (var v = 1; v < options.grid.verticalSections; v++) {
    var gy = Math.round(v * dimensions.height / options.grid.verticalSections);
    canvasContext.beginPath();
    canvasContext.moveTo(0, gy);
    canvasContext.lineTo(dimensions.width, gy);
    canvasContext.stroke();
    canvasContext.closePath();
  }
  // Bounding rectangle.
  canvasContext.beginPath();
  canvasContext.strokeRect(0, 0, dimensions.width, dimensions.height);
  canvasContext.closePath();
  canvasContext.restore();

  // Calculate the current scale of the chart, from all time series.
  var maxValue = Number.NaN;
  var minValue = Number.NaN;

  for (var d = 0; d < this.seriesSet.length; d++) {
      // TODO(ndunn): We could calculate / track these values as they stream in.
      var timeSeries = this.seriesSet[d].timeSeries;
      if (!isNaN(timeSeries.maxValue)) {
          maxValue = !isNaN(maxValue) ? Math.max(maxValue, timeSeries.maxValue) : timeSeries.maxValue;
      }

      if (!isNaN(timeSeries.minValue)) {
          minValue = !isNaN(minValue) ? Math.min(minValue, timeSeries.minValue) : timeSeries.minValue;
      }
  }

  if (isNaN(maxValue) && isNaN(minValue)) {
      return;
  }

  // Scale the maxValue to add padding at the top if required
  maxValue = maxValue * options.maxValueScale;
  // Set the minimum if we've specified one
  if (options.minValue != null)
    minValue = options.minValue;
  var valueRange = maxValue - minValue;

  // For each data set...
  for (var d = 0; d < this.seriesSet.length; d++) {
    canvasContext.save();
    var timeSeries = this.seriesSet[d].timeSeries;
    var dataSet = timeSeries.data;
    var seriesOptions = this.seriesSet[d].options;

    // Delete old data that's moved off the left of the chart.
    // We must always keep the last expired data point as we need this to draw the
    // line that comes into the chart, but any points prior to that can be removed.
    while (dataSet.length >= 2 && dataSet[1][0] < time - (dimensions.width * options.millisPerPixel)) {
      dataSet.splice(0, 1);
    }

    // Set style for this dataSet.
    canvasContext.lineWidth = seriesOptions.lineWidth || 1;
    canvasContext.fillStyle = seriesOptions.fillStyle;
    canvasContext.strokeStyle = seriesOptions.strokeStyle || '#ffffff';
    // Draw the line...
    canvasContext.beginPath();
    // Retain lastX, lastY for calculating the control points of bezier curves.
    var firstX = 0, lastX = 0, lastY = 0;
    for (var i = 0; i < dataSet.length; i++) {
      // TODO: Deal with dataSet.length < 2.
      var x = Math.round(dimensions.width - ((time - dataSet[i][0]) / options.millisPerPixel));
      var value = dataSet[i][1];
      var offset = maxValue - value;
      var scaledValue = valueRange ? Math.round((offset / valueRange) * dimensions.height) : 0;
      var y = Math.max(Math.min(scaledValue, dimensions.height - 1), 1); // Ensure line is always on chart.

      if (i == 0) {
        firstX = x;
        canvasContext.moveTo(x, y);
      }
      // Great explanation of Bezier curves: http://en.wikipedia.org/wiki/B�zier_curve#Quadratic_curves
      //
      // Assuming A was the last point in the line plotted and B is the new point,
      // we draw a curve with control points P and Q as below.
      //
      // A---P
      //     |
      //     |
      //     |
      //     Q---B
      //
      // Importantly, A and P are at the same y coordinate, as are B and Q. This is
      // so adjacent curves appear to flow as one.
      //
      else {
        canvasContext.bezierCurveTo( // startPoint (A) is implicit from last iteration of loop
          Math.round((lastX + x) / 2), lastY, // controlPoint1 (P)
          Math.round((lastX + x)) / 2, y, // controlPoint2 (Q)
          x, y); // endPoint (B)
      }

      lastX = x, lastY = y;
    }
    if (dataSet.length > 0 && seriesOptions.fillStyle) {
      // Close up the fill region.
      canvasContext.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY);
      canvasContext.lineTo(dimensions.width + seriesOptions.lineWidth + 1, dimensions.height + seriesOptions.lineWidth + 1);
      canvasContext.lineTo(firstX, dimensions.height + seriesOptions.lineWidth);
      canvasContext.fill();
    }
    canvasContext.stroke();
    canvasContext.closePath();
    canvasContext.restore();
  }

  // Draw the axis values on the chart.
  if (!options.labels.disabled) {
      canvasContext.fillStyle = options.labels.fillStyle;
      var maxValueString = maxValue.toFixed(2);
      var minValueString = minValue.toFixed(2);
      canvasContext.fillText(maxValueString, dimensions.width - canvasContext.measureText(maxValueString).width - 2, 10);
      canvasContext.fillText(minValueString, dimensions.width - canvasContext.measureText(minValueString).width - 2, dimensions.height - 2);
  }

  canvasContext.restore(); // See .save() above.
}
<|endoftext|>


<commit_before>(function () {
	'use strict';

	$.easing.easeInOutQuint = function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
		return c/2*((t-=2)*t*t*t*t + 2) + b;
	};

	// smooth scroll
	$('a[href*="#"]:not([href="#"])').click(function () {
		if (location.pathname.replace(/^\//,'') === this.pathname.replace(/^\//,'') && location.hostname === this.hostname) {
			var target = $(this.hash);
			target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
			if (target.length) {
				$('html, body').animate({
					scrollTop: target.offset().top
				}, 1000, 'easeInOutQuint');
				return false;
			}
		}
	});

	$('.email').attr('href', 'mailto:' + 'ben' + '.' + 'kremer' + '@' + 'protonmail' + '.' + 'com');

	// $('.profile a').on('mouseover mouseout', function (e) {
	// 	$(this).toggleClass('animated tada', e.type === 'mouseover');
	// });

	setTimeout(function () {
		$('.credit').addClass('activate');
	}, 1000);
})();
<commit_msg>Generate mailto dynamically<commit_after>(function () {
	'use strict';

	function generateMailTo() {
		var mailto = 'mailto:' + 'ben' + '.' + 'kremer' + '@' + 'protonmail' + '.' + 'com';
		var subject = '?subject=' + 'Hey Ben!';
		return mailto + subject;
	}

	$.easing.easeInOutQuint = function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
		return c/2*((t-=2)*t*t*t*t + 2) + b;
	};

	// smooth scroll
	$('a[href*="#"]:not([href="#"])').click(function () {
		if (location.pathname.replace(/^\//,'') === this.pathname.replace(/^\//,'') && location.hostname === this.hostname) {
			var target = $(this.hash);
			target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
			if (target.length) {
				$('html, body').animate({
					scrollTop: target.offset().top
				}, 1000, 'easeInOutQuint');
				return false;
			}
		}
	});

	$('.email').attr('href', generateMailTo());

	// $('.profile a').on('mouseover mouseout', function (e) {
	// 	$(this).toggleClass('animated tada', e.type === 'mouseover');
	// });

	setTimeout(function () {
		$('.credit').addClass('activate');
	}, 1000);
})();
<|endoftext|>


<commit_before>
var MetricFTW = MetricFTW || {

    // http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/
    filterImperial: function(searchNode) {
        var valueReg = "(\\d[0-9\\/\\,\\.\\s]+\\s*)";
        var nameReg = MetricFTW.generateNameReg();
        var reg = new RegExp(valueReg + nameReg, "gi");

        var childNodes = (searchNode || document.body).childNodes,
            excludes = 'html,head,style,title,link,meta,script,object,iframe',
            cnLength = childNodes.length,
            peasantryFound = 0;

        while (cnLength--) {
            var currentNode = childNodes[cnLength];
            if (currentNode.nodeType === 1
                && (excludes + ',').indexOf(currentNode.nodeName.toLowerCase() + ',') === -1) {
                peasantryFound += arguments.callee(currentNode);
            }

            m = reg.exec(currentNode.data);
            if (currentNode.nodeType !== 3 || m === null) {
                continue;
            }

            // reset regex index when getting multiple matches
            reg.lastIndex = 0;

            peasantryFound++;
            metric = MetricFTW.convertToMetric(m);

            // I'll add a star cause I'll forget I have it turned on if it changes something
            replacement = metric.value.toString() + " " + metric.name + "*";

            // replace the peasantry with metric values
            var parent = currentNode.parentNode,
                frag = (function() {
                    var html = currentNode.data.replace(reg, replacement),
                        wrap = document.createElement('div'),
                        frag = document.createDocumentFragment();
                    wrap.innerHTML = html;
                    while (wrap.firstChild) {
                        frag.appendChild(wrap.firstChild);
                    }
                    return frag;
                })();
            parent.insertBefore(frag, currentNode);
            parent.removeChild(currentNode);
        }
        return peasantryFound;
    },

    // http://www.initium.demon.co.uk/converts/metimp.htm
    convertToMetric: function(match) {
        name = this.removeWhitespace(match[2].toLowerCase());
        value = this.parseNumeric(match[1]);
        var newName = "" , newValue = 0;

        var found = false;
        var dictLen = this.peasant_table.length;
        for(var i = 0; i < dictLen; i++) {
            var abbr = this.peasant_table[i].aliases.split(",");
            for(var j = 0; j < abbr.length; j++) {
                if (name === abbr[j]) {
                    found = true;
                    return {
                        name: this.peasant_table[i].name,
                        value: this.peasant_table[i].convert(value).toFixed(2)
                    }
                    break;
                }
            }
            if (found) {
                break;
            }
        }

        if (!found) {
            console.log("this can't happen, right?");
        }

        return null;
    },

    removeWhitespace: function(string) {
        return string.replace(/ /g, "");
    },

    // http://docs.oracle.com/cd/E19455-01/806-0169/overview-9/index.html
    parseNumeric: function(numString) {
        var re = /[\\ \\,]+/gi;
        numString = numString.replace(re, ""); // parseInt doesn't remove ','
        return parseFloat(numString);
    },

    generateNameReg: function() {
        var aliasarr = [];
        var dictLen = this.peasant_table.length;
        for(var i = 0; i < dictLen; i++) {
            var abbr = this.peasant_table[i].aliases.split(",");
            var abbrlen = abbr.length;
            for(var j = 0; j < abbrlen; j++) {
                aliasarr.push(abbr[j]);
            }
        }
        return "(" + aliasarr.join("|") + ")";
    },

    // http://www.initium.demon.co.uk/converts/metimp.htm
    //
    // Much thanks to Rocket for providing a list of units and measurements!
    // https://github.com/cpancake/unitconvert/blob/master/default.json
    peasant_table: [
        { name: "kg", aliases: "pounds,pound,lbs,lb", convert: function(value) { return value * 0.4536; }  },
        { name: "grams", aliases: "ounces,ounce,oz", convert: function(value) { return value * 28.41; }    },
        { name: "millimeters", aliases: "inches,inch", convert: function(value) { return value * 25.4; }   },
        { name: "centimeters", aliases: "foot,feet,ft", convert: function(value) { return value * 30.48; } },
        { name: "kilometers", aliases: "miles,mile", convert: function(value) { return value * 1.609; }    },
        { name: "meters", aliases: "yards,yard", convert: function(value) { return value * 0.9144; }       }
    ]
}

var init = function() {
    var numActs =  MetricFTW.filterImperial();
    console.log("Removed " + numActs.toString() + " act(s) of peasantry!");
    return numActs;
};

chrome.extension.sendMessage({
    action: "filterImperial",
    actsOfPeasantry: init()
});<commit_msg>ran through JSHint, it's a miracle this code runs at all<commit_after>// JSHint wants me to use this or I'll get 'too many errors'
"use strict";

var MetricFTW = MetricFTW || {

    // http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/
    filterImperial: function(searchNode) {
        var valueReg = "(\\d[0-9\\/\\,\\.\\s]+\\s*)";
        var nameReg = MetricFTW.generateNameReg();
        var reg = new RegExp(valueReg + nameReg, "gi");

        var childNodes = (searchNode || document.body).childNodes,
            excludes = "html,head,style,title,link,meta,script,object,iframe",
            cnLength = childNodes.length,
            peasantryFound = 0;

        while (cnLength--) {
            var currentNode = childNodes[cnLength];
            if (currentNode.nodeType === 1 && (excludes + ",").indexOf(currentNode.nodeName.toLowerCase() + ",") === -1) {
                peasantryFound += MetricFTW.filterImperial(currentNode);
            }

            var m = reg.exec(currentNode.data);
            if (currentNode.nodeType !== 3 || m === null) {
                continue;
            }

            // reset regex index when getting multiple matches
            reg.lastIndex = 0;

            peasantryFound++;
            var metric = MetricFTW.convertToMetric(m);

            // I'll add a star cause I'll forget I have it turned on if it changes something
            var replacement = metric.value.toString() + " " + metric.name + "*";

            // replace the peasantry with metric values
            var parent = currentNode.parentNode,
                frag = (function() {
                    var html = currentNode.data.replace(reg, replacement),
                        wrap = document.createElement("div"),
                        frag = document.createDocumentFragment();
                    wrap.innerHTML = html;
                    while (wrap.firstChild) {
                        frag.appendChild(wrap.firstChild);
                    }
                    return frag;
                })();
            parent.insertBefore(frag, currentNode);
            parent.removeChild(currentNode);
        }
        return peasantryFound;
    },

    // http://www.initium.demon.co.uk/converts/metimp.htm
    convertToMetric: function(match) {
        var name = this.removeWhitespace(match[2].toLowerCase());
        var value = this.parseNumeric(match[1]);

        var found = false;
        var dictLen = this.peasant_table.length;
        for(var i = 0; i < dictLen; i++) {
            var abbr = this.peasant_table[i].aliases.split(",");
            for(var j = 0; j < abbr.length; j++) {
                if (name === abbr[j]) {
                    found = true;
                    return {
                        name: this.peasant_table[i].name,
                        value: this.peasant_table[i].convert(value).toFixed(2)
                    };
                }
            }
            if (found) {
                break;
            }
        }

        if (!found) {
            console.log("this can't happen, right?");
        }

        return null;
    },

    removeWhitespace: function(string) {
        return string.replace(/ /g, "");
    },

    // http://docs.oracle.com/cd/E19455-01/806-0169/overview-9/index.html
    parseNumeric: function(numString) {
        var re = /[\\ \\,]+/gi;
        numString = numString.replace(re, ""); // parseInt doesn't remove ','
        return parseFloat(numString);
    },

    generateNameReg: function() {
        var aliasarr = [];
        var dictLen = this.peasant_table.length;
        for(var i = 0; i < dictLen; i++) {
            var abbr = this.peasant_table[i].aliases.split(",");
            var abbrlen = abbr.length;
            for(var j = 0; j < abbrlen; j++) {
                aliasarr.push(abbr[j]);
            }
        }
        return "(" + aliasarr.join("|") + ")";
    },

    // http://www.initium.demon.co.uk/converts/metimp.htm
    //
    // Much thanks to Rocket for providing a list of units and measurements!
    // https://github.com/cpancake/unitconvert/blob/master/default.json
    peasant_table: [
        { name: "kg", aliases: "pounds,pound,lbs,lb", convert: function(value) { return value * 0.4536; }  },
        { name: "grams", aliases: "ounces,ounce,oz", convert: function(value) { return value * 28.41; }    },
        { name: "millimeters", aliases: "inches,inch", convert: function(value) { return value * 25.4; }   },
        { name: "centimeters", aliases: "foot,feet,ft", convert: function(value) { return value * 30.48; } },
        { name: "kilometers", aliases: "miles,mile", convert: function(value) { return value * 1.609; }    },
        { name: "meters", aliases: "yards,yard", convert: function(value) { return value * 0.9144; }       }
    ]
};

function init() {
    var numActs =  MetricFTW.filterImperial();
    console.log("Removed " + numActs.toString() + " act(s) of peasantry!");
    return numActs;
}

chrome.extension.sendMessage({
    action: "filterImperial",
    actsOfPeasantry: init()
});<|endoftext|>


<commit_before>var generators = require('yeoman-generator');
var path = require('path');
var _ = require('lodash');
var npmVersion = require('../lib/utils').npmVersion;
var npmOptions = function(options){
  return _.extend({ loglevel: 'error' }, options);
};

module.exports = generators.Base.extend({
  initializing: function () {
    this.pkg = this.fs.readJSON(this.destinationPath('package.json'), {});

    // Pre set the default props from the information we have at this point
    this.props = {
      name: this.pkg.name,
      description: this.pkg.description,
      version: this.pkg.version,
      homepage: this.pkg.homepage,
      repository: this.pkg.repository
    };

    this.mainFiles = [
      'documentjs.json',
      'readme.md',
      '_gitignore'
    ];

    this.srcFiles = [
      'test/test.html',
      'test/test.js',
      'plugin_test.js',
      'plugin.js',
      'plugin.md'
    ];
  },

  prompting: function () {
    var done = this.async();

    npmVersion(function(err, version){
      if(err) {
        done(err);
        return;
      }

      var prompts = [{
        name: 'name',
        message: 'Project name',
        when: !this.pkg.name,
        default: process.cwd().split(path.sep).pop()
      }, {
        name: 'folder',
        message: 'Project main folder',
        default: 'src'
      }, {
        name: 'description',
        message: 'Description',
        when: !this.pkg.description
      }, {
        name: 'homepage',
        message: 'Project homepage url',
        when: !this.pkg.homepage
      }, {
        name: 'githubAccount',
        message: 'GitHub username or organization',
        when: !this.pkg.repository
      }, {
        name: 'authorName',
        message: 'Author\'s Name',
        when: !this.pkg.author,
        store: true
      }, {
        name: 'authorEmail',
        message: 'Author\'s Email',
        when: !this.pkg.author,
        store: true
      }, {
        name: 'authorUrl',
        message: 'Author\'s Homepage',
        when: !this.pkg.author,
        store: true
      }, {
        name: 'keywords',
        message: 'Application keywords',
        when: !this.pkg.keywords,
        filter: _.words
      }, {
        name: 'npmVersion',
        message: 'NPM version used',
        default: version.major
      }];

      this.prompt(prompts, function (props) {
        this.props = _.extend(this.props, props);
        this.props.name = _.kebabCase(this.props.name);
        done();
      }.bind(this));
    }.bind(this));
  },

  writing: function () {
    var self = this;
    var pkgJsonFields = {
      name: this.props.name,
      version: '0.0.0',
      description: this.props.description,
      homepage: this.props.homepage,
      repository: this.props.repository,
      author: {
        name: this.props.authorName,
        email: this.props.authorEmail,
        url: this.props.authorUrl
      },
      "scripts": {
        preversion: "npm test && npm run build",
        version: "git commit -am \"Update dist for release\" && git checkout -b release && git add -f dist/",
        postversion: "git push --tags && git checkout master && git branch -D release && git push",
        testee: "testee src/test/test.html --browsers firefox",
        test: "npm run jshint && npm run testee",
        jshint: "jshint src/. --config",
        "release:patch": "npm version patch && npm publish",
        "release:minor": "npm version minor && npm publish",
        "release:major": "npm version major && npm publish",
        build: "node build.js",
        document: "documentjs",
        develop: "can-serve --static --develop --port 8080"
      },
      main: "dist/cjs/" + this.props.name,
      browser: {
       transform: [ "cssify" ]
      },
      browserify: {
       transform: [ "cssify" ]
      },
      keywords: this.props.keywords,
      system: {
        main: this.props.name,
        directories: {
          lib: this.props.folder
        },
        configDependencies: [ 'live-reload' ],
        npmIgnore: [
          'documentjs',
          'testee',
          'generator-donejs',
          'donejs-cli',
          'steal-tools'
        ]
      }
    };

    if(this.props.npmVersion >= 3) {
      pkgJsonFields.system.npmAlgorithm = 'flat';
    }

    if(!this.options.packages) {
      throw new Error('No DoneJS dependency package list provided!');
    }

    this.log('Writing package.json v' + this.options.version);

    var getDependency = function(name) {
      return self.options.packages.dependencies[name] ||
        self.options.packages.devDependencies[name];
    };

    this.fs.writeJSON('package.json', _.extend(pkgJsonFields, this.pkg, {
      dependencies: {
        'can': getDependency('can'),
        'jquery': getDependency('jquery')
      },
      devDependencies: {
        'documentjs': getDependency('documentjs'),
        'jshint': '^2.9.1',
        'cssify': '^0.6.0',
        'steal': getDependency('steal'),
        'steal-qunit': getDependency('steal-qunit'),
        'steal-tools': getDependency('steal-tools'),
        'testee': getDependency('testee'),
        'generator-donejs': getDependency('generator-donejs'),
        'donejs-cli': getDependency('donejs-cli'),
      }
    }));

    this.fs.copy(this.templatePath('static'), this.destinationPath());
    this.fs.copy(this.templatePath('static/.*'), this.destinationPath());

    this.mainFiles.forEach(function(name) {
      // Handle bug where npm has renamed .gitignore to .npmignore
      // https://github.com/npm/npm/issues/3763
      self.fs.copyTpl(
        self.templatePath(name),
        self.destinationPath((name === "_gitignore") ? ".gitignore" : name),
        self.props
      );
    });

    this.srcFiles.forEach(function(name) {
      self.fs.copyTpl(
        self.templatePath(name),
        self.destinationPath(path.join(self.props.folder, name.replace('plugin', self.props.name))),
        self.props
      );
    });
  },

  end: function () {
    if(!this.options.skipInstall) {
      var done = this.async();
      this.spawnCommand('npm', ['--loglevel', 'error', 'install']).on('close', done);
    }
  }
});
<commit_msg>Adding can-ssr dependency back to plugin generator<commit_after>var generators = require('yeoman-generator');
var path = require('path');
var _ = require('lodash');
var npmVersion = require('../lib/utils').npmVersion;
var npmOptions = function(options){
  return _.extend({ loglevel: 'error' }, options);
};

module.exports = generators.Base.extend({
  initializing: function () {
    this.pkg = this.fs.readJSON(this.destinationPath('package.json'), {});

    // Pre set the default props from the information we have at this point
    this.props = {
      name: this.pkg.name,
      description: this.pkg.description,
      version: this.pkg.version,
      homepage: this.pkg.homepage,
      repository: this.pkg.repository
    };

    this.mainFiles = [
      'documentjs.json',
      'readme.md',
      '_gitignore'
    ];

    this.srcFiles = [
      'test/test.html',
      'test/test.js',
      'plugin_test.js',
      'plugin.js',
      'plugin.md'
    ];
  },

  prompting: function () {
    var done = this.async();

    npmVersion(function(err, version){
      if(err) {
        done(err);
        return;
      }

      var prompts = [{
        name: 'name',
        message: 'Project name',
        when: !this.pkg.name,
        default: process.cwd().split(path.sep).pop()
      }, {
        name: 'folder',
        message: 'Project main folder',
        default: 'src'
      }, {
        name: 'description',
        message: 'Description',
        when: !this.pkg.description
      }, {
        name: 'homepage',
        message: 'Project homepage url',
        when: !this.pkg.homepage
      }, {
        name: 'githubAccount',
        message: 'GitHub username or organization',
        when: !this.pkg.repository
      }, {
        name: 'authorName',
        message: 'Author\'s Name',
        when: !this.pkg.author,
        store: true
      }, {
        name: 'authorEmail',
        message: 'Author\'s Email',
        when: !this.pkg.author,
        store: true
      }, {
        name: 'authorUrl',
        message: 'Author\'s Homepage',
        when: !this.pkg.author,
        store: true
      }, {
        name: 'keywords',
        message: 'Application keywords',
        when: !this.pkg.keywords,
        filter: _.words
      }, {
        name: 'npmVersion',
        message: 'NPM version used',
        default: version.major
      }];

      this.prompt(prompts, function (props) {
        this.props = _.extend(this.props, props);
        this.props.name = _.kebabCase(this.props.name);
        done();
      }.bind(this));
    }.bind(this));
  },

  writing: function () {
    var self = this;
    var pkgJsonFields = {
      name: this.props.name,
      version: '0.0.0',
      description: this.props.description,
      homepage: this.props.homepage,
      repository: this.props.repository,
      author: {
        name: this.props.authorName,
        email: this.props.authorEmail,
        url: this.props.authorUrl
      },
      "scripts": {
        preversion: "npm test && npm run build",
        version: "git commit -am \"Update dist for release\" && git checkout -b release && git add -f dist/",
        postversion: "git push --tags && git checkout master && git branch -D release && git push",
        testee: "testee src/test/test.html --browsers firefox",
        test: "npm run jshint && npm run testee",
        jshint: "jshint src/. --config",
        "release:patch": "npm version patch && npm publish",
        "release:minor": "npm version minor && npm publish",
        "release:major": "npm version major && npm publish",
        build: "node build.js",
        document: "documentjs",
        develop: "can-serve --static --develop --port 8080"
      },
      main: "dist/cjs/" + this.props.name,
      browser: {
       transform: [ "cssify" ]
      },
      browserify: {
       transform: [ "cssify" ]
      },
      keywords: this.props.keywords,
      system: {
        main: this.props.name,
        directories: {
          lib: this.props.folder
        },
        configDependencies: [ 'live-reload' ],
        npmIgnore: [
          'documentjs',
          'testee',
          'generator-donejs',
          'donejs-cli',
          'steal-tools'
        ]
      }
    };

    if(this.props.npmVersion >= 3) {
      pkgJsonFields.system.npmAlgorithm = 'flat';
    }

    if(!this.options.packages) {
      throw new Error('No DoneJS dependency package list provided!');
    }

    this.log('Writing package.json v' + this.options.version);

    var getDependency = function(name) {
      return self.options.packages.dependencies[name] ||
        self.options.packages.devDependencies[name];
    };

    this.fs.writeJSON('package.json', _.extend(pkgJsonFields, this.pkg, {
      dependencies: {
        'can': getDependency('can'),
        'jquery': getDependency('jquery')
      },
      devDependencies: {
        'documentjs': getDependency('documentjs'),
        'jshint': '^2.9.1',
        'cssify': '^0.6.0',
        'steal': getDependency('steal'),
        'steal-qunit': getDependency('steal-qunit'),
        'steal-tools': getDependency('steal-tools'),
        'testee': getDependency('testee'),
        'generator-donejs': getDependency('generator-donejs'),
        'donejs-cli': getDependency('donejs-cli'),
        'can-ssr': getDependency('can-ssr')
      }
    }));

    this.fs.copy(this.templatePath('static'), this.destinationPath());
    this.fs.copy(this.templatePath('static/.*'), this.destinationPath());

    this.mainFiles.forEach(function(name) {
      // Handle bug where npm has renamed .gitignore to .npmignore
      // https://github.com/npm/npm/issues/3763
      self.fs.copyTpl(
        self.templatePath(name),
        self.destinationPath((name === "_gitignore") ? ".gitignore" : name),
        self.props
      );
    });

    this.srcFiles.forEach(function(name) {
      self.fs.copyTpl(
        self.templatePath(name),
        self.destinationPath(path.join(self.props.folder, name.replace('plugin', self.props.name))),
        self.props
      );
    });
  },

  end: function () {
    if(!this.options.skipInstall) {
      var done = this.async();
      this.spawnCommand('npm', ['--loglevel', 'error', 'install']).on('close', done);
    }
  }
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * 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.
 */

foam.CLASS({
  package: 'foam.dao',
  name: 'ProxyDAO',
  extends: 'foam.dao.AbstractDAO',

  requires: [
    'foam.dao.ProxyListener'
  ],

  documentation: 'Proxy implementation for the DAO interface.',

  properties: [
    {
      class: 'Proxy',
      of: 'foam.dao.DAO',
      name: 'delegate',
      forwards: [ 'put_', 'remove_', 'find_', 'select_', 'removeAll_', 'cmd_' ],
      topics: [ 'on' ], // TODO: Remove this when all users of it are updated.
      factory: function() { return foam.dao.NullDAO.create() },
      postSet: function(old, nu) {
        if ( old ) this.on.reset.pub();
      }
    },
    {
      name: 'of',
      factory: function() {
        return this.delegate.of;
      }
    }
  ],

  methods: [
    {
      name: 'getOf',
      javaReturns: 'foam.core.ClassInfo',
      javaCode: 'if ( of_ == null && getDelegate() != null ) return getDelegate().getOf(); return of_;'
    },

    function listen_(x, sink, predicate) {
      var listener = this.ProxyListener.create({
        delegate: sink,
        args: [ predicate ]
      });

      listener.onDetach(listener.dao$.follow(this.delegate$));

      return listener;
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'ProxyListener',

  implements: ['foam.dao.Sink'],

  properties: [
    'args',
    'delegate',
    {
      name: 'innerSub',
      postSet: function(_, s) {
        if (s) this.onDetach(s);
      }
    },
    {
      name: 'dao',
      postSet: function(old, nu) {
        this.innerSub && this.innerSub.detach();
        this.innerSub = nu && nu.listen.apply(nu, [this].concat(this.args));
        if ( old ) this.reset();
      }
    }
  ],

  methods: [
    function put(obj, s) {
      this.delegate.put(obj, this);
    },

    function remove(obj, s) {
      this.delegate.remove(obj, this);
    },

    function reset(s) {
      this.delegate.reset(this);
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'ArraySink',
  extends: 'foam.dao.AbstractSink',

  constants: {
    // Dual to outputJSON method.
    //
    // TODO(markdittmer): Turn into static method: "parseJSON" once
    // https://github.com/foam-framework/foam2/issues/613 is fixed.
    PARSE_JSON: function(json, opt_cls, opt_ctx) {
      var cls = json.of || opt_cls;
      var array = json.array;
      if ( ! array ) return foam.dao.ArraySink.create({ of: cls }, opt_ctx);
      if ( foam.typeOf(cls) === foam.String )
        cls = ( opt_ctx || foam ).lookup(cls);

      return foam.dao.ArraySink.create({
        of: cls,
        array: foam.json.parse(array, cls, opt_ctx)
      }, opt_ctx);
    }
  },

  properties: [
    {
      class: 'List',
      name: 'array',
      adapt: function(old, nu) {
        if ( ! this.of ) return nu;
        var cls = this.of;
        for ( var i = 0; i < nu.length; i++ ) {
          if ( ! cls.isInstance(nu[i]) )
            nu[i] = cls.create(nu[i], this.__subContext__);
        }
        return nu;
      },
      factory: function() { return []; },
      javaFactory: `return new java.util.ArrayList();`
    },
    {
      class: 'Class',
      name: 'of',
      value: null
    },
    {
      name: 'a',
      transient: true,
      getter: function() {
        this.warn('Use of deprecated ArraySink.a');
        return this.array;
      }
    }
  ],

  methods: [
    {
      name: 'put',
      code: function put(o, sub) {
        var cls = this.of;
        if ( ! cls ) {
          this.array.push(o);
          return;
        }
        if ( cls.isInstance(o) )
          this.array.push(o);
        else
          this.array.push(cls.create(o, this.__subContext__));
      },
      javaCode: `getArray().add(obj);`
    },
    function outputJSON(outputter) {
      outputter.start('{');
      var outputClassName = outputter.outputClassNames;
      if ( outputClassName ) {
        outputter.nl().indent().out(
            outputter.maybeEscapeKey('class'), ':', outputter.postColonStr, '"',
            this.cls_.id, '"');
      }

      var array = this.array;
      var outputComma = outputClassName;
      if ( this.of ) {
        outputter.outputProperty(this, this.OF, outputComma);
        outputComma = true;
      }
      if ( array.length > 0 ) {
        if ( outputComma ) outputter.out(',');
        outputter.nl().indent().outputPropertyName(this.ARRAY).
            out(':', outputter.postColonStr).output(array, this.of);
      }
      outputter.nl().end('}');
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'PromisedDAO',
  extends: 'foam.dao.AbstractDAO',

  properties: [
    {
      class: 'Promised',
      of: 'foam.dao.DAO',
      methods: [ 'put_', 'remove_', 'find_', 'select_', 'removeAll_', 'listen_', 'cmd_' ],
      name: 'promise'
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'LocalStorageDAO',
  extends: 'foam.dao.ArrayDAO',

  properties: [
    {
      name:  'name',
      label: 'Store Name',
      class:  'foam.core.String'
    }
  ],

  methods: [
    function init() {
      var objs = localStorage.getItem(this.name);
      if ( objs ) this.array = foam.json.parseString(objs, this);

      this.on.put.sub(this.updated);
      this.on.remove.sub(this.updated);

      // TODO: base on an indexed DAO
    }
  ],

  listeners: [
    {
      name: 'updated',
      isMerged: true,
      mergeDelay: 100,
      code: function() {
        localStorage.setItem(this.name, foam.json.stringify(this.array));
      }
    }
  ]
});

foam.LIB({
  name: 'foam.String',
  methods: [
    {
      name: 'daoize',
      code: foam.Function.memoize1(function(str) {
        // Turns SomeClassName into someClassNameDAO,
        // of package.ClassName into package.ClassNameDAO
        return str.substring(0, 1).toLowerCase() + str.substring(1) + 'DAO';
      })
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'InvalidArgumentException',
  extends: 'foam.dao.ExternalException',

  properties: [
    {
      class: 'String',
      name: 'message'
    }
  ]
});
<commit_msg>Add java implements for foam.dao.ProxyDAO.listen_<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * 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.
 */

foam.CLASS({
  package: 'foam.dao',
  name: 'ProxyDAO',
  extends: 'foam.dao.AbstractDAO',

  requires: [
    'foam.dao.ProxyListener'
  ],

  documentation: 'Proxy implementation for the DAO interface.',

  properties: [
    {
      class: 'Proxy',
      of: 'foam.dao.DAO',
      name: 'delegate',
      forwards: [ 'put_', 'remove_', 'find_', 'select_', 'removeAll_', 'cmd_' ],
      topics: [ 'on' ], // TODO: Remove this when all users of it are updated.
      factory: function() { return foam.dao.NullDAO.create() },
      postSet: function(old, nu) {
        if ( old ) this.on.reset.pub();
      }
    },
    {
      name: 'of',
      factory: function() {
        return this.delegate.of;
      }
    }
  ],

  methods: [
    {
      name: 'getOf',
      javaReturns: 'foam.core.ClassInfo',
      javaCode: 'if ( of_ == null && getDelegate() != null ) return getDelegate().getOf(); return of_;'
    },

    {
      name: 'listen',
      code: function listen_(x, sink, predicate) {
        var listener = this.ProxyListener.create({
          delegate: sink,
          args: [ predicate ]
        });

        listener.onDetach(listener.dao$.follow(this.delegate$));

        return listener;
      },
      javaCode: `
// TODO: Support changing of delegate
return getDelegate().listen_(x, sink, predicate);
`
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'ProxyListener',

  implements: ['foam.dao.Sink'],

  properties: [
    'args',
    'delegate',
    {
      name: 'innerSub',
      postSet: function(_, s) {
        if (s) this.onDetach(s);
      }
    },
    {
      name: 'dao',
      postSet: function(old, nu) {
        this.innerSub && this.innerSub.detach();
        this.innerSub = nu && nu.listen.apply(nu, [this].concat(this.args));
        if ( old ) this.reset();
      }
    }
  ],

  methods: [
    function put(obj, s) {
      this.delegate.put(obj, this);
    },

    function remove(obj, s) {
      this.delegate.remove(obj, this);
    },

    function reset(s) {
      this.delegate.reset(this);
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'ArraySink',
  extends: 'foam.dao.AbstractSink',

  constants: {
    // Dual to outputJSON method.
    //
    // TODO(markdittmer): Turn into static method: "parseJSON" once
    // https://github.com/foam-framework/foam2/issues/613 is fixed.
    PARSE_JSON: function(json, opt_cls, opt_ctx) {
      var cls = json.of || opt_cls;
      var array = json.array;
      if ( ! array ) return foam.dao.ArraySink.create({ of: cls }, opt_ctx);
      if ( foam.typeOf(cls) === foam.String )
        cls = ( opt_ctx || foam ).lookup(cls);

      return foam.dao.ArraySink.create({
        of: cls,
        array: foam.json.parse(array, cls, opt_ctx)
      }, opt_ctx);
    }
  },

  properties: [
    {
      class: 'List',
      name: 'array',
      adapt: function(old, nu) {
        if ( ! this.of ) return nu;
        var cls = this.of;
        for ( var i = 0; i < nu.length; i++ ) {
          if ( ! cls.isInstance(nu[i]) )
            nu[i] = cls.create(nu[i], this.__subContext__);
        }
        return nu;
      },
      factory: function() { return []; },
      javaFactory: `return new java.util.ArrayList();`
    },
    {
      class: 'Class',
      name: 'of',
      value: null
    },
    {
      name: 'a',
      transient: true,
      getter: function() {
        this.warn('Use of deprecated ArraySink.a');
        return this.array;
      }
    }
  ],

  methods: [
    {
      name: 'put',
      code: function put(o, sub) {
        var cls = this.of;
        if ( ! cls ) {
          this.array.push(o);
          return;
        }
        if ( cls.isInstance(o) )
          this.array.push(o);
        else
          this.array.push(cls.create(o, this.__subContext__));
      },
      javaCode: `getArray().add(obj);`
    },
    function outputJSON(outputter) {
      outputter.start('{');
      var outputClassName = outputter.outputClassNames;
      if ( outputClassName ) {
        outputter.nl().indent().out(
            outputter.maybeEscapeKey('class'), ':', outputter.postColonStr, '"',
            this.cls_.id, '"');
      }

      var array = this.array;
      var outputComma = outputClassName;
      if ( this.of ) {
        outputter.outputProperty(this, this.OF, outputComma);
        outputComma = true;
      }
      if ( array.length > 0 ) {
        if ( outputComma ) outputter.out(',');
        outputter.nl().indent().outputPropertyName(this.ARRAY).
            out(':', outputter.postColonStr).output(array, this.of);
      }
      outputter.nl().end('}');
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'PromisedDAO',
  extends: 'foam.dao.AbstractDAO',

  properties: [
    {
      class: 'Promised',
      of: 'foam.dao.DAO',
      methods: [ 'put_', 'remove_', 'find_', 'select_', 'removeAll_', 'listen_', 'cmd_' ],
      name: 'promise'
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'LocalStorageDAO',
  extends: 'foam.dao.ArrayDAO',

  properties: [
    {
      name:  'name',
      label: 'Store Name',
      class:  'foam.core.String'
    }
  ],

  methods: [
    function init() {
      var objs = localStorage.getItem(this.name);
      if ( objs ) this.array = foam.json.parseString(objs, this);

      this.on.put.sub(this.updated);
      this.on.remove.sub(this.updated);

      // TODO: base on an indexed DAO
    }
  ],

  listeners: [
    {
      name: 'updated',
      isMerged: true,
      mergeDelay: 100,
      code: function() {
        localStorage.setItem(this.name, foam.json.stringify(this.array));
      }
    }
  ]
});

foam.LIB({
  name: 'foam.String',
  methods: [
    {
      name: 'daoize',
      code: foam.Function.memoize1(function(str) {
        // Turns SomeClassName into someClassNameDAO,
        // of package.ClassName into package.ClassNameDAO
        return str.substring(0, 1).toLowerCase() + str.substring(1) + 'DAO';
      })
    }
  ]
});

foam.CLASS({
  package: 'foam.dao',
  name: 'InvalidArgumentException',
  extends: 'foam.dao.ExternalException',

  properties: [
    {
      class: 'String',
      name: 'message'
    }
  ]
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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.
 */

foam.CLASS({
  package: 'foam.u2.tag',
  name: 'Input',
  extends: 'foam.u2.View',

  properties: [
    [ 'nodeName', 'input' ],
    {
      name: 'data',
      preSet: function(o, d) {
        var f = ! d || typeof d === 'string' || typeof d === 'number' || typeof d === 'boolean' || foam.Date.isInstance(d);
        if ( ! f ) {
          this.warn('Set Input data to non-primitive:' + d);
          return o;
        }
        return d;
      }
      /*
      assertValue: function(d) {
        foam.assert(! d || typeof d === 'string' || typeof d === 'number' || typeof d === 'boolean' || foam.Date.isInstance(d), 'Set Input data to non-primitive.');
      }*/
    },
    {
      class: 'Boolean',
      name: 'onKey',
      attribute: true,
      // documentation: 'When true, $$DOC{ref:".data"} is updated on every keystroke, rather than on blur.'
    },
    {
      class: 'Int',
      name: 'size'
    },
    {
      class: 'Int',
      name: 'maxLength',
      attribute: true,
      // documentation: 'When set, will limit the length of the input to a certain number'
    },
    'type',
    'placeholder'
  ],

  axioms: [
    foam.u2.CSS.create({
      code: function CSS() {/*
        ^:read-only { border: none; background: rgba(0,0,0,0); }
      */}
    })
  ],

  methods: [
    function initE() {
      this.SUPER();

      if ( this.size          ) this.setAttribute('size',        this.size);
      if ( this.type          ) this.setAttribute('type',        this.type);
      if ( this.placeholder   ) this.setAttribute('placeholder', this.placeholder);
      if ( this.maxLength > 0 ) this.setAttribute('maxlength',   this.maxLength);

      this.initCls();
      this.link();
    },

    function initCls() {
      // Template method, can be overriden by sub-classes
      this.addClass(this.myClass());
    },

    function link() {
      // Template method, can be overriden by sub-classes
      this.attrSlot(null, this.onKey ? 'input' : null).linkFrom(this.data$);
    },

    function updateMode_(mode) {
      // TODO: make sure that DOM is updated if values don't change
      this.setAttribute('readonly', mode === foam.u2.DisplayMode.RO);
      this.setAttribute('disabled', mode === foam.u2.DisplayMode.DISABLED);
    }
  ]
});
<commit_msg>Modernized css.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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.
 */

foam.CLASS({
  package: 'foam.u2.tag',
  name: 'Input',
  extends: 'foam.u2.View',

  properties: [
    [ 'nodeName', 'input' ],
    {
      name: 'data',
      preSet: function(o, d) {
        var f = ! d || typeof d === 'string' || typeof d === 'number' || typeof d === 'boolean' || foam.Date.isInstance(d);
        if ( ! f ) {
          this.warn('Set Input data to non-primitive:' + d);
          return o;
        }
        return d;
      }
      /*
      assertValue: function(d) {
        foam.assert(! d || typeof d === 'string' || typeof d === 'number' || typeof d === 'boolean' || foam.Date.isInstance(d), 'Set Input data to non-primitive.');
      }*/
    },
    {
      class: 'Boolean',
      name: 'onKey',
      attribute: true,
      // documentation: 'When true, $$DOC{ref:".data"} is updated on every keystroke, rather than on blur.'
    },
    {
      class: 'Int',
      name: 'size'
    },
    {
      class: 'Int',
      name: 'maxLength',
      attribute: true,
      // documentation: 'When set, will limit the length of the input to a certain number'
    },
    'type',
    'placeholder'
  ],

  css: `
    ^:read-only { border: none; background: rgba(0,0,0,0); }
  `,

  methods: [
    function initE() {
      this.SUPER();

      if ( this.size          ) this.setAttribute('size',        this.size);
      if ( this.type          ) this.setAttribute('type',        this.type);
      if ( this.placeholder   ) this.setAttribute('placeholder', this.placeholder);
      if ( this.maxLength > 0 ) this.setAttribute('maxlength',   this.maxLength);

      this.initCls();
      this.link();
    },

    function initCls() {
      // Template method, can be overriden by sub-classes
      this.addClass(this.myClass());
    },

    function link() {
      // Template method, can be overriden by sub-classes
      this.attrSlot(null, this.onKey ? 'input' : null).linkFrom(this.data$);
    },

    function updateMode_(mode) {
      // TODO: make sure that DOM is updated if values don't change
      this.setAttribute('readonly', mode === foam.u2.DisplayMode.RO);
      this.setAttribute('disabled', mode === foam.u2.DisplayMode.DISABLED);
    }
  ]
});
<|endoftext|>


<commit_before>!(function(factory) {
  if (typeof define === "function" && define.amd) {
    define(factory);
  } else {
    factory();
  }
}(function() {

  // Code form Zepto.js
  // @see https://github.com/madrobby/zepto/blob/master/src/form.js#files
  var serializeArray = function() {
    var name, type, result = [],
      add = function(value) {
        if (value.forEach) return value.forEach(add)
        result.push({ name: name, value: value })
      };

    [].slice.apply(this.elements || this.querySelectorAll('input,textarea')).forEach(function(field) {

      type = field.type, name = field.name
      if (name && field.nodeName.toLowerCase() !== 'fieldset' &&
        !field.disabled && type !== 'submit' && type !== 'reset' && type !== 'button' && type !== 'file' &&
        ((type !== 'radio' && type !== 'checkbox') || field.checked))
        add(field.value)
    })
    return result
  }

  var serialize = function() {
    var result = []
    serializeArray.call(this).forEach(function(elm) {
      result.push(encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value))
    })
    return result.join('&')
  }

  var serializeJSONForGET = function() {
    var result = []
    serializeArray.call(this).forEach(function(elm) {
      result.push(encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value))
    })
    return result.join('&')
  }

    /**
   * Set obj's value by keys
   * @param  {[object]}           obj   The target you want to set.
   * @param  {[array]}            keys  The value's path.
   * @param  {[string, array...]} value Target's value.
   * @return {[object]}                 The target after setting value.
   *
   * Example: obj = {'a':'a'}, keys = ['b','bb'], value = 'b-value'
   * return {
   *         'a': 'a',
   *         'b': {
   *           'bb': 'b-value'
   *          }
   *        }
   */
  var deepSet = function(obj, keys, value) {

    var curKey = keys[0];
    if(keys.length === 1) {

      curKey === '' ? obj.push(value) : obj[curKey] = value;

      return;
    }
    if (keys.length > 1) {
      if (typeof obj[curKey] === 'undefined')
        obj[curKey] = keys[1] === '' ? [] : {};
    }

    var o = obj[curKey];
    keys.shift();
    return deepSet(o, keys, value);
  }

  var integerKeysAsArrayIndexes = function(obj, keys, value) {

    var allKeysIsInteger = true;
    var arr = [];
    var temp;
    keys = keys || [];
    value = value || obj;

    for (var key in value) {

      temp = keys.concat();

      if (isNaN(+key)) {
        allKeysIsInteger = false;
      } else {
        arr.push(value[key]);
      }
      temp.push(key);

      if ((typeof value[key] === 'object') && !Array.isArray(value[key])) {
        integerKeysAsArrayIndexes(obj, temp, value[key]);
      }
    }

    if (allKeysIsInteger) {

      if (keys.length === 0) {
        obj = arr;
      } else {
        deepSet(obj, keys, arr)
      }
    }
    return obj;
  }
  // TEST: integerKeysAsArrayIndexes
  // var json = integerKeysAsArrayIndexes({
  //   a: {
  //     0: {
  //       aa: "aa",
  //       bb: {
  //         0: "bb0",
  //         1: "bb1",
  //         3: "bb3"
  //       }
  //     },
  //     1: "a"
  //   },
  //   b: "b"
  // })
  // console.log(JSON.stringify(json));
  // var json = integerKeysAsArrayIndexes({
  //   0: "a",
  //   1: {
  //     "a": "b"
  //   }
  // })
  // console.log(JSON.stringify(json));

  // keys: ["a", ""]
  // ["a", "b", "c"], ["a", "b", "d"]
  // types: "string", "array", "number", "bool", "object"
  //
  // TEST
  // form.json
  // input(name='a')
  // input(name='images.')
  // input(name='images.')
  // input(name='b.b')
  // input(name='c.c.:number')
  // input(name='c.c.')
  // input(type='submit')
  //
  var submitHandler = function(e, options, action) {

    var self = this;
    var method = self.getAttribute('method') || 'POST';
    var res;

    if (self['_running']) {
      console.log('form submiting. return.')
      return;
    }
    res = {};
    serializeArray.call(self).forEach(function(t) {
      var keys  = t.name.split('.');
      var len   = keys.length - 1;
      var index = keys[len].indexOf(':');
      var value = t.value;
      var type;
      if (index !== -1) {
        type    = keys[len].substr(index + 1);
        keys[len] = keys[len].substr(0, index);
      } else {
        type = 'string';
      }

      if (type === 'array' || type === 'object') {

        value  = JSON.parse(value);

      } else if (type === 'number') {

        value = +value;
      } else if (type === 'datetime') {

        // var value = '2016-05-05 05:05:05';
        // Not an UTC-time. Convert it to an UTC-time.
        if (value.indexOf('T') === -1) {

          value = value.replace(' ', 'T');
          value = new Date(value + 'Z');
          value = new Date(value.getTime() + value.getTimezoneOffset() * 60 * 1000);

        // var value = '2016-05-05T05:05:05';
        // An UTC-time, just new it directly.
        } else {
          value = new Date(value + 'Z');
        }

        if (value.toString() === 'Invalid Date') {
          value = '';
        } else {
          value = value.getTime();
        }

      } else if (type === 'date') {

        value = new Date(value);
        if (value.toString() === 'Invalid Date') {
          value = '';
        } else {
          value = value.getTime();
        }

      } else if (type === 'bool' || type === 'boolean') {
        if (value === 'false')
          value = false
        else
          value = !!value;

      }

      deepSet(res, keys, value);
    });

    if (options.parseInteger)
      res = integerKeysAsArrayIndexes(res);
    res = options.data.call(self, res);

    // Stringify result object.
    if (method === "GET") {

      var keys = Object.keys(res || {});
      var arr = []
      for (var i = 0, max = keys.length; i < max; i++) {
        if (typeof res[keys[i]] === 'string') {

          arr.push(encodeURIComponent(keys[i]) + '=' + encodeURIComponent(res[keys[i]]))
        } else {
          arr.push(encodeURIComponent(keys[i]) + '=' + encodeURIComponent(JSON.stringify(res[keys[i]])));
        }
      }
      res = arr.join('&')

    } else {
      res = JSON.stringify(res);
    }

    /**
     * If there are no data should be sent, just return.
     */
    if (res === 'null') {
      return;
    }

    /**
     *
     * AJAX request
     *
     */
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.timeout = 20000;
    action = (action === undefined ? (self.getAttribute('action') || location.href) : action);

    if (method === 'GET') {
      action += action.indexOf('?') === -1 ? ('?' + res) : ('&' + res);
    }

    xmlHttp.open(method, action, true);
    xmlHttp.setRequestHeader('Content-Type', 'application/json');

    /**
    * Add session header for cross-domain request.
    * You can't set cookies for ajax
    * and you have to response #options request# like:
    * "Access-Control-Allow-Headers: X-CUSTOME-HEADERS"
    * before you send it.
    *
    * Call beforeSend function before send xhr.
    * */
    self._beforeSend && self._beforeSend.call(self, xmlHttp);

    xmlHttp.responseType = 'json';

    xmlHttp.onreadystatechange = function() {
      if (xmlHttp.readyState === 4) {

        self['_running'] = false;

        var done  = self._done && self._done(xmlHttp);
        if (done === null) {
          return;
        } else {
          xmlHttp = done || xmlHttp;
        }

        // xmlHttp.response is read-only. So we shoud copy it. xmlHttp.responseType is not accessiable.
        // responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'json').
        var fakeXMLHttp = {};
        ["status", "response", "statusText", "readyState", "responseText"].forEach(function(i) {
          try {
            fakeXMLHttp[i] = xmlHttp[i];
          } catch (e) {}
        })

        if (xmlHttp.status >= 200 && xmlHttp.status < 300) {

          if (typeof xmlHttp.response === 'string') {
            try {
              fakeXMLHttp.response = JSON.parse(fakeXMLHttp.response);
            } catch (e) {
              fakeXMLHttp.response = {};
            }
            options.ended.call(self, fakeXMLHttp.response , fakeXMLHttp);
            self['_XMLHttpRequest'] = fakeXMLHttp;
          } else {
            options.ended.call(self, xmlHttp.response, xmlHttp);
            self['_XMLHttpRequest'] = xmlHttp;
          }
          self.dispatchEvent(new Event('formJSON-ended', {bubbles: true}));

        } else {

          if (typeof xmlHttp.response === 'string') {
            try {
              fakeXMLHttp.response = JSON.parse(fakeXMLHttp.response);
            } catch (e) {
              fakeXMLHttp.response = {};
            }
            options.error.call(self, fakeXMLHttp);
            self['_XMLHttpRequest'] = fakeXMLHttp;
          } else {
            options.error.call(self, xmlHttp);
            self['_XMLHttpRequest'] = xmlHttp;
          }

          self.dispatchEvent(new Event('formJSON-error', {bubbles: true}));
        }
      }
    }

    self['_running'] = true;
    if (method === 'GET')
      xmlHttp.send();
    else
      xmlHttp.send(res);
  }

  /**
   * Example:
   * <form action='/action/a'>
   * <input type='submit'>
   * <input type='submit' formAction='/action/b'>
   * </form>
   */
  var formAction;
  document.addEventListener('click', function(e) {
    var target = e.target;

    if (target.type === 'submit' && target.getAttribute('formAction')) {
      formAction = target.getAttribute('formAction');
    }

    setTimeout(function() {
      formAction = undefined;
    }, 1000);

  })

  /**
   * Submit form's handler.
   * Example:
   * <form data-role='formJSON'>
   * </form>
   */
  var submitListener = function(e) {

    var target = e.target;
    if (target.getAttribute('data-role') === 'formJSON') {

      e.preventDefault();
      e.stopPropagation();

      var temp = (temp = target.getAttribute('data-target')) && document.querySelector(temp);
      var parseInteger = target.getAttribute('data-parseInteger') === 'false' ? false : true;

      var options = {
        parseInteger: parseInteger,
        ended: target.onended || (temp && temp.onended) || function() {},
        // to change data
        data: target._data || (temp && temp._data) || function(d) {return d},
        error: target.onerror || (temp && temp.onerror) || function() {}
      };

      submitHandler.call(target, e, options, formAction);

    }

  }
  document.addEventListener('submit', submitListener)
  document.addEventListener('formJSON-submit', submitListener)

  // Fix.
  // Submit a form by javascript won't fire submit event.
  // @see http://stackoverflow.com/questions/12819357/form-submitted-using-submit-from-a-link-cannot-be-caught-by-onsubmit-handler

  var _submit = HTMLFormElement.prototype.submit;
  HTMLFormElement.prototype.submit = function() {

    if (this.getAttribute('data-role') === 'formJSON') {
      this.dispatchEvent(new Event('formJSON-submit', {bubbles: true}));
    } else {
      _submit.call(this);
    }
  }

  return null;

}));
<commit_msg>If result is null, just return.<commit_after>!(function(factory) {
  if (typeof define === "function" && define.amd) {
    define(factory);
  } else {
    factory();
  }
}(function() {

  // Code form Zepto.js
  // @see https://github.com/madrobby/zepto/blob/master/src/form.js#files
  var serializeArray = function() {
    var name, type, result = [],
      add = function(value) {
        if (value.forEach) return value.forEach(add)
        result.push({ name: name, value: value })
      };

    [].slice.apply(this.elements || this.querySelectorAll('input,textarea')).forEach(function(field) {

      type = field.type, name = field.name
      if (name && field.nodeName.toLowerCase() !== 'fieldset' &&
        !field.disabled && type !== 'submit' && type !== 'reset' && type !== 'button' && type !== 'file' &&
        ((type !== 'radio' && type !== 'checkbox') || field.checked))
        add(field.value)
    })
    return result
  }

  var serialize = function() {
    var result = []
    serializeArray.call(this).forEach(function(elm) {
      result.push(encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value))
    })
    return result.join('&')
  }

  var serializeJSONForGET = function() {
    var result = []
    serializeArray.call(this).forEach(function(elm) {
      result.push(encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value))
    })
    return result.join('&')
  }

    /**
   * Set obj's value by keys
   * @param  {[object]}           obj   The target you want to set.
   * @param  {[array]}            keys  The value's path.
   * @param  {[string, array...]} value Target's value.
   * @return {[object]}                 The target after setting value.
   *
   * Example: obj = {'a':'a'}, keys = ['b','bb'], value = 'b-value'
   * return {
   *         'a': 'a',
   *         'b': {
   *           'bb': 'b-value'
   *          }
   *        }
   */
  var deepSet = function(obj, keys, value) {

    var curKey = keys[0];
    if(keys.length === 1) {

      curKey === '' ? obj.push(value) : obj[curKey] = value;

      return;
    }
    if (keys.length > 1) {
      if (typeof obj[curKey] === 'undefined')
        obj[curKey] = keys[1] === '' ? [] : {};
    }

    var o = obj[curKey];
    keys.shift();
    return deepSet(o, keys, value);
  }

  var integerKeysAsArrayIndexes = function(obj, keys, value) {

    var allKeysIsInteger = true;
    var arr = [];
    var temp;
    keys = keys || [];
    value = value || obj;

    for (var key in value) {

      temp = keys.concat();

      if (isNaN(+key)) {
        allKeysIsInteger = false;
      } else {
        arr.push(value[key]);
      }
      temp.push(key);

      if ((typeof value[key] === 'object') && !Array.isArray(value[key])) {
        integerKeysAsArrayIndexes(obj, temp, value[key]);
      }
    }

    if (allKeysIsInteger) {

      if (keys.length === 0) {
        obj = arr;
      } else {
        deepSet(obj, keys, arr)
      }
    }
    return obj;
  }
  // TEST: integerKeysAsArrayIndexes
  // var json = integerKeysAsArrayIndexes({
  //   a: {
  //     0: {
  //       aa: "aa",
  //       bb: {
  //         0: "bb0",
  //         1: "bb1",
  //         3: "bb3"
  //       }
  //     },
  //     1: "a"
  //   },
  //   b: "b"
  // })
  // console.log(JSON.stringify(json));
  // var json = integerKeysAsArrayIndexes({
  //   0: "a",
  //   1: {
  //     "a": "b"
  //   }
  // })
  // console.log(JSON.stringify(json));

  // keys: ["a", ""]
  // ["a", "b", "c"], ["a", "b", "d"]
  // types: "string", "array", "number", "bool", "object"
  //
  // TEST
  // form.json
  // input(name='a')
  // input(name='images.')
  // input(name='images.')
  // input(name='b.b')
  // input(name='c.c.:number')
  // input(name='c.c.')
  // input(type='submit')
  //
  var submitHandler = function(e, options, action) {

    var self = this;
    var method = self.getAttribute('method') || 'POST';
    var res;

    if (self['_running']) {
      console.log('form submiting. return.')
      return;
    }
    res = {};
    serializeArray.call(self).forEach(function(t) {
      var keys  = t.name.split('.');
      var len   = keys.length - 1;
      var index = keys[len].indexOf(':');
      var value = t.value;
      var type;
      if (index !== -1) {
        type    = keys[len].substr(index + 1);
        keys[len] = keys[len].substr(0, index);
      } else {
        type = 'string';
      }

      if (type === 'array' || type === 'object') {

        value  = JSON.parse(value);

      } else if (type === 'number') {

        value = +value;
      } else if (type === 'datetime') {

        // var value = '2016-05-05 05:05:05';
        // Not an UTC-time. Convert it to an UTC-time.
        if (value.indexOf('T') === -1) {

          value = value.replace(' ', 'T');
          value = new Date(value + 'Z');
          value = new Date(value.getTime() + value.getTimezoneOffset() * 60 * 1000);

        // var value = '2016-05-05T05:05:05';
        // An UTC-time, just new it directly.
        } else {
          value = new Date(value + 'Z');
        }

        if (value.toString() === 'Invalid Date') {
          value = '';
        } else {
          value = value.getTime();
        }

      } else if (type === 'date') {

        value = new Date(value);
        if (value.toString() === 'Invalid Date') {
          value = '';
        } else {
          value = value.getTime();
        }

      } else if (type === 'bool' || type === 'boolean') {
        if (value === 'false')
          value = false
        else
          value = !!value;

      }

      deepSet(res, keys, value);
    });

    if (options.parseInteger)
      res = integerKeysAsArrayIndexes(res);
    res = options.data.call(self, res);

    /**
     * If there are no data should be sent, just return.
     */
    if (res === null) {
      return;
    }

    // Stringify result object.
    if (method === "GET") {

      var keys = Object.keys(res || {});
      var arr = []
      for (var i = 0, max = keys.length; i < max; i++) {
        if (typeof res[keys[i]] === 'string') {

          arr.push(encodeURIComponent(keys[i]) + '=' + encodeURIComponent(res[keys[i]]))
        } else {
          arr.push(encodeURIComponent(keys[i]) + '=' + encodeURIComponent(JSON.stringify(res[keys[i]])));
        }
      }
      res = arr.join('&')

    } else {
      res = JSON.stringify(res);
    }

    /**
     *
     * AJAX request
     *
     */
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.timeout = 20000;
    action = (action === undefined ? (self.getAttribute('action') || location.href) : action);

    if (method === 'GET') {
      action += action.indexOf('?') === -1 ? ('?' + res) : ('&' + res);
    }

    xmlHttp.open(method, action, true);
    xmlHttp.setRequestHeader('Content-Type', 'application/json');

    /**
    * Add session header for cross-domain request.
    * You can't set cookies for ajax
    * and you have to response #options request# like:
    * "Access-Control-Allow-Headers: X-CUSTOME-HEADERS"
    * before you send it.
    *
    * Call beforeSend function before send xhr.
    * */
    self._beforeSend && self._beforeSend.call(self, xmlHttp);

    xmlHttp.responseType = 'json';

    xmlHttp.onreadystatechange = function() {
      if (xmlHttp.readyState === 4) {

        self['_running'] = false;

        var done  = self._done && self._done(xmlHttp);
        if (done === null) {
          return;
        } else {
          xmlHttp = done || xmlHttp;
        }

        // xmlHttp.response is read-only. So we shoud copy it. xmlHttp.responseType is not accessiable.
        // responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'json').
        var fakeXMLHttp = {};
        ["status", "response", "statusText", "readyState", "responseText"].forEach(function(i) {
          try {
            fakeXMLHttp[i] = xmlHttp[i];
          } catch (e) {}
        })

        if (xmlHttp.status >= 200 && xmlHttp.status < 300) {

          if (typeof xmlHttp.response === 'string') {
            try {
              fakeXMLHttp.response = JSON.parse(fakeXMLHttp.response);
            } catch (e) {
              fakeXMLHttp.response = {};
            }
            options.ended.call(self, fakeXMLHttp.response , fakeXMLHttp);
            self['_XMLHttpRequest'] = fakeXMLHttp;
          } else {
            options.ended.call(self, xmlHttp.response, xmlHttp);
            self['_XMLHttpRequest'] = xmlHttp;
          }
          self.dispatchEvent(new Event('formJSON-ended', {bubbles: true}));

        } else {

          if (typeof xmlHttp.response === 'string') {
            try {
              fakeXMLHttp.response = JSON.parse(fakeXMLHttp.response);
            } catch (e) {
              fakeXMLHttp.response = {};
            }
            options.error.call(self, fakeXMLHttp);
            self['_XMLHttpRequest'] = fakeXMLHttp;
          } else {
            options.error.call(self, xmlHttp);
            self['_XMLHttpRequest'] = xmlHttp;
          }

          self.dispatchEvent(new Event('formJSON-error', {bubbles: true}));
        }
      }
    }

    self['_running'] = true;
    if (method === 'GET')
      xmlHttp.send();
    else
      xmlHttp.send(res);
  }

  /**
   * Example:
   * <form action='/action/a'>
   * <input type='submit'>
   * <input type='submit' formAction='/action/b'>
   * </form>
   */
  var formAction;
  document.addEventListener('click', function(e) {
    var target = e.target;

    if (target.type === 'submit' && target.getAttribute('formAction')) {
      formAction = target.getAttribute('formAction');
    }

    setTimeout(function() {
      formAction = undefined;
    }, 1000);

  })

  /**
   * Submit form's handler.
   * Example:
   * <form data-role='formJSON'>
   * </form>
   */
  var submitListener = function(e) {

    var target = e.target;
    if (target.getAttribute('data-role') === 'formJSON') {

      e.preventDefault();
      e.stopPropagation();

      var temp = (temp = target.getAttribute('data-target')) && document.querySelector(temp);
      var parseInteger = target.getAttribute('data-parseInteger') === 'false' ? false : true;

      var options = {
        parseInteger: parseInteger,
        ended: target.onended || (temp && temp.onended) || function() {},
        // to change data
        data: target._data || (temp && temp._data) || function(d) {return d},
        error: target.onerror || (temp && temp.onerror) || function() {}
      };

      submitHandler.call(target, e, options, formAction);

    }

  }
  document.addEventListener('submit', submitListener)
  document.addEventListener('formJSON-submit', submitListener)

  // Fix.
  // Submit a form by javascript won't fire submit event.
  // @see http://stackoverflow.com/questions/12819357/form-submitted-using-submit-from-a-link-cannot-be-caught-by-onsubmit-handler

  var _submit = HTMLFormElement.prototype.submit;
  HTMLFormElement.prototype.submit = function() {

    if (this.getAttribute('data-role') === 'formJSON') {
      this.dispatchEvent(new Event('formJSON-submit', {bubbles: true}));
    } else {
      _submit.call(this);
    }
  }

  return null;

}));
<|endoftext|>


<commit_before>/*jshint esversion: 6 */
(function() {
  'use strict';

  const util = require('util');
  const _ = require('lodash');
  const cheerio = require('cheerio');

  class Common {

    static get SEARCH_PATH() {
      return '/haku';
    }

    static get CONTENT_FOLDER() {
      return '/sisalto';
    }

    static get PAGE_IMAGES_FOLDER() {
      return '/pageImages';
    }

    static get EVENT_COUNT() {
      return 9;
    }

    static get JOB_COUNT() {
      return 5;
    }

    static get ANNOUNCEMENT_COUNT() {
      return 3;
    }

    static get ANNOUNCEMENT_COUNT_PAGE() {
      return 10;
    }

    static get NEWS_COUNT_PAGE() {
      return 10;
    }

    static get SOCIAL_MEDIA_POSTS() {
      return 4 * 3;
    }

    static get FILES_FOLDER() {
      return '/tiedostot';
    }

    static get NEWS_FOLDER() {
      return '/uutiset';
    }

    static get ANNOUNCEMENTS_FOLDER() {
      return '/kuulutukset';
    }

    static get JOBS_FOLDER() {
      return '/tyot';
    }

    static get TIMETABLE_ROWS() {
      return 15;
    }

    static get SEARCH_RESULTS_PER_TYPE() {
      return 5;
    }

    static get SEARCH_PAGES_PER_PAGE() {
      return 5;
    }

    static get SEARCH_FILES_PER_PAGE() {
      return 10;
    }

    static get SEARCH_NEWS_PER_PAGE() {
      return 5;
    }

    static get SEARCH_CONTACT_PER_TYPE() {
      return 10;
    }

    static resolveLinkType(link) {
      if (!link || link.startsWith('#')) {
        return 'NONE';
      }

      if (link.startsWith('/')) {
        return 'PATH';
      } else if (link.match(/[a-zA-Z]*:\/\/.*/)) {
        return 'ABSOLUTE';
      }

      return 'RELATIVE';
    }

    static processLink(currentPage, text) {
      if (!text) {
        return null;
      }

      var link = text.trim();
      if (!link) {
        return null;
      }

      switch (Common.resolveLinkType(link)) {
        case 'PATH':
          return util.format('%s%s', Common.CONTENT_FOLDER, link);
        case 'RELATIVE':
          return util.format('%s/%s', currentPage.split('/').splice(-1), link);
        default:
      }

      return link;
    }

    static resolvePageCasemMeta(content) {
      const result = {};

      if (content && _.trim(content)) {
        const $ = cheerio.load(content);

        $('.casem-meta').each((index, metaElement) => {
          const name = $(metaElement).attr('data-meta-name');
          const value = $(metaElement).attr('data-meta-value');
          result[name] = value;
        });
      }

      return result;
    }

    /**
     * Processes links within dom
     *
     * @param {String} currentPage current page
     * @param {Cheerio} $ dom
     */
    static processDomLinks(currentPage, $) {
      $('a[href]').each((index, link) => {
        const href = $(link).attr('href');
        $(link).attr('href', Common.processLink(currentPage, href));
      });
    }

    static processPageContent(currentPage, content) {
      if (!content) {
        return '';
      }

      const $ = cheerio.load(content);

      $('aside').remove();

      Common.processDomLinks(currentPage, $);
      Common.processPageDomImages($);
      Common.lazyPageDomImages($);

      return $.html();
    }

    static getSidebarContent(currentPage, content) {
      if (!content) {
        return '';
      }

      const $ = cheerio.load(content);
      Common.processPageDomImages($);
      Common.processDomLinks(currentPage, $);

      $('aside').find('*[contenteditable]').removeAttr('contenteditable');

      $('aside').find('img')
        .removeAttr('srcset')
        .removeAttr('width')
        .removeAttr('sizes')
        .removeAttr('class')
        .removeAttr('height');

      return $('aside').html();
    }

    static processPageDomImages($) {
      $('.kunta-api-image[data-image-type="content-image"]').each((index, img) => {
        const pageId = $(img).attr('data-page-id');
        const imageId = $(img).attr('data-attachment-id');
        const width = parseInt($(img).attr('width'));
        const height = parseInt($(img).attr('height'));
        const size = width && height ? Math.min(width, height) : width || height || null;
        const src = util.format('/pageImages/%s/%s%s', pageId, imageId, size ? util.format('?size=%s', size) : '');

        $(img)
          .removeAttr('data-page-id')
          .removeAttr('data-attachment-id')
          .removeAttr('data-organization-id')
          .removeAttr('data-image-type')
          .attr('src', src);
      });
    }

    static lazyPageDomImages($) {
      $('img[src]').each((index, img) => {
        const src = $(img).attr('src');
        $(img)
          .addClass('lazy')
          .removeAttr('src')
          .removeAttr('srcset')
          .attr('data-original', src);
      });
    }

    static plainTextParagraphs(text) {
      var result = [];
      var paragraphs = (text||'').split('\n');

      for (var i = 0; i < paragraphs.length; i++) {
        result.push(util.format('
%s
', paragraphs[i]));
      }

      return result.join('');
    }

    static htmlToText(html) {
      if (!html) {
        return '';
      }

      const $ = cheerio.load(html);
      return $.text().replace(/\s+/g, ' ');
    }

    static processFreeTextSearch(search) {
      if (!search) {
        return null;
      }

      const searchTerms = _.map(search.replace(/\ {1,}/g, ' ').split(' '), (term) => {
        return `+(${term}*)`;
      });

      return searchTerms.join(' ');
    }
  }

  module.exports = Common;

}).call(this);<commit_msg>Stripped non visible elements from search excerpt<commit_after>/*jshint esversion: 6 */
(function() {
  'use strict';

  const util = require('util');
  const _ = require('lodash');
  const cheerio = require('cheerio');

  class Common {

    static get SEARCH_PATH() {
      return '/haku';
    }

    static get CONTENT_FOLDER() {
      return '/sisalto';
    }

    static get PAGE_IMAGES_FOLDER() {
      return '/pageImages';
    }

    static get EVENT_COUNT() {
      return 9;
    }

    static get JOB_COUNT() {
      return 5;
    }

    static get ANNOUNCEMENT_COUNT() {
      return 3;
    }

    static get ANNOUNCEMENT_COUNT_PAGE() {
      return 10;
    }

    static get NEWS_COUNT_PAGE() {
      return 10;
    }

    static get SOCIAL_MEDIA_POSTS() {
      return 4 * 3;
    }

    static get FILES_FOLDER() {
      return '/tiedostot';
    }

    static get NEWS_FOLDER() {
      return '/uutiset';
    }

    static get ANNOUNCEMENTS_FOLDER() {
      return '/kuulutukset';
    }

    static get JOBS_FOLDER() {
      return '/tyot';
    }

    static get TIMETABLE_ROWS() {
      return 15;
    }

    static get SEARCH_RESULTS_PER_TYPE() {
      return 5;
    }

    static get SEARCH_PAGES_PER_PAGE() {
      return 5;
    }

    static get SEARCH_FILES_PER_PAGE() {
      return 10;
    }

    static get SEARCH_NEWS_PER_PAGE() {
      return 5;
    }

    static get SEARCH_CONTACT_PER_TYPE() {
      return 10;
    }

    static resolveLinkType(link) {
      if (!link || link.startsWith('#')) {
        return 'NONE';
      }

      if (link.startsWith('/')) {
        return 'PATH';
      } else if (link.match(/[a-zA-Z]*:\/\/.*/)) {
        return 'ABSOLUTE';
      }

      return 'RELATIVE';
    }

    static processLink(currentPage, text) {
      if (!text) {
        return null;
      }

      var link = text.trim();
      if (!link) {
        return null;
      }

      switch (Common.resolveLinkType(link)) {
        case 'PATH':
          return util.format('%s%s', Common.CONTENT_FOLDER, link);
        case 'RELATIVE':
          return util.format('%s/%s', currentPage.split('/').splice(-1), link);
        default:
      }

      return link;
    }

    static resolvePageCasemMeta(content) {
      const result = {};

      if (content && _.trim(content)) {
        const $ = cheerio.load(content);

        $('.casem-meta').each((index, metaElement) => {
          const name = $(metaElement).attr('data-meta-name');
          const value = $(metaElement).attr('data-meta-value');
          result[name] = value;
        });
      }

      return result;
    }

    /**
     * Processes links within dom
     *
     * @param {String} currentPage current page
     * @param {Cheerio} $ dom
     */
    static processDomLinks(currentPage, $) {
      $('a[href]').each((index, link) => {
        const href = $(link).attr('href');
        $(link).attr('href', Common.processLink(currentPage, href));
      });
    }

    static processPageContent(currentPage, content) {
      if (!content) {
        return '';
      }

      const $ = cheerio.load(content);

      $('aside').remove();

      Common.processDomLinks(currentPage, $);
      Common.processPageDomImages($);
      Common.lazyPageDomImages($);

      return $.html();
    }

    static getSidebarContent(currentPage, content) {
      if (!content) {
        return '';
      }

      const $ = cheerio.load(content);
      Common.processPageDomImages($);
      Common.processDomLinks(currentPage, $);

      $('aside').find('*[contenteditable]').removeAttr('contenteditable');

      $('aside').find('img')
        .removeAttr('srcset')
        .removeAttr('width')
        .removeAttr('sizes')
        .removeAttr('class')
        .removeAttr('height');

      return $('aside').html();
    }

    static processPageDomImages($) {
      $('.kunta-api-image[data-image-type="content-image"]').each((index, img) => {
        const pageId = $(img).attr('data-page-id');
        const imageId = $(img).attr('data-attachment-id');
        const width = parseInt($(img).attr('width'));
        const height = parseInt($(img).attr('height'));
        const size = width && height ? Math.min(width, height) : width || height || null;
        const src = util.format('/pageImages/%s/%s%s', pageId, imageId, size ? util.format('?size=%s', size) : '');

        $(img)
          .removeAttr('data-page-id')
          .removeAttr('data-attachment-id')
          .removeAttr('data-organization-id')
          .removeAttr('data-image-type')
          .attr('src', src);
      });
    }

    static lazyPageDomImages($) {
      $('img[src]').each((index, img) => {
        const src = $(img).attr('src');
        $(img)
          .addClass('lazy')
          .removeAttr('src')
          .removeAttr('srcset')
          .attr('data-original', src);
      });
    }

    static plainTextParagraphs(text) {
      var result = [];
      var paragraphs = (text||'').split('\n');

      for (var i = 0; i < paragraphs.length; i++) {
        result.push(util.format('
%s
', paragraphs[i]));
      }

      return result.join('');
    }

    static htmlToText(html) {
      if (!html) {
        return '';
      }

      const $ = cheerio.load(html);
      $('script,embed,style,.embed-container,iframe').remove();

      return $.text().replace(/\s+/g, ' ');
    }

    static processFreeTextSearch(search) {
      if (!search) {
        return null;
      }

      const searchTerms = _.map(search.replace(/\ {1,}/g, ' ').split(' '), (term) => {
        return `+(${term}*)`;
      });

      return searchTerms.join(' ');
    }
  }

  module.exports = Common;

}).call(this);<|endoftext|>