<commit_before>var images = require('./images');
var object = require('./object');
var util = require('./util');

module.exports = map;

/* global mapboxgl */

function customControl(options) {
  return {
    onAdd: function (map) {
      var ctrl = this;
      ctrl._map = map;
      ctrl._container = document.createElement('div');
      ctrl._container.className = 'mapboxgl-ctrl';
      if (options.el) {
        ctrl._container.appendChild(options.el);
      }
      if (options.onAdd) {
        options.onAdd.call(ctrl, map);
      }
      return ctrl._container;
    },
    onRemove: function () {
      var ctrl = this;
      if (options.el) {
        ctrl._container.removeChild(options.el);
      }
      if (options.onRemove) {
        options.onRemove.call(ctrl);
      }
      ctrl._container.parentNode.removeChild(ctrl._container);
      delete ctrl._container;
      delete ctrl._map;
    }
  };
}

function mapTypeControl(mapTypeControlOptions, customMapTypes, fn) {
  return customControl({
    onAdd: function () {
      var ctrl = this;
      ctrl._events = [];
      mapTypeControlOptions.mapTypeIds.forEach(function (id) {
        var type = ctrl._container.appendChild(document.createElement('div')),
          ev = {
            el: type,
            event: 'click',
            fn: fn.bind(undefined, id)
          };
        type.className = 'map-type';
        type.innerHTML = '
' + customMapTypes[id].name + '
';
        type.addEventListener(ev.event, ev.fn);
        ctrl._events.push(ev);
      });
    },
    onRemove: function () {
      var ctrl = this;
      ctrl._events.forEach(function (ev) {
        ev.type.removeEventListener(ev.event, ev.fn);
      });
      delete ctrl._events;
    }
  });
}

function getAttribution(map) {
  var sourceCaches, attributions;
  if (!map.style) {
    return '';
  }
  sourceCaches = map.style.sourceCaches;
  attributions = Object.keys(sourceCaches).reduce(function (attributions, id) {
    var source = sourceCaches[id].getSource();
    if (source.attribution && attributions.indexOf(source.attribution) < 0) {
      attributions.push(source.attribution);
    }
    return attributions;
  }, []);

  // remove any entries that are substrings of another entry.
  // first sort by length so that substrings come first
  attributions.sort(function (a, b) {
    return a.length - b.length;
  });
  attributions = attributions.filter(function(attrib, i) {
    var j;
    for (j = i + 1; j < attributions.length; j += 1) {
      if (attributions[j].indexOf(attrib) >= 0) {
        return false;
      }
    }
    return true;
  });
  return attributions.join(' ');
}

function transition(prop) {
  var _m = this;
  _m[prop] = Object.assign(function (options) {
    if (options.zoom) {
      options.zoom = Math.floor(options.zoom);
    }
    _m[prop].superior.apply(this, arguments);
  }, {
    superior: _m[prop]
  });
}

function mapUnits(units) {
  return units === 'km' ? 'metric' : 'imperial';
}

function map(node, options) {
  var controlPosition = {
      BL: 'bottom-left',
      LB: 'bottom-left',
      BR: 'bottom-right',
      RB: 'bottom-right',
      TL: 'top-left',
      LT: 'top-left',
      TR: 'top-right',
      RT: 'top-right'
    }, self, mapTypeId;

  function element() {
    return node;
  }

  function center(c) {
    if (!c) {
      return util.mll2ll(self._m.getCenter());
    }
    self._m.setCenter(c);
  }

  function zoom(z) {
    if (z === undefined) {
      return Math.round(self._m.getZoom() + 1);
    }
    self._m[self._m.isStyleLoaded() ? 'zoomTo' : 'setZoom'](z - 1);
  }

  function bounds(b) {
    if (b === undefined) {
      return util.mbounds2bounds(self._m.getBounds());
    }
    self._m.fitBounds(b, {
      padding: 100
    });
  }

  function panToBounds(bounds) {
    // display north-west corner
    self._m.panTo([bounds[1][0], bounds[0][1]]);
    return self;
  }

  function panBy(x, y) {
    self._m.panBy([x, y]);
    return self;
  }

  function mapType() {
    return mapTypeId;
  }

  function refresh() {
    self._m.resize();
  }

  function callback() {
    options.onReady(self);
  }

  function addControl(el, position) {
    self._m.addControl(customControl({
      el: el
    }), controlPosition[position]);
    return self;
  }

  function ll(e) {
    var features;
    if (e.type === 'click') {
      features = self._m.queryRenderedFeatures(e.point, {
        filter: ['has', 'map_facade']
      });
      if (features && features.length) {
        // swallow clicks on layers (for the same behavior as Google maps)
        return;
      }
    }
    if (e && e.lngLat) {
      e.ll = util.mll2ll(e.lngLat);
    }
  }

  function units(u) {
    if (u === undefined) {
      return options.units;
    }
    options.units = u;
    if (options.scaleControl) {
      self._m.removeControl(options.scaleControl);
      options.scaleControl = new mapboxgl.ScaleControl({
        unit: mapUnits(u)
      });
      self._m.addControl(options.scaleControl, options.scaleControlOptions.position);
    }
  }

  function destroy() {
    if (self._m) {
      self.off();
      self._m.remove();
      images.destroy();
      delete self._m;
    }
  }

  options = Object.assign({
    container: node,
    scaleControl: true,
    mapTypeControl: false,
    style: {
      version: 8,
      sources: {},
      layers: []
    }
  }, options);

  self = object({
    addControl: addControl,
    bounds: bounds,
    center: center,
    destroy: destroy,
    element: element,
    fitBounds: bounds, // obsolete; use bounds(b)
    ll: ll,
    mapType: mapType,
    panBy: panBy,
    panToBounds: panToBounds,
    refresh: refresh,
    units: units,
    zoom: zoom
  });

  if (options.zoom) {
    options.zoom -= 1;
  }
  if (options.minZoom) {
    options.minZoom -= 1;
  }
  if (options.maxZoom) {
    options.maxZoom -= 1;
  }
  if (options.scaleControl === true) {
    options.scaleControl = {};
  }
  if (options.units && options.scaleControl) {
    options.scaleControl.unit = mapUnits(options.units);
  }

  [
    'mapTypeControlOptions',
    'scaleControlOptions',
    'zoomControlOptions',
    'fullscreenControlOptions'
  ].forEach(function (ctrlOptions) {
    ctrlOptions = options[ctrlOptions];
    if (ctrlOptions && ctrlOptions.position) {
      ctrlOptions.position = controlPosition[ctrlOptions.position] || ctrlOptions.position;
    }
  });

  self._m = new mapboxgl.Map(options);

  // ensure integral zoom
  ['flyTo', 'easeTo'].forEach(transition, self._m);

  if (options.onReady) {
    self._m.once('styledata', callback);
  }

  if (options.mapTypeControl) {
    self._m.addControl(mapTypeControl(options.mapTypeControlOptions, options.customMapTypes, function (id) {
      mapTypeId = id;
      self._m.fire('maptypeid_changed');
    }), (options.mapTypeControlOptions && options.mapTypeControlOptions.position) || 'bottom-left');
  }
  if (options.scaleControl) {
    options.scaleControl = new mapboxgl.ScaleControl(options.scaleControl);
    options.scaleControlOptions = options.scaleControlOptions || {};
    options.scaleControlOptions.position = options.scaleControlOptions.position || 'bottom-right';
    self._m.addControl(options.scaleControl, options.scaleControlOptions.position);
  }
  if (options.zoomControl) {
    self._m.addControl(new mapboxgl.NavigationControl(),
        (options.zoomControlOptions && options.zoomControlOptions.position) || 'bottom-right');
  }
  if (options.fullscreenControl) {
    self._m.addControl(new mapboxgl.FullscreenControl(),
        (options.fullscreenControlOptions && options.fullscreenControlOptions.position) || 'top-right');
  }
  if (options.attribution) {
    self._m.on('data', function () {
      options.attribution.innerHTML = getAttribution(self._m);
    });
  }
  return self;
}
<commit_msg>fit map to bounds without animation if the style loading is not finished<commit_after>var images = require('./images');
var object = require('./object');
var util = require('./util');

module.exports = map;

/* global mapboxgl */

function customControl(options) {
  return {
    onAdd: function (map) {
      var ctrl = this;
      ctrl._map = map;
      ctrl._container = document.createElement('div');
      ctrl._container.className = 'mapboxgl-ctrl';
      if (options.el) {
        ctrl._container.appendChild(options.el);
      }
      if (options.onAdd) {
        options.onAdd.call(ctrl, map);
      }
      return ctrl._container;
    },
    onRemove: function () {
      var ctrl = this;
      if (options.el) {
        ctrl._container.removeChild(options.el);
      }
      if (options.onRemove) {
        options.onRemove.call(ctrl);
      }
      ctrl._container.parentNode.removeChild(ctrl._container);
      delete ctrl._container;
      delete ctrl._map;
    }
  };
}

function mapTypeControl(mapTypeControlOptions, customMapTypes, fn) {
  return customControl({
    onAdd: function () {
      var ctrl = this;
      ctrl._events = [];
      mapTypeControlOptions.mapTypeIds.forEach(function (id) {
        var type = ctrl._container.appendChild(document.createElement('div')),
          ev = {
            el: type,
            event: 'click',
            fn: fn.bind(undefined, id)
          };
        type.className = 'map-type';
        type.innerHTML = '
' + customMapTypes[id].name + '
';
        type.addEventListener(ev.event, ev.fn);
        ctrl._events.push(ev);
      });
    },
    onRemove: function () {
      var ctrl = this;
      ctrl._events.forEach(function (ev) {
        ev.type.removeEventListener(ev.event, ev.fn);
      });
      delete ctrl._events;
    }
  });
}

function getAttribution(map) {
  var sourceCaches, attributions;
  if (!map.style) {
    return '';
  }
  sourceCaches = map.style.sourceCaches;
  attributions = Object.keys(sourceCaches).reduce(function (attributions, id) {
    var source = sourceCaches[id].getSource();
    if (source.attribution && attributions.indexOf(source.attribution) < 0) {
      attributions.push(source.attribution);
    }
    return attributions;
  }, []);

  // remove any entries that are substrings of another entry.
  // first sort by length so that substrings come first
  attributions.sort(function (a, b) {
    return a.length - b.length;
  });
  attributions = attributions.filter(function(attrib, i) {
    var j;
    for (j = i + 1; j < attributions.length; j += 1) {
      if (attributions[j].indexOf(attrib) >= 0) {
        return false;
      }
    }
    return true;
  });
  return attributions.join(' ');
}

function transition(prop) {
  var _m = this;
  _m[prop] = Object.assign(function (options) {
    if (options.zoom) {
      options.zoom = Math.floor(options.zoom);
    }
    _m[prop].superior.apply(this, arguments);
  }, {
    superior: _m[prop]
  });
}

function mapUnits(units) {
  return units === 'km' ? 'metric' : 'imperial';
}

function map(node, options) {
  var controlPosition = {
      BL: 'bottom-left',
      LB: 'bottom-left',
      BR: 'bottom-right',
      RB: 'bottom-right',
      TL: 'top-left',
      LT: 'top-left',
      TR: 'top-right',
      RT: 'top-right'
    }, self, mapTypeId;

  function element() {
    return node;
  }

  function center(c) {
    if (!c) {
      return util.mll2ll(self._m.getCenter());
    }
    self._m.setCenter(c);
  }

  function zoom(z) {
    if (z === undefined) {
      return Math.round(self._m.getZoom() + 1);
    }
    self._m[self._m.isStyleLoaded() ? 'zoomTo' : 'setZoom'](z - 1);
  }

  function bounds(b) {
    var opt;
    if (b === undefined) {
      return util.mbounds2bounds(self._m.getBounds());
    }
    opt = {
      padding: 100
    };
    if (!self._m.isStyleLoaded()) {
      opt.animate = false;
    }
    self._m.fitBounds(b, opt);
  }

  function panToBounds(bounds) {
    // display north-west corner
    self._m.panTo([bounds[1][0], bounds[0][1]]);
    return self;
  }

  function panBy(x, y) {
    self._m.panBy([x, y]);
    return self;
  }

  function mapType() {
    return mapTypeId;
  }

  function refresh() {
    self._m.resize();
  }

  function callback() {
    options.onReady(self);
  }

  function addControl(el, position) {
    self._m.addControl(customControl({
      el: el
    }), controlPosition[position]);
    return self;
  }

  function ll(e) {
    var features;
    if (e.type === 'click') {
      features = self._m.queryRenderedFeatures(e.point, {
        filter: ['has', 'map_facade']
      });
      if (features && features.length) {
        // swallow clicks on layers (for the same behavior as Google maps)
        return;
      }
    }
    if (e && e.lngLat) {
      e.ll = util.mll2ll(e.lngLat);
    }
  }

  function units(u) {
    if (u === undefined) {
      return options.units;
    }
    options.units = u;
    if (options.scaleControl) {
      self._m.removeControl(options.scaleControl);
      options.scaleControl = new mapboxgl.ScaleControl({
        unit: mapUnits(u)
      });
      self._m.addControl(options.scaleControl, options.scaleControlOptions.position);
    }
  }

  function destroy() {
    if (self._m) {
      self.off();
      self._m.remove();
      images.destroy();
      delete self._m;
    }
  }

  options = Object.assign({
    container: node,
    scaleControl: true,
    mapTypeControl: false,
    style: {
      version: 8,
      sources: {},
      layers: []
    }
  }, options);

  self = object({
    addControl: addControl,
    bounds: bounds,
    center: center,
    destroy: destroy,
    element: element,
    fitBounds: bounds, // obsolete; use bounds(b)
    ll: ll,
    mapType: mapType,
    panBy: panBy,
    panToBounds: panToBounds,
    refresh: refresh,
    units: units,
    zoom: zoom
  });

  if (options.zoom) {
    options.zoom -= 1;
  }
  if (options.minZoom) {
    options.minZoom -= 1;
  }
  if (options.maxZoom) {
    options.maxZoom -= 1;
  }
  if (options.scaleControl === true) {
    options.scaleControl = {};
  }
  if (options.units && options.scaleControl) {
    options.scaleControl.unit = mapUnits(options.units);
  }

  [
    'mapTypeControlOptions',
    'scaleControlOptions',
    'zoomControlOptions',
    'fullscreenControlOptions'
  ].forEach(function (ctrlOptions) {
    ctrlOptions = options[ctrlOptions];
    if (ctrlOptions && ctrlOptions.position) {
      ctrlOptions.position = controlPosition[ctrlOptions.position] || ctrlOptions.position;
    }
  });

  self._m = new mapboxgl.Map(options);

  // ensure integral zoom
  ['flyTo', 'easeTo'].forEach(transition, self._m);

  if (options.onReady) {
    self._m.once('styledata', callback);
  }

  if (options.mapTypeControl) {
    self._m.addControl(mapTypeControl(options.mapTypeControlOptions, options.customMapTypes, function (id) {
      mapTypeId = id;
      self._m.fire('maptypeid_changed');
    }), (options.mapTypeControlOptions && options.mapTypeControlOptions.position) || 'bottom-left');
  }
  if (options.scaleControl) {
    options.scaleControl = new mapboxgl.ScaleControl(options.scaleControl);
    options.scaleControlOptions = options.scaleControlOptions || {};
    options.scaleControlOptions.position = options.scaleControlOptions.position || 'bottom-right';
    self._m.addControl(options.scaleControl, options.scaleControlOptions.position);
  }
  if (options.zoomControl) {
    self._m.addControl(new mapboxgl.NavigationControl(),
        (options.zoomControlOptions && options.zoomControlOptions.position) || 'bottom-right');
  }
  if (options.fullscreenControl) {
    self._m.addControl(new mapboxgl.FullscreenControl(),
        (options.fullscreenControlOptions && options.fullscreenControlOptions.position) || 'top-right');
  }
  if (options.attribution) {
    self._m.on('data', function () {
      options.attribution.innerHTML = getAttribution(self._m);
    });
  }
  return self;
}
<|endoftext|>


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

'use strict';

// [START functions_sendgrid_setup]
const sendgrid = require('sendgrid');
const config = require('./config.json');
const uuid = require('uuid');

// Get a reference to the Cloud Storage component
const storage = require('@google-cloud/storage')();
// Get a reference to the BigQuery component
const bigquery = require('@google-cloud/bigquery')();
// [END functions_sendgrid_setup]

// [START functions_sendgrid_get_client]
/**
 * Returns a configured SendGrid client.
 *
 * @param {string} key Your SendGrid API key.
 * @returns {object} SendGrid client.
 */
function getClient (key) {
  console.log('getClient , key is ' + key);
  if (!key) {
    const error = new Error('SendGrid API key not provided. Make sure you have a "sg_key" property in your request querystring');
    error.code = 401;
    throw error;
  }

  // Using SendGrid's Node.js Library https://github.com/sendgrid/sendgrid-nodejs
  return sendgrid(key);
}
// [END functions_sendgrid_get_client]

// [START functions_get_payload]
/**
 * Constructs the SendGrid email request from the HTTP request body.
 *
 * @param {object} requestBody Cloud Function request body.
 * @param {string} data.to Email address of the recipient.
 * @param {string} data.from Email address of the sender.
 * @param {string} data.subject Email subject line.
 * @param {string} data.body Body of the email subject line.
 * @returns {object} Payload object.
 */
function getPayload (requestBody) {
  if (!requestBody.to) {
    const error = new Error('To email address not provided. Make sure you have a "to" property in your request');
    error.code = 400;
    throw error;
  } else if (!requestBody.from) {
    const error = new Error('From email address not provided. Make sure you have a "from" property in your request');
    error.code = 400;
    throw error;
  } else if (!requestBody.subject) {
    const error = new Error('Email subject line not provided. Make sure you have a "subject" property in your request');
    error.code = 400;
    throw error;
  } else if (!requestBody.body) {
    const error = new Error('Email content not provided. Make sure you have a "body" property in your request');
    error.code = 400;
    throw error;
  }

  return {
    personalizations: [
      {
        to: [
          {
            email: requestBody.to
          }
        ],
        subject: requestBody.subject
      }
    ],
    from: {
      email: requestBody.from
    },
    content: [
      {
        type: 'text/plain',
        value: requestBody.body
      }
    ]
  };
}
// [END functions_get_payload]

// [START functions_sendgrid_email]
/**
 * Send an email using SendGrid.
 *
 * Trigger this function by making a POST request with a payload to:
 * https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/sendEmail?sg_key=[YOUR_API_KEY]
 *
 * @example
 * curl -X POST "https://us-central1.your-project-id.cloudfunctions.net/sendEmail?sg_key=your_api_key" --data '{"to":"bob@email.com","from":"alice@email.com","subject":"Hello from Sendgrid!","body":"Hello World!"}' --header "Content-Type: application/json"
 *
 * @param {object} req Cloud Function request context.
 * @param {object} req.query The parsed querystring.
 * @param {string} req.query.sg_key Your SendGrid API key.
 * @param {object} req.body The request payload.
 * @param {string} req.body.to Email address of the recipient.
 * @param {string} req.body.from Email address of the sender.
 * @param {string} req.body.subject Email subject line.
 * @param {string} req.body.body Body of the email subject line.
 * @param {object} res Cloud Function response context.
 */
exports.sendgridEmail = function sendgridEmail (req, res) {
  return Promise.resolve()
    .then(() => {
      console.log(`sendgridEmail`);
      if (req.method !== 'POST') {
        const error = new Error('Only POST requests are accepted');
        error.code = 405;
        throw error;
      }

      console.log(`sendgridEmail, req.query.sg_key is ` + req.query.sg_key);

      // Get a SendGrid client
      const client = getClient(req.query.sg_key);

      // Build the SendGrid request to send email
      const request = client.emptyRequest({
        method: 'POST',
        path: '/v3/mail/send',
        body: getPayload(req.body)
      });

      // Make the request to SendGrid's API
      console.log(`Sending email to: ${req.body.to}`);
      return client.API(request);
    })
    .then((response) => {
      if (response.statusCode < 200 || response.statusCode >= 400) {
        const error = Error(response.body);
        error.code = response.statusCode;
        throw error;
      }

      console.log(`Email sent to: ${req.body.to}`);

      // Forward the response back to the requester
      res.status(response.statusCode);
      if (response.headers['content-type']) {
        res.set('content-type', response.headers['content-type']);
      }
      if (response.headers['content-length']) {
        res.set('content-length', response.headers['content-length']);
      }
      if (response.body) {
        res.send(response.body);
      } else {
        res.end();
      }
    })
    .catch((err) => {
      console.error(err);
      const code = err.code || (err.response ? err.response.statusCode : 500) || 500;
      res.status(code).send(err);
      return Promise.reject(err);
    });
};
// [END functions_sendgrid_email]

// [START functions_sendgrid_verify_webhook]
/**
 * Verify that the webhook request came from sendgrid.
 *
 * @param {string} authorization The authorization header of the request, e.g. "Basic ZmdvOhJhcg=="
 */
function verifyWebhook (authorization) {
  const basicAuth = new Buffer(authorization.replace('Basic ', ''), 'base64').toString();
  const parts = basicAuth.split(':');
  if (parts[0] !== config.USERNAME || parts[1] !== config.PASSWORD) {
    const error = new Error('Invalid credentials');
    error.code = 401;
    throw error;
  }
}
// [END functions_sendgrid_verify_webhook]

// [START functions_sendgrid_fix_names]
/**
 * Recursively rename properties in to meet BigQuery field name requirements.
 *
 * @param {*} obj Value to examine.
 */
function fixNames (obj) {
  if (Array.isArray(obj)) {
    obj.forEach(fixNames);
  } else if (obj && typeof obj === 'object') {
    Object.keys(obj).forEach((key) => {
      const value = obj[key];
      fixNames(value);
      const fixedKey = key.replace('-', '_');
      if (fixedKey !== key) {
        obj[fixedKey] = value;
        delete obj[key];
      }
    });
  }
}
// [END functions_sendgrid_fix_names]

// [START functions_sendgrid_webhook]
/**
 * Receive a webhook from SendGrid.
 *
 * See https://sendgrid.com/docs/API_Reference/Webhooks/event.html
 *
 * @param {object} req Cloud Function request context.
 * @param {object} res Cloud Function response context.
 */
exports.sendgridWebhook = function sendgridWebhook (req, res) {
  return Promise.resolve()
    .then(() => {
      if (req.method !== 'POST') {
        const error = new Error('Only POST requests are accepted');
        error.code = 405;
        throw error;
      }

      verifyWebhook(req.get('authorization') || '');

      const events = req.body || [];

      // Make sure property names in the data meet BigQuery standards
      fixNames(events);

      // Generate newline-delimited JSON
      // See https://cloud.google.com/bigquery/data-formats#json_format
      const json = events.map((event) => JSON.stringify(event)).join('\n');

      // Upload a new file to Cloud Storage if we have events to save
      if (json.length) {
        const bucketName = config.EVENT_BUCKET;
        const unixTimestamp = new Date().getTime() * 1000;
        const filename = `${unixTimestamp}-${uuid.v4()}.json`;
        const file = storage.bucket(bucketName).file(filename);

        console.log(`Saving events to ${filename} in bucket ${bucketName}`);

        return file.save(json).then(() => {
          console.log(`JSON written to ${filename}`);
        });
      }
    })
    .then(() => res.status(200).end())
    .catch((err) => {
      console.error(err);
      res.status(err.code || 500).send(err);
      return Promise.reject(err);
    });
};
// [END functions_sendgrid_webhook]

// [START functions_sendgrid_get_table]
/**
 * Helper method to get a handle on a BigQuery table. Automatically creates the
 * dataset and table if necessary.
 */
function getTable () {
  const dataset = bigquery.dataset(config.DATASET);

  return dataset.get({ autoCreate: true })
    .then(([dataset]) => dataset.table(config.TABLE).get({ autoCreate: true }));
}
// [END functions_sendgrid_get_table]

// [START functions_sendgrid_load]
/**
 * Cloud Function triggered by Cloud Storage when a file is uploaded.
 *
 * @param {object} event The Cloud Functions event.
 * @param {object} event.data A Cloud Storage file object.
 * @param {string} event.data.bucket Name of the Cloud Storage bucket.
 * @param {string} event.data.name Name of the file.
 * @param {string} [event.data.timeDeleted] Time the file was deleted if this is a deletion event.
 * @see https://cloud.google.com/storage/docs/json_api/v1/objects#resource
 */
exports.sendgridLoad = function sendgridLoad (event) {
  const file = event.data;

  if (file.resourceState === 'not_exists') {
    // This was a deletion event, we don't want to process this
    return;
  }

  return Promise.resolve()
    .then(() => {
      if (!file.bucket) {
        throw new Error('Bucket not provided. Make sure you have a "bucket" property in your request');
      } else if (!file.name) {
        throw new Error('Filename not provided. Make sure you have a "name" property in your request');
      }

      return getTable();
    })
    .then(([table]) => {
      const fileObj = storage.bucket(file.bucket).file(file.name);
      console.log(`Starting job for ${file.name}`);
      const metadata = {
        autodetect: true,
        sourceFormat: 'NEWLINE_DELIMITED_JSON'
      };
      return table.import(fileObj, metadata);
    })
    .then(([job]) => job.promise())
    .then(() => console.log(`Job complete for ${file.name}`))
    .catch((err) => {
      console.log(`Job failed for ${file.name}`);
      return Promise.reject(err);
    });
};
// [END functions_sendgrid_load]
<commit_msg>send grid - send mail via gogle function<commit_after>/**
 * Copyright 2016, Google, Inc.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

// [START functions_sendgrid_setup]
const sendgrid = require('sendgrid');
const config = require('./config.json');
const uuid = require('uuid');

// Get a reference to the Cloud Storage component
const storage = require('@google-cloud/storage')();
// Get a reference to the BigQuery component
const bigquery = require('@google-cloud/bigquery')();
// [END functions_sendgrid_setup]

// [START functions_sendgrid_get_client]
/**
 * Returns a configured SendGrid client.
 *
 * @param {string} key Your SendGrid API key.
 * @returns {object} SendGrid client.
 */
function getClient(key) {
  console.log('getClient , key is ' + key);
  if (!key) {
    const error = new Error('SendGrid API key not provided. Make sure you have a "sg_key" property in your request querystring');
    error.code = 401;
    throw error;
  }

  // Using SendGrid's Node.js Library https://github.com/sendgrid/sendgrid-nodejs
  return sendgrid(key);
}
// [END functions_sendgrid_get_client]

// [START functions_get_payload]
/**
 * Constructs the SendGrid email request from the HTTP request body.
 *
 * @param {object} requestBody Cloud Function request body.
 * @param {string} data.to Email address of the recipient.
 * @param {string} data.from Email address of the sender.
 * @param {string} data.subject Email subject line.
 * @param {string} data.body Body of the email subject line.
 * @returns {object} Payload object.
 */
function getPayload(requestBody) {
  if (!requestBody.to) {
    const error = new Error('To email address not provided. Make sure you have a "to" property in your request');
    error.code = 400;
    throw error;
  } else if (!requestBody.from) {
    const error = new Error('From email address not provided. Make sure you have a "from" property in your request');
    error.code = 400;
    throw error;
  } else if (!requestBody.subject) {
    const error = new Error('Email subject line not provided. Make sure you have a "subject" property in your request');
    error.code = 400;
    throw error;
  } else if (!requestBody.body) {
    const error = new Error('Email content not provided. Make sure you have a "body" property in your request');
    error.code = 400;
    throw error;
  }

  return {
    personalizations: [
      {
        to: [
          {
            email: requestBody.to
          }
        ],
        subject: requestBody.subject
      }
    ],
    from: {
      email: requestBody.from
    },
    content: [
      {
        type: 'text/plain',
        value: requestBody.body
      }
    ]
  };
}
// [END functions_get_payload]

// [START functions_sendgrid_email]
/**
 * Send an email using SendGrid.
 *
 * Trigger this function by making a POST request with a payload to:
 * https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/sendEmail?sg_key=[YOUR_API_KEY]
 *
 * @example
 * curl -X POST "https://us-central1.your-project-id.cloudfunctions.net/sendEmail?sg_key=your_api_key" --data '{"to":"bob@email.com","from":"alice@email.com","subject":"Hello from Sendgrid!","body":"Hello World!"}' --header "Content-Type: application/json"
 *
 * @param {object} req Cloud Function request context.
 * @param {object} req.query The parsed querystring.
 * @param {string} req.query.sg_key Your SendGrid API key.
 * @param {object} req.body The request payload.
 * @param {string} req.body.to Email address of the recipient.
 * @param {string} req.body.from Email address of the sender.
 * @param {string} req.body.subject Email subject line.
 * @param {string} req.body.body Body of the email subject line.
 * @param {object} res Cloud Function response context.
 */
exports.sendgridEmail = function sendgridEmail(req, res) {
  return Promise.resolve()
    .then(() => {
      if (req.method === 'OPTIONS') {
        console.log(' OPTIONS');

        let headers = {};
        // IE8 does not allow domains to be specified, just the *
        // headers["Access-Control-Allow-Origin"] = req.headers.origin;
        headers["Access-Control-Allow-Origin"] = "*";
        headers["Access-Control-Allow-Methods"] = "POST, GET, PUT, DELETE, OPTIONS";
        headers["Access-Control-Allow-Credentials"] = false;
        headers["Access-Control-Max-Age"] = '86400'; // 24 hours
        headers["Access-Control-Allow-Headers"] = "access-control-allow-origin,content-type";
        res.writeHead(200, headers);
        res.end();
        console.log(' Return OPTIONS');
        return {};
      }
      else {
        console.log('POST');

        console.log(`sendgridEmail`);
        console.log(`sendgridEmail, req ` + Object.keys(req));
        console.log(`sendgridEmail, req.query ` + Object.keys(req.query));
        console.log(`sendgridEmail, req.method ` + req.method + '  ' + Object.keys(req.method));
        console.log(`sendgridEmail, req.body ` + Object.keys(req.body));
        if (!((req.method === 'POST' ) || (req.method === 'OPTIONS' ))) {
          const error = new Error('Only POST requests are accepted');
          error.code = 405;
          throw error;
        }

  // Get a SendGrid client

        const client = getClient(req.query.sg_key);
        console.log(`client - ` + client);

        // Build the SendGrid request to send email

        const request = client.emptyRequest({method: 'POST', path: '/v3/mail/send', body: getPayload(req.body)});
        console.log(`request - ` + request);

        // Make the request to SendGrid's API

        console.log(`Sending email to: ${req.body.to}`);
        return client.API(request)
          .then((response) => {
            console.log(``);
            if (response.statusCode < 200 || response.statusCode >= 400) {
              const error = Error(response.body);
              error.code = response.statusCode;
              throw error;
            }

            console.log(`Email sent to: ${req.body.to}`);
            // Forward the response back to the requester

            res.status(response.statusCode);
            if (response.headers['content-type']) {
              res.set('content-type', response.headers['content-type']);
            }
            if (response.headers['content-length']) {
              res.set('content-length', response.headers['content-length']);
            }
            if (response.headers['Access-Control-Allow-Origin']) {
              res.set('Access-Control-Allow-Origin', response.headers['Access-Control-Allow-Origin']);
            }
            if (response.headers['Access-Control-Allow-Methods']) {
              res.set('Access-Control-Allow-Methods', response.headers['Access-Control-Allow-Methods']);
            }
            if (response.headers['Access-Control-Allow-Headers']) {
              res.set('Access-Control-Allow-Headers', response.headers['Access-Control-Allow-Headers']);
            }
            if (response.body) {
              res.send(response.body);
            } else {
              res.end();
              console.log(' Return POST');
            }
          });
      }
    })
    .catch((err) => {
      console.error(err);
      const code = err.code || (err.response ? err.response.statusCode : 500) || 500;
      res.status(code).send(err);
      return Promise.reject(err);
    });
};
// [END functions_sendgrid_email]

// [START functions_sendgrid_verify_webhook]
/**
 * Verify that the webhook request came from sendgrid.
 *
 * @param {string} authorization The authorization header of the request, e.g. "Basic ZmdvOhJhcg=="
 */
function verifyWebhook(authorization) {
  const basicAuth = new Buffer(authorization.replace('Basic ', ''), 'base64').toString();
  const parts = basicAuth.split(':');
  if (parts[0] !== config.USERNAME || parts[1] !== config.PASSWORD) {
    const error = new Error('Invalid credentials');
    error.code = 401;
    throw error;
  }
}
// [END functions_sendgrid_verify_webhook]

// [START functions_sendgrid_fix_names]
/**
 * Recursively rename properties in to meet BigQuery field name requirements.
 *
 * @param {*} obj Value to examine.
 */
function fixNames(obj) {
  if (Array.isArray(obj)) {
    obj.forEach(fixNames);
  } else if (obj && typeof obj === 'object') {
    Object.keys(obj).forEach((key) => {
      const value = obj[key];
      fixNames(value);
      const fixedKey = key.replace('-', '_');
      if (fixedKey !== key) {
        obj[fixedKey] = value;
        delete obj[key];
      }
    });
  }
}
// [END functions_sendgrid_fix_names]

// [START functions_sendgrid_webhook]
/**
 * Receive a webhook from SendGrid.
 *
 * See https://sendgrid.com/docs/API_Reference/Webhooks/event.html
 *
 * @param {object} req Cloud Function request context.
 * @param {object} res Cloud Function response context.
 */
exports.sendgridWebhook = function sendgridWebhook(req, res) {
  return Promise.resolve()
    .then(() => {
      if (req.method !== 'POST') {
        const error = new Error('Only POST requests are accepted');
        error.code = 405;
        throw error;
      }

      verifyWebhook(req.get('authorization') || '');

      const events = req.body || [];

      // Make sure property names in the data meet BigQuery standards
      fixNames(events);

      // Generate newline-delimited JSON
      // See https://cloud.google.com/bigquery/data-formats#json_format
      const json = events.map((event) => JSON.stringify(event)).join('\n');

      // Upload a new file to Cloud Storage if we have events to save
      if (json.length) {
        const bucketName = config.EVENT_BUCKET;
        const unixTimestamp = new Date().getTime() * 1000;
        const filename = `${unixTimestamp}-${uuid.v4()}.json`;
        const file = storage.bucket(bucketName).file(filename);

        console.log(`Saving events to ${filename} in bucket ${bucketName}`);

        return file.save(json).then(() => {
          console.log(`JSON written to ${filename}`);
        });
      }
    })
    .then(() => res.status(200).end())
    .catch((err) => {
      console.error(err);
      res.status(err.code || 500).send(err);
      return Promise.reject(err);
    });
};
// [END functions_sendgrid_webhook]

// [START functions_sendgrid_get_table]
/**
 * Helper method to get a handle on a BigQuery table. Automatically creates the
 * dataset and table if necessary.
 */
function getTable() {
  const dataset = bigquery.dataset(config.DATASET);

  return dataset.get({autoCreate: true})
    .then(([dataset]) => dataset.table(config.TABLE).get({autoCreate: true}));
}
// [END functions_sendgrid_get_table]

// [START functions_sendgrid_load]
/**
 * Cloud Function triggered by Cloud Storage when a file is uploaded.
 *
 * @param {object} event The Cloud Functions event.
 * @param {object} event.data A Cloud Storage file object.
 * @param {string} event.data.bucket Name of the Cloud Storage bucket.
 * @param {string} event.data.name Name of the file.
 * @param {string} [event.data.timeDeleted] Time the file was deleted if this is a deletion event.
 * @see https://cloud.google.com/storage/docs/json_api/v1/objects#resource
 */
exports.sendgridLoad = function sendgridLoad(event) {
  const file = event.data;

  if (file.resourceState === 'not_exists') {
    // This was a deletion event, we don't want to process this
    return;
  }

  return Promise.resolve()
    .then(() => {
      if (!file.bucket) {
        throw new Error('Bucket not provided. Make sure you have a "bucket" property in your request');
      } else if (!file.name) {
        throw new Error('Filename not provided. Make sure you have a "name" property in your request');
      }

      return getTable();
    })
    .then(([table]) => {
      const fileObj = storage.bucket(file.bucket).file(file.name);
      console.log(`Starting job for ${file.name}`);
      const metadata = {
        autodetect: true,
        sourceFormat: 'NEWLINE_DELIMITED_JSON'
      };
      return table.import(fileObj, metadata);
    })
    .then(([job]) => job.promise())
    .then(() => console.log(`Job complete for ${file.name}`))
    .catch((err) => {
      console.log(`Job failed for ${file.name}`);
      return Promise.reject(err);
    });
};
// [END functions_sendgrid_load]
<|endoftext|>


<commit_before>/* Release checklist
- Run `git changelog` and edit to match previous output (this should make use of jquey-release instead)
- pull latest https://github.com/jquery/jquery-release
- disable _generateChangelog task in release.js (BOOOO)
- adjust commit message in jquery-release/lib/repo.js to prepand "Release:", to make the commit hook happy
- run
	node release.js --remote=jzaefferer/jquery-validation
- Wait a while, verify and confirm each step
- Create GitHub release: Pick the new tag, add changelog, upload zip
- Update MS CDN (see 1password for url and credentials)
- Check jsdelivr CDN
- Update validation-content/pages/index.html (may have to hold off on CDN updates until available)
- Write blog post: Some highlights, changelog, download links
*/

/*jshint node:true */
module.exports = function( Release ) {

function today() {
	return new Date().toISOString().replace(/T.+/, "");
}

// also add version property to this
Release._jsonFiles.push( "validation.jquery.json" );

Release.define({
	issueTracker: "github",
	changelogShell: function() {
		return Release.newVersion + " / " + today() + "\n==================\n\n";
	},

	generateArtifacts: function( done ) {
		Release.exec( "grunt release", "Grunt command failed" );
		done([
			"dist/additional-methods.js",
			"dist/additional-methods.min.js",
			"dist/jquery.validate.js",
			"dist/jquery.validate.min.js"
		]);
	},

	cdnPublish: false,
	npmPublish: true,

	// disable authors check
	_checkAuthorsTxt: function() {}
});

};
<commit_msg>Build: removed node regarding patching of jquery-release<commit_after>/* Release checklist
- Run `git changelog` and edit to match previous output (this should make use of jquey-release instead)
- pull latest https://github.com/jquery/jquery-release
- disable _generateChangelog task in release.js (BOOOO)
- run
	node release.js --remote=jzaefferer/jquery-validation
- Wait a while, verify and confirm each step
- Create GitHub release: Pick the new tag, add changelog, upload zip
- Update MS CDN (see 1password for url and credentials)
- Check jsdelivr CDN
- Update validation-content/pages/index.html (may have to hold off on CDN updates until available)
- Write blog post: Some highlights, changelog, download links
*/

/*jshint node:true */
module.exports = function( Release ) {

function today() {
	return new Date().toISOString().replace(/T.+/, "");
}

// also add version property to this
Release._jsonFiles.push( "validation.jquery.json" );

Release.define({
	issueTracker: "github",
	changelogShell: function() {
		return Release.newVersion + " / " + today() + "\n==================\n\n";
	},

	generateArtifacts: function( done ) {
		Release.exec( "grunt release", "Grunt command failed" );
		done([
			"dist/additional-methods.js",
			"dist/additional-methods.min.js",
			"dist/jquery.validate.js",
			"dist/jquery.validate.min.js"
		]);
	},

	cdnPublish: false,
	npmPublish: true,

	// disable authors check
	_checkAuthorsTxt: function() {}
});

};
<|endoftext|>


<commit_before>//Finance.js
//For more information, visit http://financejs.org
//Copyright 2014 - 2015 Essam Al Joubori, MIT license

// Instantiate a Finance class
var Finance = function() {};

// Present Value (PV)
Finance.prototype.PV = function (rate, cf1, numOfPeriod) {
  numOfPeriod = typeof numOfPeriod !== 'undefined' ? numOfPeriod : 1;
  var rate = rate/100, pv;
  pv = cf1 / Math.pow((1 + rate),numOfPeriod);
  return Math.round(pv * 100) / 100;
};

// Future Value (FV)
Finance.prototype.FV = function (rate, cf0, numOfPeriod) {
  var rate = rate/100, fv;
  fv = cf0 * Math.pow((1 + rate), numOfPeriod);
  return Math.round(fv * 100) / 100;
};

// Net Present Value (NPV)
Finance.prototype.NPV = function (rate) {
  var rate = rate/100, npv = arguments[1];
  for (var i = 2; i < arguments.length; i++) {
    npv +=(arguments[i] / Math.pow((1 + rate), i - 1));
  }
  return Math.round(npv * 100) / 100;
};

// seekZero seeks the zero point of the function fn(x), accurate to within x \pm 0.01. fn(x) must be decreasing with x.
function seekZero(fn) {
  var x = 1;
  while (fn(x) > 0) {
    x += 1;
  }
  while (fn(x) < 0) {
    x -= 0.01
  }
  return x + 0.01;
}

// Internal Rate of Return (IRR)
Finance.prototype.IRR = function(cfs) {
  var args = arguments;
  var numberOfTries = 1;
  // Cash flow values must contain at least one positive value and one negative value
  var positive, negative;
  Array.prototype.slice.call(args).forEach(function (value) {
    if (value > 0) positive = true;
    if (value < 0) negative = true;
  })
  if (!positive || !negative) throw new Error('IRR requires at least one positive value and one negative value');
  function npv(rate) {
    numberOfTries++;
    if (numberOfTries > 1000) {
      throw new Error('IRR can\'t find a result');
    }
    var rrate = (1 + rate/100);
    var npv = args[0];
    for (var i = 1; i < args.length; i++) {
      npv += (args[i] / Math.pow(rrate, i));
    }
    return npv;
  }
  return Math.round(seekZero(npv) * 100) / 100;
};

// Payback Period (PP)
Finance.prototype.PP = function(numOfPeriods, cfs) {
  // for even cash flows
  if (numOfPeriods === 0) {
    return Math.abs(arguments[1]) / arguments[2];
  }
  // for uneven cash flows
  var cumulativeCashFlow = arguments[1];
  var yearsCounter = 1;
  for (i = 2; i < arguments.length; i++) {
    cumulativeCashFlow += arguments[i];
    if (cumulativeCashFlow > 0) {
      yearsCounter += (cumulativeCashFlow - arguments[i]) / arguments[i];
      return yearsCounter;
    } else {
      yearsCounter++;
    }
  }
};

// Return on Investment (ROI)
Finance.prototype.ROI = function(cf0, earnings) {
  var roi = (earnings - Math.abs(cf0)) / Math.abs(cf0) * 100;
  return Math.round(roi * 100) / 100;
};

// Amortization
Finance.prototype.AM = function (principal, rate, period, yearOrMonth, payAtBeginning) {
  var numerator, denominator, am;
  var ratePerPeriod = rate / 12 / 100;

  // for inputs in years
  if (!yearOrMonth) {
    numerator = buildNumerator(period * 12);
    denominator = Math.pow((1 + ratePerPeriod), period * 12) - 1;

  // for inputs in months
  } else if (yearOrMonth === 1) {
    numerator = buildNumerator(period)
    denominator = Math.pow((1 + ratePerPeriod), period) - 1;

  } else {
    console.log('not defined');
  }
  am = principal * (numerator / denominator);
  return Math.round(am * 100) / 100;

  function buildNumerator(numInterestAccruals){
    if( payAtBeginning ){
      //if payments are made in the beginning of the period, then interest shouldn't be calculated for first period
      numInterestAccruals -= 1;
    }
    return ratePerPeriod * Math.pow((1 + ratePerPeriod), numInterestAccruals);
  }
};

// Profitability Index (PI)
Finance.prototype.PI = function(rate, cfs){
  var totalOfPVs = 0, PI;
  for (var i = 2; i < arguments.length; i++) {
    var discountFactor;
    // calculate discount factor
    discountFactor = 1 / Math.pow((1 + rate/100), (i - 1));
    totalOfPVs += arguments[i] * discountFactor;
  }
  PI = totalOfPVs/Math.abs(arguments[1]);
  return Math.round(PI * 100) / 100;
};

// Discount Factor (DF)
Finance.prototype.DF = function(rate, numOfPeriods) {
  var dfs = [], discountFactor;
  for (var i = 1; i < numOfPeriods; i++) {
    discountFactor = 1 / Math.pow((1 + rate/100), (i - 1));
    roundedDiscountFactor = Math.ceil(discountFactor * 1000)/1000;
    dfs.push(roundedDiscountFactor);
  }
  return dfs;
};

// Compound Interest (CI)
Finance.prototype.CI = function(rate, numOfCompoundings, principal, numOfPeriods) {
  var CI = principal * Math.pow((1 + ((rate/100)/ numOfCompoundings)), numOfCompoundings * numOfPeriods);
  return Math.round(CI * 100) / 100;
};

// Compound Annual Growth Rate (CAGR)
Finance.prototype.CAGR = function(beginningValue, endingValue, numOfPeriods) {
  var CAGR = Math.pow((endingValue / beginningValue), 1 / numOfPeriods) - 1;
  return Math.round(CAGR * 10000) / 100;
};

// Leverage Ratio (LR)
Finance.prototype.LR = function(totalLiabilities, totalDebts, totalIncome) {
  return (totalLiabilities  + totalDebts) / totalIncome;
};

// Rule of 72
Finance.prototype.R72 = function(rate) {
  return 72 / rate;
};

// Weighted Average Cost of Capital (WACC)
Finance.prototype.WACC = function(marketValueOfEquity, marketValueOfDebt, costOfEquity, costOfDebt, taxRate) {
  E = marketValueOfEquity;
  D = marketValueOfDebt;
  V =  marketValueOfEquity + marketValueOfDebt;
  Re = costOfEquity;
  Rd = costOfDebt;
  T = taxRate;

  var WACC = ((E / V) * Re/100) + (((D / V) * Rd/100) * (1 - T/100));
  return Math.round(WACC * 1000) / 10;
};

// PMT calculates the payment for a loan based on constant payments and a constant interest rate
Finance.prototype.PMT = function(fractionalRate, numOfPayments, principal) {
  return -principal * fractionalRate/(1-Math.pow(1+fractionalRate,-numOfPayments))
};

// IAR calculates the Inflation-adjusted return
Finance.prototype.IAR = function(investmentReturn, inflationRate){
  return 100 * (((1 + investmentReturn) / (1 + inflationRate)) - 1);
}

// XIRR - IRR for irregular intervals
Finance.prototype.XIRR = function(cfs, dts, guess) {
if (cfs.length != dts.length) throw new Error('Number of cash flows and dates should match');

  var positive, negative;
  Array.prototype.slice.call(cfs).forEach(function (value) {
    if (value > 0) positive = true;
    if (value < 0) negative = true;
  });

  if (!positive || !negative) throw new Error('XIRR requires at least one positive value and one negative value');

  guess = !!guess ? guess : 0;

  var limit = 100; //loop limit
  var guess_last;
  var durs = [];

  durs.push(0);

  //Create Array of durations from First date
  for(var i = 1; i < dts.length; i++) {
    durs.push(durYear(dts[0], dts[i]));
  }

  do {
    guess_last = guess;
    guess = guess_last - sumEq(cfs, durs, guess_last);
    limit--;

  }while(guess_last.toFixed(5) != guess.toFixed(5) && limit > 0);

  var xirr = guess_last.toFixed(5) != guess.toFixed(5) ? null : guess*100;

  return Math.round(xirr * 100) / 100;
}

//CAPM calculates expected return of an asset.
Finance.prototype.CAPM = function (rf, beta, emr, err) {
  var ans = rf/100 + beta * (emr/100 - rf/100);
  return ans;
}

//Returns the Value of stock with dividend growing at a constant growth rate to perpetuity.
Finance.prototype.stockPV = function (g, ke, D0) {
  var valueOfStock = (D0 * (1 + g/100))/((ke/100) - (g/100))
  return Math.round(valueOfStock)
}

//Returns Sum of f(x)/f'(x)
function sumEq(cfs, durs, guess) {
  var sum_fx = 0;
  var sum_fdx = 0;
  for (var i = 0; i < cfs.length; i++) {
    sum_fx = sum_fx + (cfs[i] / Math.pow(1 + guess, durs[i]));
  }
  for ( i = 0; i < cfs.length; i++) {
    sum_fdx = sum_fdx + (-cfs[i] * durs[i] * Math.pow(1 + guess, -1 - durs[i]));
  }
  return sum_fx / sum_fdx;
}

//Returns duration in years between two dates
function durYear(first, last) {
  return (Math.abs(last.getTime() - first.getTime()) / (1000 * 3600 * 24 * 365));
}

if (typeof exports !== 'undefined') {
  if (typeof module !== 'undefined' && module.exports) {
      module.exports = Finance;
      module.exports.Finance = Finance;
  }
}
<commit_msg>Added the formula for clculating the value of stock using the Gordon Growth model<commit_after>//Finance.js
//For more information, visit http://financejs.org
//Copyright 2014 - 2015 Essam Al Joubori, MIT license

// Instantiate a Finance class
var Finance = function() {};

// Present Value (PV)
Finance.prototype.PV = function (rate, cf1, numOfPeriod) {
  numOfPeriod = typeof numOfPeriod !== 'undefined' ? numOfPeriod : 1;
  var rate = rate/100, pv;
  pv = cf1 / Math.pow((1 + rate),numOfPeriod);
  return Math.round(pv * 100) / 100;
};

// Future Value (FV)
Finance.prototype.FV = function (rate, cf0, numOfPeriod) {
  var rate = rate/100, fv;
  fv = cf0 * Math.pow((1 + rate), numOfPeriod);
  return Math.round(fv * 100) / 100;
};

// Net Present Value (NPV)
Finance.prototype.NPV = function (rate) {
  var rate = rate/100, npv = arguments[1];
  for (var i = 2; i < arguments.length; i++) {
    npv +=(arguments[i] / Math.pow((1 + rate), i - 1));
  }
  return Math.round(npv * 100) / 100;
};

// seekZero seeks the zero point of the function fn(x), accurate to within x \pm 0.01. fn(x) must be decreasing with x.
function seekZero(fn) {
  var x = 1;
  while (fn(x) > 0) {
    x += 1;
  }
  while (fn(x) < 0) {
    x -= 0.01
  }
  return x + 0.01;
}

// Internal Rate of Return (IRR)
Finance.prototype.IRR = function(cfs) {
  var args = arguments;
  var numberOfTries = 1;
  // Cash flow values must contain at least one positive value and one negative value
  var positive, negative;
  Array.prototype.slice.call(args).forEach(function (value) {
    if (value > 0) positive = true;
    if (value < 0) negative = true;
  })
  if (!positive || !negative) throw new Error('IRR requires at least one positive value and one negative value');
  function npv(rate) {
    numberOfTries++;
    if (numberOfTries > 1000) {
      throw new Error('IRR can\'t find a result');
    }
    var rrate = (1 + rate/100);
    var npv = args[0];
    for (var i = 1; i < args.length; i++) {
      npv += (args[i] / Math.pow(rrate, i));
    }
    return npv;
  }
  return Math.round(seekZero(npv) * 100) / 100;
};

// Payback Period (PP)
Finance.prototype.PP = function(numOfPeriods, cfs) {
  // for even cash flows
  if (numOfPeriods === 0) {
    return Math.abs(arguments[1]) / arguments[2];
  }
  // for uneven cash flows
  var cumulativeCashFlow = arguments[1];
  var yearsCounter = 1;
  for (i = 2; i < arguments.length; i++) {
    cumulativeCashFlow += arguments[i];
    if (cumulativeCashFlow > 0) {
      yearsCounter += (cumulativeCashFlow - arguments[i]) / arguments[i];
      return yearsCounter;
    } else {
      yearsCounter++;
    }
  }
};

// Return on Investment (ROI)
Finance.prototype.ROI = function(cf0, earnings) {
  var roi = (earnings - Math.abs(cf0)) / Math.abs(cf0) * 100;
  return Math.round(roi * 100) / 100;
};

// Amortization
Finance.prototype.AM = function (principal, rate, period, yearOrMonth, payAtBeginning) {
  var numerator, denominator, am;
  var ratePerPeriod = rate / 12 / 100;

  // for inputs in years
  if (!yearOrMonth) {
    numerator = buildNumerator(period * 12);
    denominator = Math.pow((1 + ratePerPeriod), period * 12) - 1;

  // for inputs in months
  } else if (yearOrMonth === 1) {
    numerator = buildNumerator(period)
    denominator = Math.pow((1 + ratePerPeriod), period) - 1;

  } else {
    console.log('not defined');
  }
  am = principal * (numerator / denominator);
  return Math.round(am * 100) / 100;

  function buildNumerator(numInterestAccruals){
    if( payAtBeginning ){
      //if payments are made in the beginning of the period, then interest shouldn't be calculated for first period
      numInterestAccruals -= 1;
    }
    return ratePerPeriod * Math.pow((1 + ratePerPeriod), numInterestAccruals);
  }
};

// Profitability Index (PI)
Finance.prototype.PI = function(rate, cfs){
  var totalOfPVs = 0, PI;
  for (var i = 2; i < arguments.length; i++) {
    var discountFactor;
    // calculate discount factor
    discountFactor = 1 / Math.pow((1 + rate/100), (i - 1));
    totalOfPVs += arguments[i] * discountFactor;
  }
  PI = totalOfPVs/Math.abs(arguments[1]);
  return Math.round(PI * 100) / 100;
};

// Discount Factor (DF)
Finance.prototype.DF = function(rate, numOfPeriods) {
  var dfs = [], discountFactor;
  for (var i = 1; i < numOfPeriods; i++) {
    discountFactor = 1 / Math.pow((1 + rate/100), (i - 1));
    roundedDiscountFactor = Math.ceil(discountFactor * 1000)/1000;
    dfs.push(roundedDiscountFactor);
  }
  return dfs;
};

// Compound Interest (CI)
Finance.prototype.CI = function(rate, numOfCompoundings, principal, numOfPeriods) {
  var CI = principal * Math.pow((1 + ((rate/100)/ numOfCompoundings)), numOfCompoundings * numOfPeriods);
  return Math.round(CI * 100) / 100;
};

// Compound Annual Growth Rate (CAGR)
Finance.prototype.CAGR = function(beginningValue, endingValue, numOfPeriods) {
  var CAGR = Math.pow((endingValue / beginningValue), 1 / numOfPeriods) - 1;
  return Math.round(CAGR * 10000) / 100;
};

// Leverage Ratio (LR)
Finance.prototype.LR = function(totalLiabilities, totalDebts, totalIncome) {
  return (totalLiabilities  + totalDebts) / totalIncome;
};

// Rule of 72
Finance.prototype.R72 = function(rate) {
  return 72 / rate;
};

// Weighted Average Cost of Capital (WACC)
Finance.prototype.WACC = function(marketValueOfEquity, marketValueOfDebt, costOfEquity, costOfDebt, taxRate) {
  E = marketValueOfEquity;
  D = marketValueOfDebt;
  V =  marketValueOfEquity + marketValueOfDebt;
  Re = costOfEquity;
  Rd = costOfDebt;
  T = taxRate;

  var WACC = ((E / V) * Re/100) + (((D / V) * Rd/100) * (1 - T/100));
  return Math.round(WACC * 1000) / 10;
};

// PMT calculates the payment for a loan based on constant payments and a constant interest rate
Finance.prototype.PMT = function(fractionalRate, numOfPayments, principal) {
  return -principal * fractionalRate/(1-Math.pow(1+fractionalRate,-numOfPayments))
};

// IAR calculates the Inflation-adjusted return
Finance.prototype.IAR = function(investmentReturn, inflationRate){
  return 100 * (((1 + investmentReturn) / (1 + inflationRate)) - 1);
}

// XIRR - IRR for irregular intervals
Finance.prototype.XIRR = function(cfs, dts, guess) {
if (cfs.length != dts.length) throw new Error('Number of cash flows and dates should match');

  var positive, negative;
  Array.prototype.slice.call(cfs).forEach(function (value) {
    if (value > 0) positive = true;
    if (value < 0) negative = true;
  });

  if (!positive || !negative) throw new Error('XIRR requires at least one positive value and one negative value');

  guess = !!guess ? guess : 0;

  var limit = 100; //loop limit
  var guess_last;
  var durs = [];

  durs.push(0);

  //Create Array of durations from First date
  for(var i = 1; i < dts.length; i++) {
    durs.push(durYear(dts[0], dts[i]));
  }

  do {
    guess_last = guess;
    guess = guess_last - sumEq(cfs, durs, guess_last);
    limit--;

  }while(guess_last.toFixed(5) != guess.toFixed(5) && limit > 0);

  var xirr = guess_last.toFixed(5) != guess.toFixed(5) ? null : guess*100;

  return Math.round(xirr * 100) / 100;
}

//CAPM calculates expected return of an asset.
Finance.prototype.CAPM = function (rf, beta, emr, err) {
  var ans = rf/100 + beta * (emr/100 - rf/100);
  return ans;
}

//Returns the Value of stock with dividend growing at a
//constant growth rate to perpetuity.
Finance.prototype.stockPV = function (g, ke, D0) {
  var valueOfStock = (D0 * (1 + g/100))/((ke/100) - (g/100))
  return Math.round(valueOfStock)
}

//Returns Sum of f(x)/f'(x)
function sumEq(cfs, durs, guess) {
  var sum_fx = 0;
  var sum_fdx = 0;
  for (var i = 0; i < cfs.length; i++) {
    sum_fx = sum_fx + (cfs[i] / Math.pow(1 + guess, durs[i]));
  }
  for ( i = 0; i < cfs.length; i++) {
    sum_fdx = sum_fdx + (-cfs[i] * durs[i] * Math.pow(1 + guess, -1 - durs[i]));
  }
  return sum_fx / sum_fdx;
}

//Returns duration in years between two dates
function durYear(first, last) {
  return (Math.abs(last.getTime() - first.getTime()) / (1000 * 3600 * 24 * 365));
}

if (typeof exports !== 'undefined') {
  if (typeof module !== 'undefined' && module.exports) {
      module.exports = Finance;
      module.exports.Finance = Finance;
  }
}
<|endoftext|>


<commit_before>import { isFunction, isInteger, isArray } from '../util/is'
import createRequest from '../util/createRequest'
import localProcedureCall from '../util/localProcedureCall'
import { RPC_CREATOR } from '../const'

export default function createNodeFactory({ encode, decode }) {
    return function createNode({
        serialize = JSON.stringify,
        deserialize = JSON.parse,
        rpcFilter = (props) => props.rpc,
        errorInstances = [Error],
    } = {}) {
        const requests = {}
        let request_id_index = 0
        const local_rpcs_id = {}
        const local_rpcs = new Map()
        let local_function_index = 1 // 0 is reserved for the entry function used in open()

        const encode_params = {
            local_rpcs,
            registerLocalRpcFromEncode,
        }

        function registerRpc(function_id, fn) {
            local_rpcs_id[function_id] = fn
            local_rpcs.set(fn, function_id)
            return function_id
        }

        function createRpc(function_id) {
            const makeCall = (request_id, args) => {
                const data = [request_id, function_id]
                if (args.length > 0) data.push(args)
                node.send(encode(data, encode_params))
                return data
            }

            const rpc = (...args) => {
                const request_id = ++request_id_index
                const req = createRequest()
                const { resolve, reject } = req
                const resolveOrReject = (fn, value) => {
                    fn(value)
                    delete requests[request_id]
                    return req
                }
                req.data = makeCall(request_id, args)
                req.node = node
                req.createdAt = new Date().getTime()
                req.resolve = (value) => resolveOrReject(resolve, value)
                req.reject = (error) => resolveOrReject(reject, error)
                requests[request_id] = req
                return req
            }

            rpc.push = (...args) => {
                makeCall(0, args)
            }

            return rpc
        }

        function createRemoteFunction({
            function_id,
            function_creator,
            caller,
            path,
        }) {
            return rpcFilter({
                rpc: createRpc(function_id),
                node,
                function_id,
                function_creator,
                caller,
                path,
            })
        }

        function getNextLocalFunctionId() {
            while (local_rpcs_id.hasOwnProperty(local_function_index)) {
                local_function_index += 1
            }
            return local_function_index
        }

        function open(send, fn) {
            const function_id = 0
            if (isFunction(fn)) registerRpc(function_id, fn)
            node.send = (msg) => send(serialize(msg))
            return createRemoteFunction({
                function_id,
                function_creator: RPC_CREATOR.ENTRY,
            })
        }

        function message(msg) {
            msg = deserialize(msg)
            if (!isArray(msg) || !isInteger(msg[0])) {
                return false
            }

            const [id, function_id] = msg
            const is_request = id > -1
            const fn = is_request ? local_rpcs_id[function_id] : undefined

            msg = decode(msg, {
                createRemoteFunction,
                caller: fn,
                function_creator: is_request
                    ? RPC_CREATOR.REQUEST
                    : RPC_CREATOR.RESPONSE,
            })

            let args = msg[2]
            const response_id = -id

            if (is_request && isFunction(fn)) {
                args = isArray(msg[2]) ? msg[2] : []

                // Request without response
                if (id === 0) {
                    const req = { node }
                    args.push(req)
                    fn.apply(req, args)
                }

                // Request
                else {
                    const req = createRequest()
                    const response = [response_id]
                    req.node = node
                    req.then((value) => {
                        response.push(0) // no errors
                        if (value !== undefined) response.push(value)
                        node.send(encode(response, encode_params))
                    }).catch((error) => {
                        response.push(error === 0 ? null : error)
                        node.send(encode(response, encode_params))
                    })
                    args.push(req)
                    localProcedureCall(fn, req, args, errorInstances)
                }
                return true
            }

            // Response
            else if (id < 0 && requests.hasOwnProperty(response_id)) {
                const response_status = function_id
                const req = requests[response_id]
                response_status === 0
                    ? req.resolve(args)
                    : req.reject(response_status)
                return true
            }

            return false
        }

        function registerLocalRpcFromEncode(fn) {
            return registerRpc(getNextLocalFunctionId(), fn)
        }

        const node = {
            open,
            message,
            requests,
            registerRpc,
            createRpc,
        }

        return node
    }
}
<commit_msg>function named to rpc<commit_after>import { isFunction, isInteger, isArray } from '../util/is'
import createRequest from '../util/createRequest'
import localProcedureCall from '../util/localProcedureCall'
import { RPC_CREATOR } from '../const'

export default function createNodeFactory({ encode, decode }) {
    return function createNode({
        serialize = JSON.stringify,
        deserialize = JSON.parse,
        rpcFilter = (props) => props.rpc,
        errorInstances = [Error],
    } = {}) {
        const local_rpcs_id = {}
        const local_rpcs = new Map()
        let local_rpc_index = 1 // 0 is reserved for the entry function used in open()
        const requests = {}
        let request_id_index = 0

        const encode_params = {
            local_rpcs,
            registerLocalRpcFromEncode,
        }

        function registerRpc(function_id, fn) {
            local_rpcs_id[function_id] = fn
            local_rpcs.set(fn, function_id)
            return function_id
        }

        function createRpc(function_id) {
            const makeCall = (request_id, args) => {
                const data = [request_id, function_id]
                if (args.length > 0) data.push(args)
                node.send(encode(data, encode_params))
                return data
            }

            const rpc = (...args) => {
                const request_id = ++request_id_index
                const req = createRequest()
                const { resolve, reject } = req
                const resolveOrReject = (fn, value) => {
                    fn(value)
                    delete requests[request_id]
                    return req
                }
                req.data = makeCall(request_id, args)
                req.node = node
                req.createdAt = new Date().getTime()
                req.resolve = (value) => resolveOrReject(resolve, value)
                req.reject = (error) => resolveOrReject(reject, error)
                requests[request_id] = req
                return req
            }

            rpc.push = (...args) => {
                makeCall(0, args)
            }

            return rpc
        }

        function createRemoteFunction({
            function_id,
            function_creator,
            caller,
            path,
        }) {
            return rpcFilter({
                rpc: createRpc(function_id),
                node,
                function_id,
                function_creator,
                caller,
                path,
            })
        }

        function getNextLocalFunctionId() {
            while (local_rpcs_id.hasOwnProperty(local_rpc_index)) {
                local_rpc_index += 1
            }
            return local_rpc_index
        }

        function open(send, fn) {
            const function_id = 0
            if (isFunction(fn)) registerRpc(function_id, fn)
            node.send = (msg) => send(serialize(msg))
            return createRemoteFunction({
                function_id,
                function_creator: RPC_CREATOR.ENTRY,
            })
        }

        function message(msg) {
            msg = deserialize(msg)
            if (!isArray(msg) || !isInteger(msg[0])) {
                return false
            }

            const [id, function_id] = msg
            const is_request = id > -1
            const fn = is_request ? local_rpcs_id[function_id] : undefined

            msg = decode(msg, {
                createRemoteFunction,
                caller: fn,
                function_creator: is_request
                    ? RPC_CREATOR.REQUEST
                    : RPC_CREATOR.RESPONSE,
            })

            let args = msg[2]
            const response_id = -id

            if (is_request && isFunction(fn)) {
                args = isArray(msg[2]) ? msg[2] : []

                // Request without response
                if (id === 0) {
                    const req = { node }
                    args.push(req)
                    fn.apply(req, args)
                }

                // Request
                else {
                    const req = createRequest()
                    const response = [response_id]
                    req.node = node
                    req.then((value) => {
                        response.push(0) // no errors
                        if (value !== undefined) response.push(value)
                        node.send(encode(response, encode_params))
                    }).catch((error) => {
                        response.push(error === 0 ? null : error)
                        node.send(encode(response, encode_params))
                    })
                    args.push(req)
                    localProcedureCall(fn, req, args, errorInstances)
                }
                return true
            }

            // Response
            else if (id < 0 && requests.hasOwnProperty(response_id)) {
                const response_status = function_id
                const req = requests[response_id]
                response_status === 0
                    ? req.resolve(args)
                    : req.reject(response_status)
                return true
            }

            return false
        }

        function registerLocalRpcFromEncode(fn) {
            return registerRpc(getNextLocalFunctionId(), fn)
        }

        const node = {
            open,
            message,
            requests,
            registerRpc,
            createRpc,
        }

        return node
    }
}
<|endoftext|>


<commit_before>'use strict'

var Joi = require('joi')
var Boom = require('boom')

module.exports = {
  auth: false,
  payload: {
    allow: 'application/x-www-form-urlencoded'
  },
  validate: {
    params: {
      fingerprint: Joi.string().required()
    }
  },
  jsonp: 'callback',
  handler(request, reply) {
    global.ABIBAO.services.domain.execute('command', 'individualAutoLoginWithCredentialsCommand', request.payload)
      .then(function (credentials) {
        reply(credentials)
      })
      .catch(function (error) {
        if (error === 'ERROR_BAD_AUTHENTIFICATION') { return reply(Boom.unauthorized('Email address and/or password invalid')) }
        reply(Boom.badRequest(error))
      })
  }
}
<commit_msg>[2.6.8] hotfix handler of endpoint autologin<commit_after>'use strict'

var Joi = require('joi')
var Boom = require('boom')

module.exports = {
  auth: false,
  payload: {
    allow: 'application/x-www-form-urlencoded'
  },
  validate: {
    params: {
      fingerprint: Joi.string().required()
    }
  },
  jsonp: 'callback',
  handler(request, reply) {
    global.ABIBAO.services.domain.execute('command', 'individualLoginWithFingerprintCommand', request.params.fingerprint)
      .then(function (credentials) {
        reply(credentials)
      })
      .catch(function (error) {
        reply(Boom.badRequest(error))
      })
  }
}
<|endoftext|>


<commit_before>// jscs:disable disallowMultipleLineStrings
// jshint multistr: true

(function poll() {
  if (typeof ynabToolKit !== 'undefined'  && ynabToolKit.pageReady === true) {

    ynabToolKit.collapseSideMenu = new function() { // jshint ignore:line

      var buttonText = (ynabToolKit.l10nData && ynabToolKit.l10nData["toolkit.collapse"]) || 'Collapse';
      this.collapseBtn = '
 \

 \
           \
             \
            ' + buttonText + ' \
           \

 \

';

      this.originalButtons = {};

      this.originalSizes = {
        sidebarWidth:   $('.sidebar').width(),
        contentLeft:    $('.content').css('left'),
        headerLeft:     $('.budget-header, .accounts-header').css('left'),
        contentWidth:   $('.budget-content').css('width'),
        inspectorWidth: $('.budget-inspector').css('width'),
      };

      this.invoke = function() {
        ynabToolKit.collapseSideMenu.setupBtns();
      };

      this.observe = function(changedNodes) {
        if (changedNodes.has('pure-g layout user-logged-in')) {
          if ($('.nav-main').length) {
            ynabToolKit.collapseSideMenu.setupBtns();
          }
        }

        changedNodes.forEach(function (changedNode) {
          if ($('.collapsed-buttons').is(':visible') &&
              changedNode.startsWith('navlink-') && changedNode.endsWith(' active')) {

            ynabToolKit.collapseSideMenu.setCollapsedSizes();
            ynabToolKit.collapseSideMenu.setActiveButton();
          }
        });

        if (changedNodes.has('nav-main')) {
          var numNavLinks = $('.nav-main').children().length;
          var collapseIndex = $('.nav-main').children()
            .index($('.navlink-collapse'));

          if (numNavLinks > (collapseIndex + 1)) {
            $('.navlink-collapse').remove();

            ynabToolKit.collapseSideMenu.setUpCollapseBtn();
            ynabToolKit.collapseSideMenu.setUpCollapsedButtons();
          }

        }
      };

      // Add buttons and handlers to screen
      this.setupBtns = function() {

        // Don't proceed if buttons already exist
        if ($('.navlink-collapse').is(':visible') ||
            $('.navbar-expand').is(':visible')) {
          return;
        }

        ynabToolKit.collapseSideMenu.setUpCollapseBtn();
        ynabToolKit.collapseSideMenu.setUpCollapsedButtons();
      };

      this.setUpCollapseBtn = function() {
        $('.nav-main').append(ynabToolKit.collapseSideMenu.collapseBtn);
        $('.navlink-collapse').on('click',
          ynabToolKit.collapseSideMenu.collapseMenu);
      };

      this.setUpCollapsedButtons = function() {
        var expandBtns = ynabToolKit.collapseSideMenu.getUnCollapseBtnGroup();

        if (!$('.collapsed-buttons').length) {
          $('.sidebar').prepend(expandBtns);
        } else {
          $('.collapsed-buttons').remove();
          $('.sidebar').prepend(expandBtns);
        }

        $('.collapsed-buttons').hide();
      };

      this.getUnCollapseBtnGroup = function() {
        var navChildren = $('.nav-main').children();
        var navChildrenLength = navChildren.length;

        var collapsedBtnContainer =
          $('
', {
            'class': 'collapsed-buttons'
          });

        for (var i = 0; i < navChildrenLength; i++) {
          var child = navChildren[i];

          // If this is the collapse button, skip
          if (child.className.indexOf('navlink-collapse') > -1) {
            continue;
          }

          var span = $(child).find('span')[0];

          // Don't process if not actually a button
          if (!span) {
            continue;
          }

          var btnClasses = span.className;
          var button = $('<button>');
          button.addClass(btnClasses);
          button.addClass('button button-prefs');

          var listItem = $(child).find('li')[0] || child;
          var linkClasses = listItem.className.replace(' active', '');

          var link = $('');
          link.attr('href','#');
          link.addClass(linkClasses);
          link.html(button);
          link.click(function() {
            ynabToolKit.collapseSideMenu.originalButtons[this.className.replace(' active', '')].click();
          });

          ynabToolKit.collapseSideMenu.originalButtons[linkClasses.replace(' active', '')] = $(child).find('a');

          // Set proper class so the active styling can be applied
          if (btnClasses.indexOf('mail-1') > -1) {
            button.addClass('collapsed-budget');
          } else if (btnClasses.indexOf('government-1') > -1) {
            button.addClass('collapsed-account');
          } else {
            // Fallback if we don't know what the button is.
            button.addClass('collapsed');
          }

          collapsedBtnContainer.append(link);
        }

        // Add uncollapse button
        var collapseBtn = $('<button>');
        collapseBtn.addClass('button button-prefs flaticon stroke \
          right-circle-4 navbar-expand');

        collapsedBtnContainer.append(collapseBtn);

        $('body').on('click', '.navbar-expand', function() {
          ynabToolKit.collapseSideMenu.expandMenu(ynabToolKit.collapseSideMenu.originalSizes);
        });

        return collapsedBtnContainer;
      };

      // Handle clicking expand button. Puts things back to original sizes
      this.expandMenu = function(originalSizes) {
        $('.collapsed-buttons').hide();
        $('.sidebar > .ember-view').fadeIn();
        $('.sidebar').animate({width: originalSizes.sidebarWidth});
        $('.content').animate({left: originalSizes.contentLeft});
        $('.budget-header').animate({left: originalSizes.headerLeft});
        $('.budget-content').animate({width: originalSizes.contentWidth}, 400, 'swing', function() {
          // Need to remove width after animation completion
          $('.budget-content').removeAttr('style');
        });
        $('.budget-inspector').animate({width: originalSizes.inspectorWidth});
      };

      // Handle clicking the collapse button
      this.collapseMenu = function() {
        ynabToolKit.collapseSideMenu.setActiveButton();
        $('.sidebar > .ember-view').hide();
        $('.collapsed-buttons').fadeIn();
        ynabToolKit.collapseSideMenu.setCollapsedSizes();
      };

      // Set collapsed sizes
      this.setCollapsedSizes = function() {
        $('.sidebar').animate({width: '40px'});
        $('.content').animate({left: '40px'}, 400, 'swing', function() {
          // Need to remove width after animation completion
          $('.ynab-grid-header').removeAttr('style');
        });

        $('.budget-header').animate({left: '40px'});
        $('.budget-content').animate({width: '73%'});
        $('.budget-inspector').animate({width: '27%'});
      };

      // Add the active style to correct button
      this.setActiveButton = function() {
        ynabToolKit.collapseSideMenu.deactivateCollapsedActive();

        var originalButtons = ynabToolKit.collapseSideMenu.originalButtons;

        for (var classList in originalButtons) {
          if (originalButtons.hasOwnProperty(classList)) {
            var originalButton = $(originalButtons[classList]).closest('.ember-view');

            if (originalButton.hasClass('active')) {

              // Set the active button in the collapsed panel.
              var collapsedSelector = '.collapsed-buttons .' +
                      $(originalButton).attr('class')
                        .replace(' active', '')
                        .replace(' ', '.') +
                          ' button';

              $(collapsedSelector).addClass('collapsed-active');
            }
          }
        }
      };

      // Deactivate collapsed buttons
      this.deactivateCollapsedActive = function() {
        $('.collapsed-buttons a button').removeClass('collapsed-active');
        $('.collapsed-buttons a').removeClass('active');
      };
    }();

    ynabToolKit.collapseSideMenu.invoke();

  } else {
    setTimeout(poll, 250);
  }
})();
<commit_msg>Updating DOM building style to make build with JQuery and avoid possible DOM or script injection. Also now hiding the collapse button when the side bar is already collapsed.<commit_after>// jscs:disable disallowMultipleLineStrings
// jshint multistr: true

(function poll() {
  if (typeof ynabToolKit !== 'undefined'  && ynabToolKit.pageReady === true) {

    ynabToolKit.collapseSideMenu = new function() { // jshint ignore:line

      this.collapseBtn =
      $('
').append(
        $('
', { class: 'ember-view navlink-collapse' }).append(
          $('', { href: '#' }).append(
            $('', { class: 'ember-view flaticon stroke left-circle-4' })
          ).append(
            (ynabToolKit.l10nData && ynabToolKit.l10nData["toolkit.collapse"]) || 'Collapse'
          )
        )
      );

      this.originalButtons = {};

      this.originalSizes = {
        sidebarWidth:   $('.sidebar').width(),
        contentLeft:    $('.content').css('left'),
        headerLeft:     $('.budget-header, .accounts-header').css('left'),
        contentWidth:   $('.budget-content').css('width'),
        inspectorWidth: $('.budget-inspector').css('width'),
      };

      this.invoke = function() {
        ynabToolKit.collapseSideMenu.setupBtns();
      };

      this.observe = function(changedNodes) {
        if (changedNodes.has('pure-g layout user-logged-in')) {
          if ($('.nav-main').length) {
            ynabToolKit.collapseSideMenu.setupBtns();
          }
        }

        changedNodes.forEach(function (changedNode) {
          if ($('.collapsed-buttons').is(':visible') &&
              changedNode.startsWith('navlink-') && changedNode.endsWith(' active')) {

            ynabToolKit.collapseSideMenu.setCollapsedSizes();
            ynabToolKit.collapseSideMenu.setActiveButton();
          }
        });

        if (changedNodes.has('nav-main')) {
          var numNavLinks = $('.nav-main').children().length;
          var collapseIndex = $('.nav-main').children()
            .index($('.navlink-collapse'));

          if (numNavLinks > (collapseIndex + 1)) {
            $('.navlink-collapse').remove();

            ynabToolKit.collapseSideMenu.setUpCollapseBtn();
            ynabToolKit.collapseSideMenu.setUpCollapsedButtons();
          }

        }
      };

      // Add buttons and handlers to screen
      this.setupBtns = function() {

        // Don't proceed if buttons already exist
        if ($('.navlink-collapse').is(':visible') ||
            $('.navbar-expand').is(':visible')) {
          return;
        }

        ynabToolKit.collapseSideMenu.setUpCollapseBtn();
        ynabToolKit.collapseSideMenu.setUpCollapsedButtons();
      };

      this.setUpCollapseBtn = function() {
        $('.nav-main').append(ynabToolKit.collapseSideMenu.collapseBtn);
        $('.navlink-collapse').on('click',
          ynabToolKit.collapseSideMenu.collapseMenu);
      };

      this.setUpCollapsedButtons = function() {
        var expandBtns = ynabToolKit.collapseSideMenu.getUnCollapseBtnGroup();

        if (!$('.collapsed-buttons').length) {
          $('.sidebar').prepend(expandBtns);
        } else {
          $('.collapsed-buttons').remove();
          $('.sidebar').prepend(expandBtns);
        }

        $('.collapsed-buttons').hide();
      };

      this.getUnCollapseBtnGroup = function() {
        var navChildren = $('.nav-main').children();
        var navChildrenLength = navChildren.length;

        var collapsedBtnContainer =
          $('
', {
            'class': 'collapsed-buttons'
          });

        for (var i = 0; i < navChildrenLength; i++) {
          var child = navChildren[i];

          // If this is the collapse button, skip
          if (child.className.indexOf('navlink-collapse') > -1) {
            continue;
          }

          var span = $(child).find('span')[0];

          // Don't process if not actually a button
          if (!span) {
            continue;
          }

          var btnClasses = span.className;
          var button = $('<button>');
          button.addClass(btnClasses);
          button.addClass('button button-prefs');

          var listItem = $(child).find('li')[0] || child;
          var linkClasses = listItem.className.replace(' active', '');

          var link = $('');
          link.attr('href','#');
          link.addClass(linkClasses);
          link.html(button);
          link.click(function() {
            ynabToolKit.collapseSideMenu.originalButtons[this.className.replace(' active', '')].click();
          });

          ynabToolKit.collapseSideMenu.originalButtons[linkClasses.replace(' active', '')] = $(child).find('a');

          // Set proper class so the active styling can be applied
          if (btnClasses.indexOf('mail-1') > -1) {
            button.addClass('collapsed-budget');
          } else if (btnClasses.indexOf('government-1') > -1) {
            button.addClass('collapsed-account');
          } else {
            // Fallback if we don't know what the button is.
            button.addClass('collapsed');
          }

          collapsedBtnContainer.append(link);
        }

        // Add uncollapse button
        var collapseBtn = $('<button>');
        collapseBtn.addClass('button button-prefs flaticon stroke \
          right-circle-4 navbar-expand');

        collapsedBtnContainer.append(collapseBtn);

        $('body').on('click', '.navbar-expand', function() {
          ynabToolKit.collapseSideMenu.expandMenu(ynabToolKit.collapseSideMenu.originalSizes);
        });

        return collapsedBtnContainer;
      };

      // Handle clicking expand button. Puts things back to original sizes
      this.expandMenu = function(originalSizes) {
        $('.collapsed-buttons').hide();
        $('.sidebar > .ember-view').fadeIn();
        $('.navlink-collapse').show();
        $('.sidebar').animate({width: originalSizes.sidebarWidth});
        $('.content').animate({left: originalSizes.contentLeft});
        $('.budget-header').animate({left: originalSizes.headerLeft});
        $('.budget-content').animate({width: originalSizes.contentWidth}, 400, 'swing', function() {
          // Need to remove width after animation completion
          $('.budget-content').removeAttr('style');
        });
        $('.budget-inspector').animate({width: originalSizes.inspectorWidth});
      };

      // Handle clicking the collapse button
      this.collapseMenu = function() {
        ynabToolKit.collapseSideMenu.setActiveButton();
        $('.navlink-collapse').hide();
        $('.sidebar > .ember-view').hide();
        $('.collapsed-buttons').fadeIn();
        ynabToolKit.collapseSideMenu.setCollapsedSizes();
      };

      // Set collapsed sizes
      this.setCollapsedSizes = function() {
        $('.sidebar').animate({width: '40px'});
        $('.content').animate({left: '40px'}, 400, 'swing', function() {
          // Need to remove width after animation completion
          $('.ynab-grid-header').removeAttr('style');
        });

        $('.budget-header').animate({left: '40px'});
        $('.budget-content').animate({width: '73%'});
        $('.budget-inspector').animate({width: '27%'});
      };

      // Add the active style to correct button
      this.setActiveButton = function() {
        ynabToolKit.collapseSideMenu.deactivateCollapsedActive();

        var originalButtons = ynabToolKit.collapseSideMenu.originalButtons;

        for (var classList in originalButtons) {
          if (originalButtons.hasOwnProperty(classList)) {
            var originalButton = $(originalButtons[classList]).closest('.ember-view');

            if (originalButton.hasClass('active')) {

              // Set the active button in the collapsed panel.
              var collapsedSelector = '.collapsed-buttons .' +
                      $(originalButton).attr('class')
                        .replace(' active', '')
                        .replace(' ', '.') +
                          ' button';

              $(collapsedSelector).addClass('collapsed-active');
            }
          }
        }
      };

      // Deactivate collapsed buttons
      this.deactivateCollapsedActive = function() {
        $('.collapsed-buttons a button').removeClass('collapsed-active');
        $('.collapsed-buttons a').removeClass('active');
      };
    }();

    ynabToolKit.collapseSideMenu.invoke();

  } else {
    setTimeout(poll, 250);
  }
})();
<|endoftext|>