<commit_before>/* jshint
 funcscope: true,
 newcap: true,
 nonew: true,
 shadow: false,
 unused: true,

 maxlen: 90,
 maxstatements: 200
 */
/* global $ */
'use strict';

var buildMarkup = require('./NetSimTabsComponent.html.ejs');
var NetSimRouterTab = require('./NetSimRouterTab');
var NetSimMyDeviceTab = require('./NetSimMyDeviceTab');
var NetSimDnsTab = require('./NetSimDnsTab');
var NetSimTabType = require('./netsimConstants').NetSimTabType;
var shouldShowTab = require('./netsimUtils').shouldShowTab;
var netsimGlobals = require('./netsimGlobals');

/**
 * Wrapper component for tabs panel on the right side of the page.
 * @param {jQuery} rootDiv
 * @param {RunLoop} runLoop
 * @param {Object} callbacks
 * @param {function} callbacks.chunkSizeSliderChangeCallback
 * @param {function} callbacks.myDeviceBitRateChangeCallback
 * @param {function} callbacks.encodingChangeCallback
 * @param {function} callbacks.routerBandwidthSliderChangeCallback
 * @param {function} callbacks.routerBandwidthSliderStopCallback
 * @param {function} callbacks.routerMemorySliderChangeCallback
 * @param {function} callbacks.routerMemorySliderStopCallback
 * @param {function} callbacks.dnsModeChangeCallback
 * @param {function} callbacks.becomeDnsCallback
 * @constructor
 */
var NetSimTabsComponent = module.exports = function (rootDiv, runLoop, callbacks) {
  /**
   * Component root, which we fill whenever we call render()
   * @type {jQuery}
   * @private
   */
  this.rootDiv_ = rootDiv;

  /**
   * @type {RunLoop}
   * @private
   */
  this.runLoop_ = runLoop;

  /**
   * @type {function}
   * @private
   */
  this.chunkSizeSliderChangeCallback_ = callbacks.chunkSizeSliderChangeCallback;

  /**
   * @type {function}
   * @private
   */
  this.myDeviceBitRateChangeCallback_ = callbacks.myDeviceBitRateChangeCallback;

  /**
   * @type {function}
   * @private
   */
  this.encodingChangeCallback_ = callbacks.encodingChangeCallback;

  /**
   * @type {function}
   * @private
   */
  this.routerBandwidthSliderChangeCallback_ =
      callbacks.routerBandwidthSliderChangeCallback;

  /**
   * @type {function}
   * @private
   */
  this.routerBandwidthSliderStopCallback_ =
      callbacks.routerBandwidthSliderStopCallback;

  /**
   * @type {function}
   * @private
   */
  this.routerMemorySliderChangeCallback_ =
      callbacks.routerMemorySliderChangeCallback;

  /**
   * @type {function}
   * @private
   */
  this.routerMemorySliderStopCallback_ =
      callbacks.routerMemorySliderStopCallback;

  /**
   * @type {function}
   * @private
   */
  this.dnsModeChangeCallback_ = callbacks.dnsModeChangeCallback;

  /**
   * @type {function}
   * @private
   */
  this.becomeDnsCallback_ = callbacks.becomeDnsCallback;

  /**
   * @type {NetSimRouterTab}
   * @private
   */
  this.routerTab_ = null;

  /**
   * @type {NetSimMyDeviceTab}
   * @private
   */
  this.myDeviceTab_ = null;

  /**
   * @type {NetSimDnsTab}
   * @private
   */
  this.dnsTab_ = null;

  // Initial render
  this.render();
};

/**
 * @param {RunLoop} runLoop
 */
NetSimTabsComponent.prototype.attachToRunLoop = function (runLoop) {
  if (this.routerTab_) {
    this.routerTab_.attachToRunLoop(runLoop);
  }
};

/**
 * Fill the root div with new elements reflecting the current state
 */
NetSimTabsComponent.prototype.render = function () {
  var levelConfig = netsimGlobals.getLevelConfig();

  var rawMarkup = buildMarkup({
    level: levelConfig
  });
  var jQueryWrap = $(rawMarkup);
  this.rootDiv_.html(jQueryWrap);

  this.rootDiv_.find('.netsim-tabs').tabs({
    active: levelConfig.defaultTabIndex
  });

  if (shouldShowTab(levelConfig, NetSimTabType.MY_DEVICE)) {
    this.myDeviceTab_ = new NetSimMyDeviceTab(
        this.rootDiv_.find('#tab_my_device'),
        this.runLoop_,
        {
          chunkSizeChangeCallback: this.chunkSizeSliderChangeCallback_,
          bitRateChangeCallback: this.myDeviceBitRateChangeCallback_,
          encodingChangeCallback: this.encodingChangeCallback_
        });
  }

  if (shouldShowTab(levelConfig, NetSimTabType.ROUTER)) {
    this.routerTab_ = new NetSimRouterTab(
        this.rootDiv_.find('#tab_router'),
        {
          bandwidthSliderChangeCallback: this.routerBandwidthSliderChangeCallback_,
          bandwidthSliderStopCallback: this.routerBandwidthSliderStopCallback_,
          memorySliderChangeCallback: this.routerMemorySliderChangeCallback_,
          memorySliderStopCallback: this.routerMemorySliderStopCallback_
        });
  }

  if (shouldShowTab(levelConfig, NetSimTabType.DNS)) {
    this.dnsTab_ = new NetSimDnsTab(
        this.rootDiv_.find('#tab_dns'),
        this.dnsModeChangeCallback_,
        this.becomeDnsCallback_);
  }
};

/** @param {number} newChunkSize */
NetSimTabsComponent.prototype.setChunkSize = function (newChunkSize) {
  if (this.myDeviceTab_) {
    this.myDeviceTab_.setChunkSize(newChunkSize);
  }
};

/** @param {number} newBitRate in bits per second */
NetSimTabsComponent.prototype.setMyDeviceBitRate = function (newBitRate) {
  if (this.myDeviceTab_) {
    this.myDeviceTab_.setBitRate(newBitRate);
  }
};

/** @param {EncodingType[]} newEncodings */
NetSimTabsComponent.prototype.setEncodings = function (newEncodings) {
  if (this.myDeviceTab_) {
    this.myDeviceTab_.setEncodings(newEncodings);
  }
};

/** @param {number} creationTimestampMs */
NetSimTabsComponent.prototype.setRouterCreationTime = function (creationTimestampMs) {
  if (this.routerTab_) {
    this.routerTab_.setRouterCreationTime(creationTimestampMs);
  }
};

/** @param {number} newBandwidth in bits/second */
NetSimTabsComponent.prototype.setRouterBandwidth = function (newBandwidth) {
  if (this.routerTab_) {
    this.routerTab_.setBandwidth(newBandwidth);
  }
};

/** @param {number} newMemory in bits */
NetSimTabsComponent.prototype.setRouterMemory = function (newMemory) {
  if (this.routerTab_) {
    this.routerTab_.setMemory(newMemory);
  }
};

/**
 * @param {number} queuedPacketCount
 */
NetSimTabsComponent.prototype.setRouterQueuedPacketCount = function (queuedPacketCount) {
  if (this.routerTab_) {
    this.routerTab_.setRouterQueuedPacketCount(queuedPacketCount);
  }
};

/** @param {number} usedMemoryInBits */
NetSimTabsComponent.prototype.setRouterMemoryInUse = function (usedMemoryInBits) {
  if (this.routerTab_) {
    this.routerTab_.setMemoryInUse(usedMemoryInBits);
  }
};

NetSimTabsComponent.prototype.setRouterDataRate = function (dataRateBitsPerSecond) {
  if (this.routerTab_) {
    this.routerTab_.setDataRate(dataRateBitsPerSecond);
  }
};

/** @param {string} newDnsMode */
NetSimTabsComponent.prototype.setDnsMode = function (newDnsMode) {
  if (this.dnsTab_) {
    this.dnsTab_.setDnsMode(newDnsMode);
  }
};

/** @param {boolean} isDnsNode */
NetSimTabsComponent.prototype.setIsDnsNode = function (isDnsNode) {
  if (this.dnsTab_) {
    this.dnsTab_.setIsDnsNode(isDnsNode);
  }
};

/** @param {Array} tableContents */
NetSimTabsComponent.prototype.setDnsTableContents = function (tableContents) {
  if (this.dnsTab_) {
    this.dnsTab_.setDnsTableContents(tableContents);
  }
};

/** @param {Array} logData */
NetSimTabsComponent.prototype.setRouterLogData = function (logData) {
  if (this.routerTab_) {
    this.routerTab_.setRouterLogData(logData);
  }
};
<commit_msg>Copy reference area (including video thumbnail) into NetSim Instructions Tab<commit_after>/* jshint
 funcscope: true,
 newcap: true,
 nonew: true,
 shadow: false,
 unused: true,

 maxlen: 90,
 maxstatements: 200
 */
/* global $ */
'use strict';

var buildMarkup = require('./NetSimTabsComponent.html.ejs');
var NetSimRouterTab = require('./NetSimRouterTab');
var NetSimMyDeviceTab = require('./NetSimMyDeviceTab');
var NetSimDnsTab = require('./NetSimDnsTab');
var NetSimTabType = require('./netsimConstants').NetSimTabType;
var shouldShowTab = require('./netsimUtils').shouldShowTab;
var netsimGlobals = require('./netsimGlobals');

/**
 * Wrapper component for tabs panel on the right side of the page.
 * @param {jQuery} rootDiv
 * @param {RunLoop} runLoop
 * @param {Object} callbacks
 * @param {function} callbacks.chunkSizeSliderChangeCallback
 * @param {function} callbacks.myDeviceBitRateChangeCallback
 * @param {function} callbacks.encodingChangeCallback
 * @param {function} callbacks.routerBandwidthSliderChangeCallback
 * @param {function} callbacks.routerBandwidthSliderStopCallback
 * @param {function} callbacks.routerMemorySliderChangeCallback
 * @param {function} callbacks.routerMemorySliderStopCallback
 * @param {function} callbacks.dnsModeChangeCallback
 * @param {function} callbacks.becomeDnsCallback
 * @constructor
 */
var NetSimTabsComponent = module.exports = function (rootDiv, runLoop, callbacks) {
  /**
   * Component root, which we fill whenever we call render()
   * @type {jQuery}
   * @private
   */
  this.rootDiv_ = rootDiv;

  /**
   * @type {RunLoop}
   * @private
   */
  this.runLoop_ = runLoop;

  /**
   * @type {function}
   * @private
   */
  this.chunkSizeSliderChangeCallback_ = callbacks.chunkSizeSliderChangeCallback;

  /**
   * @type {function}
   * @private
   */
  this.myDeviceBitRateChangeCallback_ = callbacks.myDeviceBitRateChangeCallback;

  /**
   * @type {function}
   * @private
   */
  this.encodingChangeCallback_ = callbacks.encodingChangeCallback;

  /**
   * @type {function}
   * @private
   */
  this.routerBandwidthSliderChangeCallback_ =
      callbacks.routerBandwidthSliderChangeCallback;

  /**
   * @type {function}
   * @private
   */
  this.routerBandwidthSliderStopCallback_ =
      callbacks.routerBandwidthSliderStopCallback;

  /**
   * @type {function}
   * @private
   */
  this.routerMemorySliderChangeCallback_ =
      callbacks.routerMemorySliderChangeCallback;

  /**
   * @type {function}
   * @private
   */
  this.routerMemorySliderStopCallback_ =
      callbacks.routerMemorySliderStopCallback;

  /**
   * @type {function}
   * @private
   */
  this.dnsModeChangeCallback_ = callbacks.dnsModeChangeCallback;

  /**
   * @type {function}
   * @private
   */
  this.becomeDnsCallback_ = callbacks.becomeDnsCallback;

  /**
   * @type {NetSimRouterTab}
   * @private
   */
  this.routerTab_ = null;

  /**
   * @type {NetSimMyDeviceTab}
   * @private
   */
  this.myDeviceTab_ = null;

  /**
   * @type {NetSimDnsTab}
   * @private
   */
  this.dnsTab_ = null;

  // Initial render
  this.render();
};

/**
 * @param {RunLoop} runLoop
 */
NetSimTabsComponent.prototype.attachToRunLoop = function (runLoop) {
  if (this.routerTab_) {
    this.routerTab_.attachToRunLoop(runLoop);
  }
};

/**
 * Fill the root div with new elements reflecting the current state
 */
NetSimTabsComponent.prototype.render = function () {
  var levelConfig = netsimGlobals.getLevelConfig();
  // Grab the reference area before we re-render in case we are wiping
  // out the only copy.
  var referenceArea = $('#reference_area');

  var rawMarkup = buildMarkup({
    level: levelConfig
  });
  var jQueryWrap = $(rawMarkup);
  this.rootDiv_.html(jQueryWrap);

  this.rootDiv_.find('.netsim-tabs').tabs({
    active: levelConfig.defaultTabIndex
  });

  if (shouldShowTab(levelConfig, NetSimTabType.INSTRUCTIONS) && referenceArea) {
    // Move the reference area into the instructions tab
    this.rootDiv_.find('#tab_instructions').append(referenceArea);
  }

  if (shouldShowTab(levelConfig, NetSimTabType.MY_DEVICE)) {
    this.myDeviceTab_ = new NetSimMyDeviceTab(
        this.rootDiv_.find('#tab_my_device'),
        this.runLoop_,
        {
          chunkSizeChangeCallback: this.chunkSizeSliderChangeCallback_,
          bitRateChangeCallback: this.myDeviceBitRateChangeCallback_,
          encodingChangeCallback: this.encodingChangeCallback_
        });
  }

  if (shouldShowTab(levelConfig, NetSimTabType.ROUTER)) {
    this.routerTab_ = new NetSimRouterTab(
        this.rootDiv_.find('#tab_router'),
        {
          bandwidthSliderChangeCallback: this.routerBandwidthSliderChangeCallback_,
          bandwidthSliderStopCallback: this.routerBandwidthSliderStopCallback_,
          memorySliderChangeCallback: this.routerMemorySliderChangeCallback_,
          memorySliderStopCallback: this.routerMemorySliderStopCallback_
        });
  }

  if (shouldShowTab(levelConfig, NetSimTabType.DNS)) {
    this.dnsTab_ = new NetSimDnsTab(
        this.rootDiv_.find('#tab_dns'),
        this.dnsModeChangeCallback_,
        this.becomeDnsCallback_);
  }
};

/** @param {number} newChunkSize */
NetSimTabsComponent.prototype.setChunkSize = function (newChunkSize) {
  if (this.myDeviceTab_) {
    this.myDeviceTab_.setChunkSize(newChunkSize);
  }
};

/** @param {number} newBitRate in bits per second */
NetSimTabsComponent.prototype.setMyDeviceBitRate = function (newBitRate) {
  if (this.myDeviceTab_) {
    this.myDeviceTab_.setBitRate(newBitRate);
  }
};

/** @param {EncodingType[]} newEncodings */
NetSimTabsComponent.prototype.setEncodings = function (newEncodings) {
  if (this.myDeviceTab_) {
    this.myDeviceTab_.setEncodings(newEncodings);
  }
};

/** @param {number} creationTimestampMs */
NetSimTabsComponent.prototype.setRouterCreationTime = function (creationTimestampMs) {
  if (this.routerTab_) {
    this.routerTab_.setRouterCreationTime(creationTimestampMs);
  }
};

/** @param {number} newBandwidth in bits/second */
NetSimTabsComponent.prototype.setRouterBandwidth = function (newBandwidth) {
  if (this.routerTab_) {
    this.routerTab_.setBandwidth(newBandwidth);
  }
};

/** @param {number} newMemory in bits */
NetSimTabsComponent.prototype.setRouterMemory = function (newMemory) {
  if (this.routerTab_) {
    this.routerTab_.setMemory(newMemory);
  }
};

/**
 * @param {number} queuedPacketCount
 */
NetSimTabsComponent.prototype.setRouterQueuedPacketCount = function (queuedPacketCount) {
  if (this.routerTab_) {
    this.routerTab_.setRouterQueuedPacketCount(queuedPacketCount);
  }
};

/** @param {number} usedMemoryInBits */
NetSimTabsComponent.prototype.setRouterMemoryInUse = function (usedMemoryInBits) {
  if (this.routerTab_) {
    this.routerTab_.setMemoryInUse(usedMemoryInBits);
  }
};

NetSimTabsComponent.prototype.setRouterDataRate = function (dataRateBitsPerSecond) {
  if (this.routerTab_) {
    this.routerTab_.setDataRate(dataRateBitsPerSecond);
  }
};

/** @param {string} newDnsMode */
NetSimTabsComponent.prototype.setDnsMode = function (newDnsMode) {
  if (this.dnsTab_) {
    this.dnsTab_.setDnsMode(newDnsMode);
  }
};

/** @param {boolean} isDnsNode */
NetSimTabsComponent.prototype.setIsDnsNode = function (isDnsNode) {
  if (this.dnsTab_) {
    this.dnsTab_.setIsDnsNode(isDnsNode);
  }
};

/** @param {Array} tableContents */
NetSimTabsComponent.prototype.setDnsTableContents = function (tableContents) {
  if (this.dnsTab_) {
    this.dnsTab_.setDnsTableContents(tableContents);
  }
};

/** @param {Array} logData */
NetSimTabsComponent.prototype.setRouterLogData = function (logData) {
  if (this.routerTab_) {
    this.routerTab_.setRouterLogData(logData);
  }
};
<|endoftext|>


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

  window.GOVUK           = window.GOVUK || {};
  window.GOVUK.Analytics = window.GOVUK.Analytics || {};

  var CONFIG = {
  }

  function ScrollTracker(sitewideConfig) {
    this.config = this.getConfigForCurrentPath(sitewideConfig);
    this.SCROLL_TIMEOUT_DELAY = 500;

    if ( !this.config ) {
      this.enabled = false;
      return;
    }
    this.enabled = true;

    this.trackedNodes = this.buildNodes(this.config);

    $(window).scroll($.proxy(this.onScroll, this));
    this.trackVisibleNodes();
  };

  ScrollTracker.prototype.getConfigForCurrentPath = function getConfigForCurrentPath(sitewideConfig) {
    for ( var path in sitewideConfig ) {
      if ( window.location.pathname == path ) return sitewideConfig[path];
    }
  };

  ScrollTracker.prototype.buildNodes = function buildNodes(config) {
    var nodes = [];
    var nodeConstructor, nodeData;

    for (var i=0; i<config.length; i++) {
      nodeConstructor = ScrollTracker[config[i][0] + "Node"];
      nodeData = config[i][1];
      nodes.push(new nodeConstructor(nodeData));
    }

    return nodes;
  };

  ScrollTracker.prototype.onScroll = function onScroll() {
    clearTimeout(this.scrollTimeout);
    this.scrollTimeout = setTimeout($.proxy(this.trackVisibleNodes, this), this.SCROLL_TIMEOUT_DELAY);
  };

  ScrollTracker.prototype.trackVisibleNodes = function trackVisibleNodes() {
    for ( var i=0; i<this.trackedNodes.length; i++ ) {
      if ( this.trackedNodes[i].isVisible() && !this.trackedNodes[i].alreadySeen ) {
        this.trackedNodes[i].alreadySeen = true;
        GOVUK.sendToAnalytics(["_trackEvent"].concat(this.trackedNodes[i].eventData));
      }
    }
  };

  ScrollTracker.PercentNode = function PercentNode(percentage) {
    this.percentage = percentage;
    this.eventData = ["ScrollTo", "Percent", String(percentage)];
  };

  ScrollTracker.PercentNode.prototype.isVisible = function isVisible() {
    return this.currentScrollPercent() >= this.percentage;
  };

  ScrollTracker.PercentNode.prototype.currentScrollPercent = function currentScrollPercent() {
    var $document = $(document);
    var $window = $(window);
    return( ($window.scrollTop() / ($document.height() - $window.height())) * 100.0 );
  };

  ScrollTracker.HeadingNode = function HeadingNode(headingText) {
    this.$element = getHeadingElement(headingText);
    this.eventData = ["ScrollTo", "Heading", headingText];

    function getHeadingElement(headingText) {
      var $headings = $('h1, h2, h3, h4, h5, h6');
      for ( var i=0; i<$headings.length; i++ ) {
        if ( $.trim($headings.eq(i).text()).replace(/\s/g, ' ') == headingText ) return $headings.eq(i);
      }
    }
  };

  ScrollTracker.HeadingNode.prototype.isVisible = function isVisible() {
    if ( !this.$element ) return false;
    return this.elementIsVisible(this.$element);
  }

  ScrollTracker.HeadingNode.prototype.elementIsVisible = function elementIsVisible($element) {
    var $window = $(window);
    var positionTop = $element.offset().top;
    return ( positionTop > $window.scrollTop() && positionTop < ($window.scrollTop() + $window.height()) );
  };

  $().ready(function() {
    window.GOVUK.Analytics.scrollTracker = new ScrollTracker(CONFIG);
  });

  window.GOVUK.Analytics.ScrollTracker = ScrollTracker;
}());
<commit_msg>Configure scroll tracker for initial trial<commit_after>(function() {
  "use strict";

  window.GOVUK           = window.GOVUK || {};
  window.GOVUK.Analytics = window.GOVUK.Analytics || {};

  var CONFIG = {
    '/': [
      ['Heading', 'Services and information'],
      ['Heading', 'More on GOV.UK'],
      ['Percent', 80] //To track 'Services and information' section in footer
    ],
    '/bank-holidays': [
      ['Percent', 25],
      ['Percent', 50],
      ['Percent', 75]
    ],
    '/foreign-travel-advice/egypt': [
      ['Heading', 'Summary'],
      ['Heading', 'Terrorism'],
      ['Heading', 'Sharm el Sheikh'],
      ['Heading', 'Hurghada'],
      ['Heading', 'Protests and demonstrations'],
      ['Heading', 'Overseas Business Risk']
    ],
    '/foreign-travel-advice/kenya': [
      ['Percent', 25],
      ['Percent', 50],
      ['Percent', 75]
    ],
    '/foreign-travel-advice/spain': [
      ['Percent', 25],
      ['Percent', 50],
      ['Percent', 75]
    ],
    '/foreign-travel-advice/syria': [
      ['Percent', 25],
      ['Percent', 50],
      ['Percent', 75]
    ],
    '/foreign-travel-advice/thailand': [
      ['Percent', 25],
      ['Percent', 50],
      ['Percent', 75]
    ],
    '/foreign-travel-advice/turkey': [
      ['Heading', 'Summary'],
      ['Heading', 'Demonstrations'],
      ['Heading', 'Terrorism'],
      ['Heading', 'Visas'],
      ['Heading', 'Earthquakes'],
      ['Heading', 'Overseas Business Risk'],
      ['Heading', 'Travel insurance']
    ],
    '/jobsearch': [
      ['Heading', 'Registration'],
      ['Heading', 'Help']
    ],
    '/renew-adult-passport': [
      ['Heading', 'Damaged and expired passports'],
      ['Heading', 'Lost or stolen passports'],
      ['Heading', 'Apply for a passport'],
      ['Heading', 'Tracking your passport application']
    ],
    '/student-finance-register-login': [
      ['Heading', 'Log in problems'],
      ['Heading', 'Manage your student finance']
    ],
    '/tax-disc': [
      ['Heading', 'Other ways to apply'],
      ['Heading', 'Help with tax discs']
    ]
  };

  function ScrollTracker(sitewideConfig) {
    this.config = this.getConfigForCurrentPath(sitewideConfig);
    this.SCROLL_TIMEOUT_DELAY = 500;

    if ( !this.config ) {
      this.enabled = false;
      return;
    }
    this.enabled = true;

    this.trackedNodes = this.buildNodes(this.config);

    $(window).scroll($.proxy(this.onScroll, this));
    this.trackVisibleNodes();
  };

  ScrollTracker.prototype.getConfigForCurrentPath = function getConfigForCurrentPath(sitewideConfig) {
    for ( var path in sitewideConfig ) {
      if ( window.location.pathname == path ) return sitewideConfig[path];
    }
  };

  ScrollTracker.prototype.buildNodes = function buildNodes(config) {
    var nodes = [];
    var nodeConstructor, nodeData;

    for (var i=0; i<config.length; i++) {
      nodeConstructor = ScrollTracker[config[i][0] + "Node"];
      nodeData = config[i][1];
      nodes.push(new nodeConstructor(nodeData));
    }

    return nodes;
  };

  ScrollTracker.prototype.onScroll = function onScroll() {
    clearTimeout(this.scrollTimeout);
    this.scrollTimeout = setTimeout($.proxy(this.trackVisibleNodes, this), this.SCROLL_TIMEOUT_DELAY);
  };

  ScrollTracker.prototype.trackVisibleNodes = function trackVisibleNodes() {
    for ( var i=0; i<this.trackedNodes.length; i++ ) {
      if ( this.trackedNodes[i].isVisible() && !this.trackedNodes[i].alreadySeen ) {
        this.trackedNodes[i].alreadySeen = true;
        GOVUK.sendToAnalytics(["_trackEvent"].concat(this.trackedNodes[i].eventData));
      }
    }
  };

  ScrollTracker.PercentNode = function PercentNode(percentage) {
    this.percentage = percentage;
    this.eventData = ["ScrollTo", "Percent", String(percentage)];
  };

  ScrollTracker.PercentNode.prototype.isVisible = function isVisible() {
    return this.currentScrollPercent() >= this.percentage;
  };

  ScrollTracker.PercentNode.prototype.currentScrollPercent = function currentScrollPercent() {
    var $document = $(document);
    var $window = $(window);
    return( ($window.scrollTop() / ($document.height() - $window.height())) * 100.0 );
  };

  ScrollTracker.HeadingNode = function HeadingNode(headingText) {
    this.$element = getHeadingElement(headingText);
    this.eventData = ["ScrollTo", "Heading", headingText];

    function getHeadingElement(headingText) {
      var $headings = $('h1, h2, h3, h4, h5, h6');
      for ( var i=0; i<$headings.length; i++ ) {
        if ( $.trim($headings.eq(i).text()).replace(/\s/g, ' ') == headingText ) return $headings.eq(i);
      }
    }
  };

  ScrollTracker.HeadingNode.prototype.isVisible = function isVisible() {
    if ( !this.$element ) return false;
    return this.elementIsVisible(this.$element);
  }

  ScrollTracker.HeadingNode.prototype.elementIsVisible = function elementIsVisible($element) {
    var $window = $(window);
    var positionTop = $element.offset().top;
    return ( positionTop > $window.scrollTop() && positionTop < ($window.scrollTop() + $window.height()) );
  };

  $().ready(function() {
    window.GOVUK.Analytics.scrollTracker = new ScrollTracker(CONFIG);
  });

  window.GOVUK.Analytics.ScrollTracker = ScrollTracker;
}());
<|endoftext|>


<commit_before>(function () {

  /* Probably a better way to do this, but not that I could think of.
   * Chris Rhoden 2013 for PRX and Pop Up Archive.
   */

  var $realRootScope, actuallyIsLoading = 0, pretendLoading = 0, pageLoadings = 0;

  function loading (val) {
    if (val === true) {
      pretendLoading += 1;
    } else if (val === false) {
      pretendLoading -=1;
      if (pretendLoading < 0) pretendLoading = 0;
    } else {
      return (actuallyIsLoading || pretendLoading);
    }
  }

  function pageLoading (val) {
    if (val === true) {
      pageLoadings += 1;
    } else if (val === false) {
      pageLoadings -= 1;
      if (pageLoadings < 0) pageLoadings = 0;
    } else {
      return !!pageLoadings;
    }
  }

  angular.module('ngLoadingIndicators', [])
  .config(['$httpProvider', function ($httpProvider) {
    $httpProvider.responseInterceptors.push('myHttpInterceptor');
    function setIsLoading (data, headersGetter) {
      actuallyIsLoading += 1;
      return data;
    };
    $httpProvider.defaults.transformRequest.push(setIsLoading);
  }])
  .factory('myHttpInterceptor', ['$q', '$rootScope', '$location', function ($q, $rootScope, $location) {
    if (typeof $rootScope.setIsLoading === 'undefined') {
      $realRootScope = $rootScope;
      $rootScope.loading = loading;
      $rootScope.pageLoading = pageLoading;
    }

    return function (promise) {
      return promise.then(function success(response) {
        actuallyIsLoading -= 1;
        if (actuallyIsLoading < 0) actuallyIsLoading = 0;
        return response;
      }, function error(response) {
        var errCode = response.status;
        // avoid recursion (error throwing error)
        // and only re-route if this was the main request (matches window.location) or a non-404.
        //console.log("error " + errCode + " for path " + $location.path() + " for response", response);
        if (response && response.config) {
          if (!$location.search().was && (response.config.url == '/api' + $location.path() || errCode != 404)) {
            console.log("redirect error " + errCode + " for response path " + response.config.url);
            $rootScope.errorLocation = response.config.url;
            $rootScope.prevLocation = $location.absUrl();
            $location.path('/error/' + errCode).search({was:$rootScope.prevLocation})
          }
        };
        actuallyIsLoading -= 1;
        if (actuallyIsLoading < 0) actuallyIsLoading = 0;
        return $q.reject(response);
      });
    };
  }])
  .factory('loading', function() {
    function maskedLoading (val) {
      return loading(val);
    }

    maskedLoading.page = function (val) {
      return pageLoading(val);
    }

    return maskedLoading;
  });
}());

<commit_msg>API responses of 404 or 5xx are silently skipped when requested as ancillary objects via the UI. #990<commit_after>(function () {

  /* Probably a better way to do this, but not that I could think of.
   * Chris Rhoden 2013 for PRX and Pop Up Archive.
   */

  var $realRootScope, actuallyIsLoading = 0, pretendLoading = 0, pageLoadings = 0;

  function loading (val) {
    if (val === true) {
      pretendLoading += 1;
    } else if (val === false) {
      pretendLoading -=1;
      if (pretendLoading < 0) pretendLoading = 0;
    } else {
      return (actuallyIsLoading || pretendLoading);
    }
  }

  function pageLoading (val) {
    if (val === true) {
      pageLoadings += 1;
    } else if (val === false) {
      pageLoadings -= 1;
      if (pageLoadings < 0) pageLoadings = 0;
    } else {
      return !!pageLoadings;
    }
  }

  angular.module('ngLoadingIndicators', [])
  .config(['$httpProvider', function ($httpProvider) {
    $httpProvider.responseInterceptors.push('myHttpInterceptor');
    function setIsLoading (data, headersGetter) {
      actuallyIsLoading += 1;
      return data;
    };
    $httpProvider.defaults.transformRequest.push(setIsLoading);
  }])
  .factory('myHttpInterceptor', ['$q', '$rootScope', '$location', function ($q, $rootScope, $location) {
    if (typeof $rootScope.setIsLoading === 'undefined') {
      $realRootScope = $rootScope;
      $rootScope.loading = loading;
      $rootScope.pageLoading = pageLoading;
    }

    return function (promise) {
      return promise.then(function success(response) {
        actuallyIsLoading -= 1;
        if (actuallyIsLoading < 0) actuallyIsLoading = 0;
        return response;
      }, function error(response) {
        var errCode = response.status;
        // avoid recursion (error throwing error)
        // and only re-route if this was the main request (matches window.location) or a non-404.
        //console.log("error " + errCode + " for path " + $location.path() + " for response", response);
        if (response && response.config) {
          var main_req = '/api' + $location.path();
          if (!$location.search().was && (response.config.url == main_req || !errCode.match(/^(404|5)/) ) ) {
            //console.log("redirect error " + errCode + " for response path " + response.config.url);
            $rootScope.errorLocation = response.config.url;
            $rootScope.prevLocation = $location.absUrl();
            $location.path('/error/' + errCode).search({was:$rootScope.prevLocation})
          }
        };
        actuallyIsLoading -= 1;
        if (actuallyIsLoading < 0) actuallyIsLoading = 0;
        return $q.reject(response);
      });
    };
  }])
  .factory('loading', function() {
    function maskedLoading (val) {
      return loading(val);
    }

    maskedLoading.page = function (val) {
      return pageLoading(val);
    }

    return maskedLoading;
  });
}());

<|endoftext|>


<commit_before>App.addChild('ProjectEditForm', _.extend({
  el: '.project-form',

  events: {
    'blur input' : 'checkInput'
  },

  activate: function() {
    this.setupForm();
  },
}, Skull.Form));

App.addChild('ProjectEdit', _.extend({
  el: '.project-dashboard-edit',

  events: {
    'click .dashboard-nav-link-left' : 'toggleNav',
    'click a.dashboard-nav-link-left' : 'goToTop'
  },

  activate: function(){
    this.route('basics');
    this.route('goal');
    this.route('description');
    this.route('budget');
    this.route('card');
    this.route('home');
    this.route('video');
    this.route('posts');
    this.route('reward');
    this.route('user_about');
    this.route('preview');
    this.route('edit');
    this.route('user_settings');
    this.route('reports');

    if($('.fa-exclamation-circle').length >= 1) {
      window.location.hash = $('.fa-exclamation-circle:eq(0)').parent().attr('href');
    } else {
      this.lookAnchors();
    }
  },

  goToTop: function(){
    window.scrollTo(0, 0);
  },

  toggleNav: function(){
    if(app.isMobile()){
      app.toggleNav();
    }
  },

  followRoute: function(name){
    var $tab = this.$('nav a[href="' + window.location.hash + '"]');
    var $tab_title = $('#dashboard_' + window.location.hash.split('#')[1]).data('page_title');
    var $tab_subtitle = $('#dashboard_' + window.location.hash.split('#')[1]).data('page_subtitle');
    if($tab.length > 0){
      this.onTabClick({ currentTarget: $tab });
    }
    $('#dashboard-page-title').text($tab_title);
    $('#dashboard-page-subtitle').text($tab_subtitle);
  },

  loadEmbed: function() {
    var that = this;

    if(this.$embed.find('.loader').length > 0) {
      $.get(this.$embed.data('path')).success(function(data){
        that.$embed.html(data);
      });
    }
  }
}, Skull.Tabs));
<commit_msg>hide navbar on click on mobile<commit_after>App.addChild('ProjectEditForm', _.extend({
  el: '.project-form',

  events: {
    'blur input' : 'checkInput'
  },

  activate: function() {
    this.setupForm();
  },
}, Skull.Form));

App.addChild('ProjectEdit', _.extend({
  el: '.project-dashboard-edit',

  events: {
    'click a.dashboard-nav-link-left' : 'toggleNav'
  },

  activate: function(){
    this.route('basics');
    this.route('goal');
    this.route('description');
    this.route('budget');
    this.route('card');
    this.route('home');
    this.route('video');
    this.route('posts');
    this.route('reward');
    this.route('user_about');
    this.route('preview');
    this.route('edit');
    this.route('user_settings');
    this.route('reports');

    if($('.fa-exclamation-circle').length >= 1) {
      window.location.hash = $('.fa-exclamation-circle:eq(0)').parent().attr('href');
    } else {
      this.lookAnchors();
    }
  },

  toggleNav: function(){
    window.scrollTo(0, 0);
    if(app.isMobile()){
      app.toggleNav();
    }
  },

  followRoute: function(name){
    var $tab = this.$('nav a[href="' + window.location.hash + '"]');
    var $tab_title = $('#dashboard_' + window.location.hash.split('#')[1]).data('page_title');
    var $tab_subtitle = $('#dashboard_' + window.location.hash.split('#')[1]).data('page_subtitle');
    if($tab.length > 0){
      this.onTabClick({ currentTarget: $tab });
    }
    $('#dashboard-page-title').text($tab_title);
    $('#dashboard-page-subtitle').text($tab_subtitle);
  },

  loadEmbed: function() {
    var that = this;

    if(this.$embed.find('.loader').length > 0) {
      $.get(this.$embed.data('path')).success(function(data){
        that.$embed.html(data);
      });
    }
  }
}, Skull.Tabs));
<|endoftext|>


<commit_before>var app = app || {};

(function ($) {
  'use strict';

  app.FirewoodsView = Backbone.View.extend({
    el: '#firewoods',
    count: 0,
    maxCount: 150,

    initialize: function () {
      this.$list = this.$('#timeline');
      this.$stack = this.$('#timeline_stack');
      this.$form = this.$('#new_firewood');
      this.$input = this.$('#contents');
      this.$title = $('#title');
      this.$commitBtn = this.$('#commit');
      this.$prevMt = this.$('#firewood_prev_mt');

      this.listenTo(app.firewoods, 'reset', this.addAll);
      this.listenTo(app.firewoods, 'add:prepend', this.prepend);
      this.listenTo(app.firewoods, 'add:append', this.append);
      this.listenTo(app.firewoods, 'add:stack', this.updateStackNotice);
      this.listenTo(app.firewoods, 'change:isHighlighted', this.highlightOne);

      this.listenTo(app.channel, 'ajaxSuccess', this.formClear);
      this.listenTo(app.firewoods, 'form:appendMt', this.appendMt);
      this.listenTo(app.firewoods, 'form:flash', this.flashForm);
      this.listenTo(app.firewoods, 'timeline:foldAll', this.foldAll);
      this.listenTo(app.firewoods, 'timeline:unFoldAll', this.unFoldAll);

      this.$('span[rel=tooltip]').tooltip();
      this.originTitle = this.$title.text();

      $(document).ajaxError(app.channel.ajaxError);
      app.channel.load();
      app.channel.setPullingTimer();
      setInterval(this.isNearBottom, 1000, this);
    },

    events: {
      'input #contents': 'update_count',
      'keyup #contents': 'update_count',
      'keydown #contents': 'keydown',
      'paste #contents': 'update_count',
      'click #timeline_stack': 'flushStack',
      'submit #new_firewood': 'submit',
      'blur #contents': function () {
        if ($('#contents').html() == '
')
          $('#contents').html('');
      }
    },

    keydown: function (e) {
      if (e.keyCode == window.ENTER_KEY) {
        this.submit(e);
      } else if (e.keyCode == window.ESC_KEY) {
        $('#contents').blur();
      } else {
        $('#contents').attr('bw-text-empty',"false");
        e.stopPropagation();
      }
    },

    // Collection Event
    addAll: function () {
      this.$list.html('');
      app.firewoods.each(function (fw) {
        var view = new app.FirewoodView({ model: fw });
        this.$list.append(view.render().el);
      }, this);
    },

    prepend: function () {
      var fws = app.firewoods.where({ state: FW_STATE.IN_STACK }).reverse();
      _.each(fws, function(fw) {
        var view = new app.FirewoodView({ model: fw });
        this.$list.prepend(view.render().el);
        fw.set('state', 0);
      }, this);

      return this;
    },

    append: function () {
      var fws = app.firewoods.where({ state: FW_STATE.IN_LOG });
      _.each(fws, function(fw) {
        var view = new app.FirewoodView({ model: fw });
        this.$list.append(view.render().el);
        fw.set('state', 0);
      }, this);

      return this;
    },

    updateStackNotice: function () {
      var fws = app.firewoods.where({ state: FW_STATE.IN_STACK});
      if ( fws.length == 0 ) {
        return false;
      }

      this.$title.html(this.originTitle + ' (' + fws.length + ')');
      this.$stack
        .html('새 장작이 ' + fws.length + '개 있습니다.')
        .slideDown(200);
    },

    highlightOne: function (fw) {
      fw.trigger('highlight');
    },

    submit: function (e) {
      e.preventDefault();

      if ( !this.isFormEmpty() ) {
        this.$form.ajaxSubmit(app.channel.ajaxBasicOptions);
      } else {
        app.channel.pulling(true, true);
      }
      this.$title.html(this.originTitle);
      this.$stack.html('').slideUp(200);

      return false;
    },

    formClear: function() {
      this.$form.clearForm();
      this.$('#contents').html('');
      this.$('#img').val('');
      this.$('.fileinput-preview').html('');
      this.$('.fileinput')
        .removeClass('fileinput-exists')
        .addClass('fileinput-new');
      this.$('.fileinput-filename').html('');
      this.$prevMt.val('');
      this.$commitBtn.button('reset');

      this.update_count();
    },

    update_count: function () {
      var before = this.count;
      var now = this.maxCount - this.$input.text().length;

      if ( this.isFormEmpty() ) {
        this.$commitBtn.val('Refresh');
      } else {
        this.$commitBtn.val('Submit');
      }

      if ( now < 0 ) {
        var str = this.$input.text();
        this.$input.text(str.substr(0, this.maxCount));
        app.util.placeCaretAtEnd('contents');
        now = 0;
      }

      if ( before != now ) {
        this.count = now;
        this.$('#remaining_count').text(now);
      }

      if ($('#contents').text().length == 0) {
          $('#contents')
            .attr('bw-text-empty',"true");
      } else {
        $('#contents').attr('bw-text-empty',"false");
      }
    },

    appendMt: function (names, target) {
      var user_name = $.cookie("user_name");
      var mts = _.map(names, function (name) { return name; });
      mts = _.filter(mts, function(mt) { return user_name != mt.slice(1); });

      this.$prevMt.val(target);
      this.$input
        .text( mts.join(' ') + ' ' + this.$input.text() );
      app.util.placeCaretAtEnd('contents');
      this.update_count();
    },

    flushStack: function () {
      $('.last_top').removeClass('last_top').attr('style','');
      $('.div-firewood:first').addClass('last_top').attr('style','border-top-width:3px;');

      var fws = app.firewoods.filter(function (fw) { return fw.get('state') == -1 && fw.get('img_link') != '0' });
      this.prepend();
      this.$title.html(this.originTitle);
      this.$stack.html('').slideUp(200);

      if ( window.localStorage['auto_image_open'] == '1' ) {
        _.each(fws, function (fw) { fw.trigger('unFold') });
      }
    },

    foldAll: function () {
      var fws = app.firewoods.where({ isOpened: true });
      _.each(fws, function ( fw ) { fw.trigger('fold'); });
    },

    unFoldAll: function () {
      var fws = app.firewoods.filter( function (fw) { return fw.get('img_link') !== '0' });
      _.each(fws, function ( fw ) { fw.trigger('unFold'); });
    },

    isNearBottom: function (context) {
      var $fws = $('.firewood');
      var fws = app.firewoods;
      var self = context;

      if ( fws.size() == 0 || !$fws.eq(-5).isOnScreen() || fws.logGetLock || fws.length < 50 || fws.last().get('id') < 10 ) {
        return false;
      }

      self.$list.append('

로딩중입니다.

');
      app.channel.getLogs().then(function () {
        $('#div-loading').parent().remove();
      });
    },

    isFormEmpty: function () {
      if ( this.$('div.fileinput-exists').length == 0 ) {
        var contents = this.$('#contents');
        var str = contents.text();
        if ( contents.attr('bw-text-empty') == 'true' || str.search(/^\s+$/) != -1 ) {
          return true;
        }
      }
      return false;
    },

    flashForm: function () {
      var $formDiv = this.$form.parent();
      $formDiv.fadeOut(300).fadeIn(500);
    }
  });
})(jQuery);<commit_msg>fixed #68. filter after paste text from outside. escape html.<commit_after>var app = app || {};

(function ($) {
  'use strict';

  app.FirewoodsView = Backbone.View.extend({
    el: '#firewoods',
    count: 0,
    maxCount: 150,

    initialize: function () {
      this.$list = this.$('#timeline');
      this.$stack = this.$('#timeline_stack');
      this.$form = this.$('#new_firewood');
      this.$input = this.$('#contents');
      this.$title = $('#title');
      this.$commitBtn = this.$('#commit');
      this.$prevMt = this.$('#firewood_prev_mt');

      this.listenTo(app.firewoods, 'reset', this.addAll);
      this.listenTo(app.firewoods, 'add:prepend', this.prepend);
      this.listenTo(app.firewoods, 'add:append', this.append);
      this.listenTo(app.firewoods, 'add:stack', this.updateStackNotice);
      this.listenTo(app.firewoods, 'change:isHighlighted', this.highlightOne);

      this.listenTo(app.channel, 'ajaxSuccess', this.formClear);
      this.listenTo(app.firewoods, 'form:appendMt', this.appendMt);
      this.listenTo(app.firewoods, 'form:flash', this.flashForm);
      this.listenTo(app.firewoods, 'timeline:foldAll', this.foldAll);
      this.listenTo(app.firewoods, 'timeline:unFoldAll', this.unFoldAll);

      this.$('span[rel=tooltip]').tooltip();
      this.originTitle = this.$title.text();

      $(document).ajaxError(app.channel.ajaxError);
      app.channel.load();
      app.channel.setPullingTimer();
      setInterval(this.isNearBottom, 1000, this);
    },

    events: {
      'input #contents': 'update_count',
      'keyup #contents': 'update_count',
      'keydown #contents': 'keydown',
      'paste #contents': 'paste_something',
      'click #timeline_stack': 'flushStack',
      'submit #new_firewood': 'submit',
      'blur #contents': function () {
        if ($('#contents').html() == '
')
          $('#contents').html('');
      }
    },

    paste_something: function () {
      setTimeout(function() {
        $('#contents').html($('#contents').text());
        this.update_count();
      }.bind(this), 0);
    },

    keydown: function (e) {
      if (e.keyCode == window.ENTER_KEY) {
        this.submit(e);
      } else if (e.keyCode == window.ESC_KEY) {
        $('#contents').blur();
      } else {
        $('#contents').attr('bw-text-empty',"false");
        e.stopPropagation();
      }
    },

    // Collection Event
    addAll: function () {
      this.$list.html('');
      app.firewoods.each(function (fw) {
        var view = new app.FirewoodView({ model: fw });
        this.$list.append(view.render().el);
      }, this);
    },

    prepend: function () {
      var fws = app.firewoods.where({ state: FW_STATE.IN_STACK }).reverse();
      _.each(fws, function(fw) {
        var view = new app.FirewoodView({ model: fw });
        this.$list.prepend(view.render().el);
        fw.set('state', 0);
      }, this);

      return this;
    },

    append: function () {
      var fws = app.firewoods.where({ state: FW_STATE.IN_LOG });
      _.each(fws, function(fw) {
        var view = new app.FirewoodView({ model: fw });
        this.$list.append(view.render().el);
        fw.set('state', 0);
      }, this);

      return this;
    },

    updateStackNotice: function () {
      var fws = app.firewoods.where({ state: FW_STATE.IN_STACK});
      if ( fws.length == 0 ) {
        return false;
      }

      this.$title.html(this.originTitle + ' (' + fws.length + ')');
      this.$stack
        .html('새 장작이 ' + fws.length + '개 있습니다.')
        .slideDown(200);
    },

    highlightOne: function (fw) {
      fw.trigger('highlight');
    },

    submit: function (e) {
      e.preventDefault();

      if ( !this.isFormEmpty() ) {
        this.$form.ajaxSubmit(app.channel.ajaxBasicOptions);
      } else {
        app.channel.pulling(true, true);
      }
      this.$title.html(this.originTitle);
      this.$stack.html('').slideUp(200);

      return false;
    },

    formClear: function() {
      this.$form.clearForm();
      this.$('#contents').html('');
      this.$('#img').val('');
      this.$('.fileinput-preview').html('');
      this.$('.fileinput')
        .removeClass('fileinput-exists')
        .addClass('fileinput-new');
      this.$('.fileinput-filename').html('');
      this.$prevMt.val('');
      this.$commitBtn.button('reset');

      this.update_count();
    },

    update_count: function () {
      var before = this.count;
      var now = this.maxCount - this.$input.text().length;

      if ( this.isFormEmpty() ) {
        this.$commitBtn.val('Refresh');
      } else {
        this.$commitBtn.val('Submit');
      }

      if ( now < 0 ) {
        var str = this.$input.text();
        this.$input.text(str.substr(0, this.maxCount));
        app.util.placeCaretAtEnd('contents');
        now = 0;
      }

      if ( before != now ) {
        this.count = now;
        this.$('#remaining_count').text(now);
      }

      if ($('#contents').text().length == 0) {
          $('#contents')
            .attr('bw-text-empty',"true");
      } else {
        $('#contents').attr('bw-text-empty',"false");
      }
    },

    appendMt: function (names, target) {
      var user_name = $.cookie("user_name");
      var mts = _.map(names, function (name) { return name; });
      mts = _.filter(mts, function(mt) { return user_name != mt.slice(1); });

      this.$prevMt.val(target);
      this.$input
        .text( mts.join(' ') + ' ' + this.$input.text() );
      app.util.placeCaretAtEnd('contents');
      this.update_count();
    },

    flushStack: function () {
      $('.last_top').removeClass('last_top').attr('style','');
      $('.div-firewood:first').addClass('last_top').attr('style','border-top-width:3px;');

      var fws = app.firewoods.filter(function (fw) { return fw.get('state') == -1 && fw.get('img_link') != '0' });
      this.prepend();
      this.$title.html(this.originTitle);
      this.$stack.html('').slideUp(200);

      if ( window.localStorage['auto_image_open'] == '1' ) {
        _.each(fws, function (fw) { fw.trigger('unFold') });
      }
    },

    foldAll: function () {
      var fws = app.firewoods.where({ isOpened: true });
      _.each(fws, function ( fw ) { fw.trigger('fold'); });
    },

    unFoldAll: function () {
      var fws = app.firewoods.filter( function (fw) { return fw.get('img_link') !== '0' });
      _.each(fws, function ( fw ) { fw.trigger('unFold'); });
    },

    isNearBottom: function (context) {
      var $fws = $('.firewood');
      var fws = app.firewoods;
      var self = context;

      if ( fws.size() == 0 || !$fws.eq(-5).isOnScreen() || fws.logGetLock || fws.length < 50 || fws.last().get('id') < 10 ) {
        return false;
      }

      self.$list.append('

로딩중입니다.

');
      app.channel.getLogs().then(function () {
        $('#div-loading').parent().remove();
      });
    },

    isFormEmpty: function () {
      if ( this.$('div.fileinput-exists').length == 0 ) {
        var contents = this.$('#contents');
        var str = contents.text();
        if ( contents.attr('bw-text-empty') == 'true' || str.search(/^\s+$/) != -1 ) {
          return true;
        }
      }
      return false;
    },

    flashForm: function () {
      var $formDiv = this.$form.parent();
      $formDiv.fadeOut(300).fadeIn(500);
    }
  });
})(jQuery);<|endoftext|>


<commit_before>/*global jQuery,cyberDojo*/
'use strict';
var cyberDojo = (function(cd, $) {

  cd.invoiceMe = () => {
    const url = "mailto:pies@cyber-dojo.org?subject=cyber-dojo donation - please invoice me";
    window.open(url, '_blank');
    return false;
  };

  cd.dialog_donate = () => {
    const title = 'please donate';
    const donateButton = () => {
      return '' +
      '<form action="https://www.paypal.com/cgi-bin/webscr"' +
           ' method="post"' +
           ' id="donate-form"' +
           ' target="_blank">' +
      '<input type="hidden"' +
            ' name="cmd"' +
            ' value="_s-xclick">' +
      '<input type="hidden"' +
            ' name="hosted_button_id"' +
            ' value="7HAUYJCMFCS8C">' +
      '<input type="image"' +
            ' src="/images/donate.png"' +
            ' width="79"' +
            ' height="22"' +
            ' name="submit"' +
            ' alt="PayPal - The safe, easier way to pay online.">' +
      '' +
      '</form>';
    };

    const html = '' +
      '
' +
        '
' +
          '
' +
            '' + donateButton() + '	' +
            '' +
              "  for an individual, we suggest donating $5+" +
            '	' +
          '
' +
          '
' +
            '' + donateButton() + '	' +
            '' +
              "  for a non-profit meetup, we suggest donating $15+" +
            '	' +
          '
' +
          '
' +
            '' + donateButton() + '	' +
            '' +
              "  for a commercial organization, we suggest donating $500+" +
            '	' +
          '
' +
          '
' +
            '' +
              'if you need an invoice, please ' +
              '<button id="email-me" onclick="return cd.invoiceMe();">email</button>' +
            '	' +
          '
' +
         '
' +
      '
';

    return cd.dialog(html, title, 'close');
  };

  return cd;

})(cyberDojo || {}, jQuery);
<commit_msg>Increase suggested amounts on donate dialog<commit_after>/*global jQuery,cyberDojo*/
'use strict';
var cyberDojo = (function(cd, $) {

  cd.invoiceMe = () => {
    const url = "mailto:pies@cyber-dojo.org?subject=cyber-dojo donation - please invoice me";
    window.open(url, '_blank');
    return false;
  };

  cd.dialog_donate = () => {
    const title = 'please donate';
    const donateButton = () => {
      return '' +
      '<form action="https://www.paypal.com/cgi-bin/webscr"' +
           ' method="post"' +
           ' id="donate-form"' +
           ' target="_blank">' +
      '<input type="hidden"' +
            ' name="cmd"' +
            ' value="_s-xclick">' +
      '<input type="hidden"' +
            ' name="hosted_button_id"' +
            ' value="7HAUYJCMFCS8C">' +
      '<input type="image"' +
            ' src="/images/donate.png"' +
            ' width="79"' +
            ' height="22"' +
            ' name="submit"' +
            ' alt="PayPal - The safe, easier way to pay online.">' +
      '' +
      '</form>';
    };

    const html = '' +
      '
' +
        '
' +
          '
' +
            '' + donateButton() + '	' +
            '' +
              "  for an individual, we suggest donating $10+" +
            '	' +
          '
' +
          '
' +
            '' + donateButton() + '	' +
            '' +
              "  for a non-profit meetup, we suggest donating $25+" +
            '	' +
          '
' +
          '
' +
            '' + donateButton() + '	' +
            '' +
              "  for a commercial organization, we suggest donating $1000+" +
            '	' +
          '
' +
          '
' +
            '' +
              'if you need an invoice, please email license@cyber-dojo.org' +
            '	' +
          '
' +
         '
' +
      '
';

    return cd.dialog(html, title, 'close');
  };

  return cd;

})(cyberDojo || {}, jQuery);
<|endoftext|>


<commit_before>import Ember from 'ember';

const ClosureActionModule = Ember.__loader.require('ember-routing-htmlbars/keywords/closure-action');

export default ClosureActionModule.ACTION;
<commit_msg>Auto merge of #167 - martndemus:fix-closure-action-import-canary, r=martndemus<commit_after>import Ember from 'ember';

let ClosureActionModule;

if ('ember-htmlbars/keywords/closure-action' in Ember.__loader.registry) {
  ClosureActionModule = Ember.__loader.require('ember-htmlbars/keywords/closure-action');
} else {
  ClosureActionModule = Ember.__loader.require('ember-routing-htmlbars/keywords/closure-action');
}

export default ClosureActionModule.ACTION;
<|endoftext|>


<commit_before>import Component from 'ember-component';
import computed from 'ember-computed';
import {htmlSafe} from 'ember-string';
import {debounce, next, throttle} from 'ember-runloop';
import layout from '../templates/components/emoji-picker';
import service from 'ember-service/inject';
import observer from 'ember-metal/observer';
import ClickOutsideMixin from 'ember-click-outside/mixins/click-outside';
import {A} from 'ember-array/utils';
import {default as EObject} from 'ember-object';
import {EMOJI_CATEGORIES_ARRAY, EMOJI_TONES_ARRAY} from "../utils/constants";

const O = EObject.create.bind(EObject);
const EMOJI_PICKER_SCROLLABLE_ELEMENT = '.eeo-emojiPicker-scrollable';
const DEPENDENT_KEYS_FOR_EMOJI_CATEGORIES = EMOJI_CATEGORIES_ARRAY.join(',');
const DEPENDENT_KEYS_FOR_EMOJI_TONES = (() => {
  let keys = [];

  EMOJI_CATEGORIES_ARRAY.forEach(category => {
    EMOJI_TONES_ARRAY.forEach(tone => {
      const value = `${category}__tone_${tone}`;
      keys.push(value);
    });
  });

  return keys.join(',');
})();

export default Component.extend(ClickOutsideMixin, {

  selectAction:        undefined,
  toneSelectAction:    undefined,
  closeAction:         undefined,
  shouldCloseOnSelect: false,
  disableAutoFocus:    false,
  textNoEmojiFound:    "No emoji found",
  textSearch:          "Search",
  textClearSearch:     "Clear search",

  emojiService: service('emoji'),

  layout,
  classNames:   ['eeo-emojiPicker'],
  _filterInput: '',
  filterInput: '',

  scrollableId: computed(function () {
    return Math.random().toString(36).substr(2, 5);
  }),

  $scrollable: computed(function () {
    return this.$(EMOJI_PICKER_SCROLLABLE_ELEMENT);
  }),

  categorySections: computed('emojiService.categories', function () {
    const objs =
      this
        .get('emojiService.categories')
        .map(category => O({
          category,
          style: htmlSafe("")
        }));

    return A(objs);
  }),

  emojiByCategoryIdFiltered: computed(
    'filterInput',
    'emojiService.currentSkinTone',
    'emojiService.categories.@each.id',
    DEPENDENT_KEYS_FOR_EMOJI_TONES,
    function () {
      const emojiCategories = this.get('emojiService.categories');
      const filterInput     = this.get('filterInput');
      const hasFilters      = filterInput.length;

      function getFilteredEmoji(emoji) {
        const filterStrs = filterInput.split(' ');
        return emoji.filter(emojo =>
          filterStrs.every(str => emojo.filterable.indexOf(str) > -1)
        );
      }

      return emojiCategories.reduce((result, category) => {
        const categoryId = category.get('id');
        const emoji      = this._getEmojiForCategoryId(categoryId);
        const finalEmoji = hasFilters ? getFilteredEmoji(emoji) : emoji;

        result.set(categoryId, finalEmoji);

        return result;
      }, O());
    }
  ),

  filteredEmojiCount: computed(
    `emojiByCategoryIdFiltered.{${DEPENDENT_KEYS_FOR_EMOJI_CATEGORIES}}.length`,
    function () {
      return EMOJI_CATEGORIES_ARRAY.reduce((count, category) => {
        return count + this.get(`emojiByCategoryIdFiltered.${category}.length`);
      }, 0);
    }
  ),

  _applyFilterInput(filterInput) {
    if (this.get('isDestroying') || this.get('isDestroyed')) return;

    this.setProperties({filterInput});
    next(() => this._updateScroll());
  },

  _applyFilterInputDebounced: observer('_filterInput', function () {
    const filterInput = this.get('_filterInput');
    debounce(this, this._applyFilterInput, filterInput, 200, false);
  }),

  _updateScroll() {
    if (this.get('isDestroying') || this.get('isDestroyed')) return;

    const $scrollable = this.get('$scrollable');
    const parHeight   = $scrollable.innerHeight();

    this
      .get('categorySections')
      .forEach(section => {
        const $category = this.$(`.eeo-emojiPicker-category._${section.category.get('id')} .eeo-emojiPicker-category-emoji`);

        if (!$category.length) {
          section.set('style', htmlSafe(''));
          return;
        }

        const catTop    = $category.position().top;
        const catHeight = $category.outerHeight();
        const catBottom = catTop + catHeight;
        const left      = (-catTop) / catHeight;
        const right     = (catBottom - parHeight) / catHeight;
        const style     = htmlSafe(`left: ${left  * 100}%; right: ${right * 100}%;`);

        section.setProperties({style});
      });
  },

  _getEmojiForCategoryId(categoryId) {
    const currentSkinTone = this.get('emojiService.currentSkinTone');
    const emojiPropName = `emojiService.${categoryId}__tone_${currentSkinTone}`;
    return this.get(emojiPropName);
  },

  _getFilteredEmojiForCategoryId(categoryId) {
    const emojiPropName = `emojiByCategoryIdFiltered.${categoryId}`;
    return this.get(emojiPropName);
  },

  didInsertElement() {
    this._super(...arguments);

    this._updateScroll();

    this
      .$(EMOJI_PICKER_SCROLLABLE_ELEMENT)
      .on('scroll.eeo', () => throttle(this, this._updateScroll, 200, false));

    next(() => this.addClickOutsideListener());
  },

  willDestroyElement() {
    this._super(...arguments);

    this
      .$(EMOJI_PICKER_SCROLLABLE_ELEMENT)
      .off('scroll.eeo');

    this.removeClickOutsideListener();
  },

  clickOutside() {
    const closeAction = this.get('closeAction');
    if (closeAction) this.sendAction('closeAction');
  },

  actions: {
    selectEmojo(emojo) {
      this.sendAction('selectAction', emojo);

      if (this.get('closeAction') && this.get('shouldCloseOnSelect')) {
        this.sendAction('closeAction');
      }
    },

    inputFilteringText(_filterInput) {
      this.setProperties({_filterInput});
    },

    enterPressedInInput() {
      let emojo = null;

      this
        .get('emojiService.categories')
        .find(category => {
          const emoji = this._getFilteredEmojiForCategory(category.get('id'));
          if (emoji.length > 0) {
            emojo = emoji[0];
            return true;
          }
        });

      if (emojo) this.send('selectEmojo', emojo);
    },
  }
});
<commit_msg>Keep line lengths at ~80 chars<commit_after>import Component from 'ember-component';
import computed from 'ember-computed';
import {htmlSafe} from 'ember-string';
import {debounce, next, throttle} from 'ember-runloop';
import layout from '../templates/components/emoji-picker';
import service from 'ember-service/inject';
import observer from 'ember-metal/observer';
import ClickOutsideMixin from 'ember-click-outside/mixins/click-outside';
import {A} from 'ember-array/utils';
import {default as EObject} from 'ember-object';
import {EMOJI_CATEGORIES_ARRAY, EMOJI_TONES_ARRAY} from "../utils/constants";

const O = EObject.create.bind(EObject);
const EMOJI_PICKER_SCROLLABLE_ELEMENT = '.eeo-emojiPicker-scrollable';
const DEPENDENT_KEYS_FOR_EMOJI_CATEGORIES = EMOJI_CATEGORIES_ARRAY.join(',');
const DEPENDENT_KEYS_FOR_EMOJI_TONES = (() => {
  let keys = [];

  EMOJI_CATEGORIES_ARRAY.forEach(category => {
    EMOJI_TONES_ARRAY.forEach(tone => {
      const value = `${category}__tone_${tone}`;
      keys.push(value);
    });
  });

  return keys.join(',');
})();

export default Component.extend(ClickOutsideMixin, {

  selectAction:        undefined,
  toneSelectAction:    undefined,
  closeAction:         undefined,
  shouldCloseOnSelect: false,
  disableAutoFocus:    false,
  textNoEmojiFound:    "No emoji found",
  textSearch:          "Search",
  textClearSearch:     "Clear search",

  emojiService: service('emoji'),

  layout,
  classNames:   ['eeo-emojiPicker'],
  _filterInput: '',
  filterInput: '',

  scrollableId: computed(function () {
    return Math.random().toString(36).substr(2, 5);
  }),

  $scrollable: computed(function () {
    return this.$(EMOJI_PICKER_SCROLLABLE_ELEMENT);
  }),

  categorySections: computed('emojiService.categories', function () {
    const objs =
      this
        .get('emojiService.categories')
        .map(category => O({
          category,
          style: htmlSafe("")
        }));

    return A(objs);
  }),

  emojiByCategoryIdFiltered: computed(
    'filterInput',
    'emojiService.currentSkinTone',
    'emojiService.categories.@each.id',
    DEPENDENT_KEYS_FOR_EMOJI_TONES,
    function () {
      const emojiCategories = this.get('emojiService.categories');
      const filterInput     = this.get('filterInput');
      const hasFilters      = filterInput.length;

      function getFilteredEmoji(emoji) {
        const filterStrs = filterInput.split(' ');
        return emoji.filter(emojo =>
          filterStrs.every(str => emojo.filterable.indexOf(str) > -1)
        );
      }

      return emojiCategories.reduce((result, category) => {
        const categoryId = category.get('id');
        const emoji      = this._getEmojiForCategoryId(categoryId);
        const finalEmoji = hasFilters ? getFilteredEmoji(emoji) : emoji;

        result.set(categoryId, finalEmoji);

        return result;
      }, O());
    }
  ),

  filteredEmojiCount: computed(
    `emojiByCategoryIdFiltered.{${DEPENDENT_KEYS_FOR_EMOJI_CATEGORIES}}.length`,
    function () {
      return EMOJI_CATEGORIES_ARRAY.reduce((count, category) => {
        return count + this.get(`emojiByCategoryIdFiltered.${category}.length`);
      }, 0);
    }
  ),

  _applyFilterInput(filterInput) {
    if (this.get('isDestroying') || this.get('isDestroyed')) return;

    this.setProperties({filterInput});
    next(() => this._updateScroll());
  },

  _applyFilterInputDebounced: observer('_filterInput', function () {
    const filterInput = this.get('_filterInput');
    debounce(this, this._applyFilterInput, filterInput, 200, false);
  }),

  _updateScroll() {
    if (this.get('isDestroying') || this.get('isDestroyed')) return;

    const $scrollable = this.get('$scrollable');
    const parHeight   = $scrollable.innerHeight();

    this
      .get('categorySections')
      .forEach(section => {
        const id = section.category.get('id');
        const $category = this.$(
          `.eeo-emojiPicker-category._${id} .eeo-emojiPicker-category-emoji`
        );

        if (!$category.length) {
          section.set('style', htmlSafe(''));
          return;
        }

        const catTop    = $category.position().top;
        const catHeight = $category.outerHeight();
        const catBottom = catTop + catHeight;
        const left      = (-catTop) / catHeight;
        const right     = (catBottom - parHeight) / catHeight;
        const style     = htmlSafe(`left: ${left  * 100}%; right: ${right * 100}%;`);

        section.setProperties({style});
      });
  },

  _getEmojiForCategoryId(categoryId) {
    const currentSkinTone = this.get('emojiService.currentSkinTone');
    const emojiPropName = `emojiService.${categoryId}__tone_${currentSkinTone}`;
    return this.get(emojiPropName);
  },

  _getFilteredEmojiForCategoryId(categoryId) {
    const emojiPropName = `emojiByCategoryIdFiltered.${categoryId}`;
    return this.get(emojiPropName);
  },

  didInsertElement() {
    this._super(...arguments);

    this._updateScroll();

    this
      .$(EMOJI_PICKER_SCROLLABLE_ELEMENT)
      .on('scroll.eeo', () => throttle(this, this._updateScroll, 200, false));

    next(() => this.addClickOutsideListener());
  },

  willDestroyElement() {
    this._super(...arguments);

    this
      .$(EMOJI_PICKER_SCROLLABLE_ELEMENT)
      .off('scroll.eeo');

    this.removeClickOutsideListener();
  },

  clickOutside() {
    const closeAction = this.get('closeAction');
    if (closeAction) this.sendAction('closeAction');
  },

  actions: {
    selectEmojo(emojo) {
      this.sendAction('selectAction', emojo);

      if (this.get('closeAction') && this.get('shouldCloseOnSelect')) {
        this.sendAction('closeAction');
      }
    },

    inputFilteringText(_filterInput) {
      this.setProperties({_filterInput});
    },

    enterPressedInInput() {
      let emojo = null;

      this
        .get('emojiService.categories')
        .find(category => {
          const emoji = this._getFilteredEmojiForCategory(category.get('id'));
          if (emoji.length > 0) {
            emojo = emoji[0];
            return true;
          }
        });

      if (emojo) this.send('selectEmojo', emojo);
    },
  }
});
<|endoftext|>


<commit_before>/**
 * Component definition for the frost-button component
 */
import Ember from 'ember'
const {Component, Logger, ViewUtils, get, isEmpty, typeOf} = Ember
import computed, {readOnly} from 'ember-computed-decorators'
import PropTypeMixin, {PropTypes} from 'ember-prop-types'
import SpreadMixin from 'ember-spread'

import layout from '../templates/components/frost-button'

/**
 * List of valid values to pass into `design` propery
 * @type {Array} valid `design` values
 */
const validDesignClasses = [
  'app-bar',
  'info-bar',
  'tab'
]

/**
 * List of valid values to pass into `size` property
 * @type {Array} valid `size` values
 */
const validSizes = [
  'large',
  'medium',
  'small'
]

export default Component.extend(SpreadMixin, PropTypeMixin, {
  // == Dependencies ==========================================================

  // == Keyword Properties ====================================================

  attributeBindings: [
    'autofocus',
    'disabled',
    'type',
    'tabIndex',
    'title'
  ],

  classNameBindings: [
    'disabled',
    'extraClasses'
  ],

  classNames: [
    'frost-button'
  ],

  layout,

  tagName: 'button',

  // == PropTypes =============================================================

  /**
   * Properties for this component. Options are expected to be (potentially)
   * passed in to the component. State properties are *not* expected to be
   * passed in/overwritten.
   */
  propTypes: {
    // options
    autofocus: PropTypes.bool,
    design: PropTypes.oneOf(validDesignClasses),
    disabled: PropTypes.bool,
    hook: PropTypes.string,
    icon: PropTypes.string,
    pack: PropTypes.string,
    priority: PropTypes.string,
    size: PropTypes.oneOf(validSizes),
    text: PropTypes.string,
    title: PropTypes.string,
    type: PropTypes.string,
    vertical: PropTypes.bool,

    // state

    // keywords
    attributeBindings: PropTypes.arrayOf(PropTypes.string),
    classNameBindings: PropTypes.arrayOf(PropTypes.string),
    classNames: PropTypes.arrayOf(PropTypes.string),
    layout: PropTypes.any,
    tagName: PropTypes.string
  },

  /** @returns {Object} the default values for properties when not provided by consumer */
  getDefaultProps () {
    return {
      // options
      autofocus: false,
      design: '',
      disabled: false,
      icon: '',
      pack: 'frost',
      priority: '',
      size: '',
      text: '',
      title: null,
      type: 'button',
      vertical: false

      // state
    }
  },

  // == Computed Properties ===================================================

  @readOnly
  @computed('icon', 'text')
  /**
   * Determine whether or not button is text only (no icon)
   * @param {String} icon - button icon
   * @param {String} text - button text
   * @returns {Boolean} whether or not button is text only (no icon)
   */
  isTextOnly (icon, text) {
    return !isEmpty(text) && isEmpty(icon)
  },

  @readOnly
  @computed('icon', 'text')
  /**
   * Determine whether or not button is icon only (no text)
   * @param {String} icon - button icon
   * @param {String} text - button text
   * @returns {Boolean} whether or not button is icon only (no text)
   */
  isIconOnly (icon, text) {
    return !isEmpty(icon) && isEmpty(text)
  },

  @readOnly
  @computed('icon', 'text')
  /**
   * Determine whether or not button contains icon and text
   * @param {String} icon - button icon
   * @param {String} text - button text
   * @returns {Boolean} whether or not button contains icon and text
   */
  isIconAndText (icon, text) {
    return !isEmpty(icon) && !isEmpty(text)
  },

  /* eslint-disable complexity */
  @readOnly
  @computed('design', 'icon', 'priority', 'size', 'text', 'vertical')
  /**
   * Get extra classes for buttons based on button's settings
   * @param {String} design - button design
   * @param {String} icon - button icon
   * @param {String} priority - button priority
   * @param {String} size - button size
   * @param {String} text - button text
   * @param {Boolean} vertical - whether or not icon is above text
   * @returns {String} extra classNames
   */
  extraClasses (design, icon, priority, size, text, vertical) {
    const classes = []

    if (validDesignClasses.indexOf(design) !== -1) {
      classes.push(design)

      // design button needs to have either text or icon property present
      if (text === '' && icon === '') {
        Logger.error('Error: The `design` property requires `text` or `icon` property to be specified.')
        return
      }

      // display warning when design property is used together with size and/or priority
      if (priority !== '' || size !== '') {
        Logger.warn('Warning: The `design` property takes precedence over `size` and `priority`.')
      }

      return classes.join(' ')
    }

    if (validSizes.indexOf(size) !== -1) {
      classes.push(size)
    }

    this.addPriorityClass(priority, classes)

    if (vertical) {
      classes.push('vertical')
    }

    return classes.join(' ')
  },
  /* eslint-enable complexity */

  // == Functions =============================================================

  /* eslint-disable complexity */
  /**
   * Add the appropriate class for the given priority to the Array of classes
   * @param {String} priority - the priority to add
   * @param {String[]} classes - the classes to add the priority class to
   */
  addPriorityClass (priority, classes) {
    switch (priority) {
      case 'confirm': // fallthrough
      case 'primary':
        classes.push('primary')
        break
      case 'normal': // fallthrough
      case 'secondary':
        classes.push('secondary')
        break
      case 'cancel': // fallthrough
      case 'tertiary':
        classes.push('tertiary')
        break
      default:
        // no class to add for invalid priority
        break
    }
  },
  /* eslint-enable complexity */

  // == DOM Events ============================================================

  // FIXME: jsdoc
  onclick: Ember.on('click', function (event) {
    if (!ViewUtils.isSimpleClick(event)) {
      return true
    }

    const onClick = this.get('onClick')
    if (typeOf(onClick) === 'function' && !get(this, 'disabled')) {
      onClick(get(this, 'id'))
    }
  }),

  // FIXME: jsdoc
  _onFocus: Ember.on('focusIn', function (e) {
    // If an onFocus handler is defined, call it
    if (this.attrs.onFocus) {
      this.attrs.onFocus()
    }
  }),

  // == Lifecycle Hooks =======================================================

  // == Actions ===============================================================

  actions: {}
})
<commit_msg>swap get(this,) to this.get<commit_after>/**
 * Component definition for the frost-button component
 */
import Ember from 'ember'
const {Component, Logger, ViewUtils, isEmpty, typeOf} = Ember
import computed, {readOnly} from 'ember-computed-decorators'
import PropTypeMixin, {PropTypes} from 'ember-prop-types'
import SpreadMixin from 'ember-spread'

import layout from '../templates/components/frost-button'

/**
 * List of valid values to pass into `design` propery
 * @type {Array} valid `design` values
 */
const validDesignClasses = [
  'app-bar',
  'info-bar',
  'tab'
]

/**
 * List of valid values to pass into `size` property
 * @type {Array} valid `size` values
 */
const validSizes = [
  'large',
  'medium',
  'small'
]

export default Component.extend(SpreadMixin, PropTypeMixin, {
  // == Dependencies ==========================================================

  // == Keyword Properties ====================================================

  attributeBindings: [
    'autofocus',
    'disabled',
    'type',
    'tabIndex',
    'title'
  ],

  classNameBindings: [
    'disabled',
    'extraClasses'
  ],

  classNames: [
    'frost-button'
  ],

  layout,

  tagName: 'button',

  // == PropTypes =============================================================

  /**
   * Properties for this component. Options are expected to be (potentially)
   * passed in to the component. State properties are *not* expected to be
   * passed in/overwritten.
   */
  propTypes: {
    // options
    autofocus: PropTypes.bool,
    design: PropTypes.oneOf(validDesignClasses),
    disabled: PropTypes.bool,
    hook: PropTypes.string,
    icon: PropTypes.string,
    pack: PropTypes.string,
    priority: PropTypes.string,
    size: PropTypes.oneOf(validSizes),
    text: PropTypes.string,
    title: PropTypes.string,
    type: PropTypes.string,
    vertical: PropTypes.bool,

    // state

    // keywords
    attributeBindings: PropTypes.arrayOf(PropTypes.string),
    classNameBindings: PropTypes.arrayOf(PropTypes.string),
    classNames: PropTypes.arrayOf(PropTypes.string),
    layout: PropTypes.any,
    tagName: PropTypes.string
  },

  /** @returns {Object} the default values for properties when not provided by consumer */
  getDefaultProps () {
    return {
      // options
      autofocus: false,
      design: '',
      disabled: false,
      icon: '',
      pack: 'frost',
      priority: '',
      size: '',
      text: '',
      title: null,
      type: 'button',
      vertical: false

      // state
    }
  },

  // == Computed Properties ===================================================

  @readOnly
  @computed('icon', 'text')
  /**
   * Determine whether or not button is text only (no icon)
   * @param {String} icon - button icon
   * @param {String} text - button text
   * @returns {Boolean} whether or not button is text only (no icon)
   */
  isTextOnly (icon, text) {
    return !isEmpty(text) && isEmpty(icon)
  },

  @readOnly
  @computed('icon', 'text')
  /**
   * Determine whether or not button is icon only (no text)
   * @param {String} icon - button icon
   * @param {String} text - button text
   * @returns {Boolean} whether or not button is icon only (no text)
   */
  isIconOnly (icon, text) {
    return !isEmpty(icon) && isEmpty(text)
  },

  @readOnly
  @computed('icon', 'text')
  /**
   * Determine whether or not button contains icon and text
   * @param {String} icon - button icon
   * @param {String} text - button text
   * @returns {Boolean} whether or not button contains icon and text
   */
  isIconAndText (icon, text) {
    return !isEmpty(icon) && !isEmpty(text)
  },

  /* eslint-disable complexity */
  @readOnly
  @computed('design', 'icon', 'priority', 'size', 'text', 'vertical')
  /**
   * Get extra classes for buttons based on button's settings
   * @param {String} design - button design
   * @param {String} icon - button icon
   * @param {String} priority - button priority
   * @param {String} size - button size
   * @param {String} text - button text
   * @param {Boolean} vertical - whether or not icon is above text
   * @returns {String} extra classNames
   */
  extraClasses (design, icon, priority, size, text, vertical) {
    const classes = []

    if (validDesignClasses.indexOf(design) !== -1) {
      classes.push(design)

      // design button needs to have either text or icon property present
      if (text === '' && icon === '') {
        Logger.error('Error: The `design` property requires `text` or `icon` property to be specified.')
        return
      }

      // display warning when design property is used together with size and/or priority
      if (priority !== '' || size !== '') {
        Logger.warn('Warning: The `design` property takes precedence over `size` and `priority`.')
      }

      return classes.join(' ')
    }

    if (validSizes.indexOf(size) !== -1) {
      classes.push(size)
    }

    this.addPriorityClass(priority, classes)

    if (vertical) {
      classes.push('vertical')
    }

    return classes.join(' ')
  },
  /* eslint-enable complexity */

  // == Functions =============================================================

  /* eslint-disable complexity */
  /**
   * Add the appropriate class for the given priority to the Array of classes
   * @param {String} priority - the priority to add
   * @param {String[]} classes - the classes to add the priority class to
   */
  addPriorityClass (priority, classes) {
    switch (priority) {
      case 'confirm': // fallthrough
      case 'primary':
        classes.push('primary')
        break
      case 'normal': // fallthrough
      case 'secondary':
        classes.push('secondary')
        break
      case 'cancel': // fallthrough
      case 'tertiary':
        classes.push('tertiary')
        break
      default:
        // no class to add for invalid priority
        break
    }
  },
  /* eslint-enable complexity */

  // == DOM Events ============================================================

  // FIXME: jsdoc
  onclick: Ember.on('click', function (event) {
    if (!ViewUtils.isSimpleClick(event)) {
      return true
    }

    const onClick = this.get('onClick')
    if (typeOf(onClick) === 'function' && !this.get('disabled')) {
      onClick(this.get('id'))
    }
  }),

  // FIXME: jsdoc
  _onFocus: Ember.on('focusIn', function (e) {
    // If an onFocus handler is defined, call it
    if (this.attrs.onFocus) {
      this.attrs.onFocus()
    }
  }),

  // == Lifecycle Hooks =======================================================

  // == Actions ===============================================================

  actions: {}
})
<|endoftext|>


<commit_before>import Ember from 'ember';
import EventsMixin from '../mixins/events-mixin';
import BaseFocusable from './base-focusable';

import FlexMixin from '../mixins/flex-mixin';
import constants from '../utils/constants';

export default BaseFocusable.extend(EventsMixin, FlexMixin, {

  tagName: 'md-slider',

  attributeBindings: ['min', 'max', 'step', 'discrete:md-discrete', 'tabindex'],

  classNames: ['md-default-theme'],
  classNameBindings: ['isMinimum:md-min', 'active', 'dragging'],

  min: 0,
  max: 100,
  step: 1,
  tabindex: 0,

  trackContainer: Ember.computed(function() {
    var element = this.$()[0];

    return this.$(element.querySelector('.md-track-container'));
  }),

  activeTrackStyle: Ember.computed('percent', function() {
    var percent = this.get('percent') || 0;
    return Ember.String.htmlSafe("width: " + (percent * 100) + "%");
  }),

  thumbContainerStyle: Ember.computed('percent', function() {
    var percent = this.get('percent') || 0;
    return Ember.String.htmlSafe("left: " + (percent * 100) + "%");
  }),

  isMinimum: Ember.computed('percent', 'min', function() {
    return this.get('percent') === this.get('min');
  }),

  percent: Ember.computed('value', 'min', 'max', function() {
    var min = parseInt(this.get('min'), 10);
    var max = parseInt(this.get('max'), 10);

    return (this.get('value') - min) / (max - min);
  }),

  positionToPercent(x) {
    return Math.max(0, Math.min(1, (x - this.get('sliderDimensions.left')) / this.get('sliderDimensions.width')));
  },

  percentToValue(x) {
    var min = parseInt(this.get('min'), 10);
    var max = parseInt(this.get('max'), 10);
    return (min + x * (max - min));
  },

  minMaxValidator(value) {
    var min = parseInt(this.get('min'), 10);
    var max = parseInt(this.get('max'), 10);
    return Math.max(min, Math.min(max, value));
  },

  stepValidator(value) {
    var step = parseInt(this.get('step'), 10);
    return Math.round(value / step) * step;
  },

  active: false,
  dragging: false,

  sliderDimensions: Ember.computed(function() {
    return this.get('trackContainer')[0].getBoundingClientRect();
  }),

  setValueFromEvent(event) {
    //var exactVal = this.percentToValue(this.positionToPercent(event.deltaX || event.clientX));
    var exactVal = this.percentToValue(this.positionToPercent(event.clientX || event.originalEvent.touches[0].clientX));
    var closestVal = this.minMaxValidator(this.stepValidator(exactVal));

    this.set('value', closestVal);
  },

  down(event) {
    if (this.get('disabled')) {
      return;
    }

    this.set('active', true);
    this.set('dragging', true);
    this.$().focus();

    this.get('sliderDimensions');

    this.setValueFromEvent(event);
  },

  up(event) {
    if (this.get('disabled')) {
      return;
    }

    event.stopPropagation();

    this.beginPropertyChanges();
    this.set('active', false);
    this.set('dragging', false);
    this.endPropertyChanges();
  },

  move(event) {
    if (this.get('disabled') || !this.get('dragging')) {
      return;
    }

    this.setValueFromEvent(event);

  },

  keyDown(event) {
    if (this.get('disabled')) {
      return;
    }

    var changeAmount;

    if (event.keyCode === constants.KEYCODE.LEFT_ARROW) {
      changeAmount = this.get('step') * -1;
    } else if (event.keyCode === constants.KEYCODE.RIGHT_ARROW) {
      changeAmount = this.get('step');
    }

    if (changeAmount) {
      if (event.metaKey || event.ctrlKey || event.altKey) {
        changeAmount *= 4;
      }

      this.incrementProperty('value', changeAmount);

      event.preventDefault();
      event.stopPropagation();
    }
  }

});
<commit_msg>Added bounds and type validation to keyboard input handler<commit_after>import Ember from 'ember';
import EventsMixin from '../mixins/events-mixin';
import BaseFocusable from './base-focusable';

import FlexMixin from '../mixins/flex-mixin';
import constants from '../utils/constants';

export default BaseFocusable.extend(EventsMixin, FlexMixin, {

  tagName: 'md-slider',

  attributeBindings: ['min', 'max', 'step', 'discrete:md-discrete', 'tabindex'],

  classNames: ['md-default-theme'],
  classNameBindings: ['isMinimum:md-min', 'active', 'dragging'],

  min: 0,
  max: 100,
  step: 1,
  tabindex: 0,

  trackContainer: Ember.computed(function() {
    var element = this.$()[0];

    return this.$(element.querySelector('.md-track-container'));
  }),

  activeTrackStyle: Ember.computed('percent', function() {
    var percent = this.get('percent') || 0;
    return Ember.String.htmlSafe("width: " + (percent * 100) + "%");
  }),

  thumbContainerStyle: Ember.computed('percent', function() {
    var percent = this.get('percent') || 0;
    return Ember.String.htmlSafe("left: " + (percent * 100) + "%");
  }),

  isMinimum: Ember.computed('percent', 'min', function() {
    return this.get('percent') === this.get('min');
  }),

  percent: Ember.computed('value', 'min', 'max', function() {
    var min = parseInt(this.get('min'), 10);
    var max = parseInt(this.get('max'), 10);

    return (this.get('value') - min) / (max - min);
  }),

  positionToPercent(x) {
    return Math.max(0, Math.min(1, (x - this.get('sliderDimensions.left')) / this.get('sliderDimensions.width')));
  },

  percentToValue(x) {
    var min = parseInt(this.get('min'), 10);
    var max = parseInt(this.get('max'), 10);
    return (min + x * (max - min));
  },

  minMaxValidator(value) {
    var min = parseInt(this.get('min'), 10);
    var max = parseInt(this.get('max'), 10);
    return Math.max(min, Math.min(max, value));
  },

  stepValidator(value) {
    var step = parseInt(this.get('step'), 10);
    return Math.round(value / step) * step;
  },

  active: false,
  dragging: false,

  sliderDimensions: Ember.computed(function() {
    return this.get('trackContainer')[0].getBoundingClientRect();
  }),

  setValueFromEvent(event) {
    //var exactVal = this.percentToValue(this.positionToPercent(event.deltaX || event.clientX));
    var exactVal = this.percentToValue(this.positionToPercent(event.clientX || event.originalEvent.touches[0].clientX));
    var closestVal = this.minMaxValidator(this.stepValidator(exactVal));

    this.set('value', closestVal);
  },

  down(event) {
    if (this.get('disabled')) {
      return;
    }

    this.set('active', true);
    this.set('dragging', true);
    this.$().focus();

    this.get('sliderDimensions');

    this.setValueFromEvent(event);
  },

  up(event) {
    if (this.get('disabled')) {
      return;
    }

    event.stopPropagation();

    this.beginPropertyChanges();
    this.set('active', false);
    this.set('dragging', false);
    this.endPropertyChanges();
  },

  move(event) {
    if (this.get('disabled') || !this.get('dragging')) {
      return;
    }

    this.setValueFromEvent(event);

  },

  keyDown(event) {
    if (this.get('disabled')) {
      return;
    }

    var changeAmount, newValue;

    if (event.keyCode === constants.KEYCODE.LEFT_ARROW) {
      changeAmount = parseInt(this.get('step')) * -1;
    } else if (event.keyCode === constants.KEYCODE.RIGHT_ARROW) {
      changeAmount = parseInt(this.get('step'));
    }

    if (changeAmount) {
      if (event.metaKey || event.ctrlKey || event.altKey) {
        changeAmount *= 4;
      }

      newValue = this.get('value') + changeAmount;

      this.set('value', this.minMaxValidator(newValue));

      event.preventDefault();
      event.stopPropagation();
    }
  }

});
<|endoftext|>


<commit_before>'use strict';
var React = require('react/addons');
var d3 = require('d3');
var _ = require('lodash');
var $ = require('jquery');

var LineChart = module.exports = React.createClass({
  chart: null,

  onWindowResize: function() {
    this.chart.update();
  },

  componentDidMount: function() {
    console.log('LineChart componentDidMount');
    // Debounce event.
    this.onWindowResize = _.debounce(this.onWindowResize, 200);

    window.addEventListener('resize', this.onWindowResize);
    this.chart = new d3LineChart(this.getDOMNode(), this.props);
  },

  componentWillUnmount: function() {
    console.log('LineChart componentWillUnmount');
    window.removeEventListener('resize', this.onWindowResize);
    this.chart.destroy();
  },

  componentDidUpdate: function(/*prevProps, prevState*/) {
    console.log('LineChart componentDidUpdate');
    this.chart.setData(this.props);
  },

  render: function() {
    return (

    );
  }
});

var d3LineChart = function(el, data) {
  this.$el = d3.select(el);

  this.data = null;
  this.xData = null;
  this.yData = null;

  // Var declaration.
  var margin = {top: 30, right: 32, bottom: 50, left: 50};
  // width and height refer to the data canvas. To know the svg size the margins
  // must be added.
  var _width, _height;
  // Scales, Axis, line and area functions.
  var x, y, xAxis, yAxis, line, area;
  // Elements.
  var svg, dataCanvas, tooltip;

  var parseDate = d3.time.format("%Y-%m-%d").parse;

  this._calcSize = function() {
    _width = parseInt(this.$el.style('width'), 10) - margin.left - margin.right;
    _height = parseInt(this.$el.style('height'), 10) - margin.top - margin.bottom;
    console.log('_calcSize', _width, 'w');
    console.log('_calcSize', _height, 'h');
  };

  this.setData = function(data) {
    data.data.forEach(function(d) {
      d.date = parseDate(d.date);
    });
    console.log('data', data);
    this.data = data.data;
    this.xData = data.x;
    this.yData = data.y;
    this.update();
  };

  this._init = function() {
    this._calcSize();
    // The svg
    svg = this.$el.append('svg')
        .attr('class', 'chart');
    // X scale. Range updated in function.
    x = d3.time.scale();

    // Y scale. Range updated in function.
    y = d3.scale.linear();
    // Define xAxis function.
    xAxis = d3.svg.axis()
      .scale(x)
      .ticks(6)
      .orient("bottom");

    // Define yAxis function.
    yAxis = d3.svg.axis()
      .scale(y)
      .orient("left");

    // Line function.
    line = d3.svg.line()
      .x(function(d) { return x(d.date); })
      .y(function(d) { return y(d.value); });

    // Area function.
    area = d3.svg.area()
      .x(function(d) { return x(d.date); })
      .y0(function(d) { return y(d.value); })
      .y1(function(d) { return _height; });

    // Chart elements
    dataCanvas = svg.append("g")
        .attr('class', 'data-canvas')
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    svg.append("g")
      .attr("class", "x axis")
      .append("text")
      .attr("class", "label")
      .attr("text-anchor", "end");

    svg.append("g")
      .attr("class", "y axis")
      .append("text")
      .attr("class", "label")
      .attr("text-anchor", "middle");

    dataCanvas.append("path")
      .attr("class", "area");

    dataCanvas.append("path")
      .attr("class", "line");

    dataCanvas.append("g")
      .attr("class", "focus-circles");
  };

  this.update = function() {
    this._calcSize();

    x.range([0, _width])
      .domain(d3.extent(this.data, function(d) { return d.date; }));
      //.domain(this.data.map(function(d) { return d.date; }));

    y.range([_height, 0])
      .domain(this.yData.domain);

    svg
      .attr('width', _width + margin.left + margin.right)
      .attr('height', _height + margin.top + margin.bottom);

    dataCanvas
      .attr('width', _width)
      .attr('height', _height);

    var pathLine = dataCanvas.select(".line")
      .datum(this.data)
      .attr("d", line);

    var pathArea = dataCanvas.select(".area")
      .datum(this.data)
      .attr("d", area);

    var focusCirlces = dataCanvas.select(".focus-circles").selectAll('.focus-circle')
      .data(this.data);

    focusCirlces.enter()
      .append('circle')
      .attr('class', 'focus-circle')
      .attr('cx', function(d) { return x(d.date);})
      .attr('cy', function(d) { return y(d.value);})
      .style('opacity', 0)
      .attr('r', 6);

    focusCirlces
      .attr('cx', function(d) { return x(d.date);})
      .attr('cy', function(d) { return y(d.value);})
      .style('opacity', 0)
      .attr('r', 6);

    focusCirlces.exit()
      .remove();

    focusCirlces
      .on("mouseover", function(d) {
        var $this = $(this);
        var cr = d3.select(this);
        cr.transition().attr('r', 8)
      .style('opacity', 1);

        var matrix = this.getScreenCTM()
          .translate(this.getAttribute("cx"), this.getAttribute("cy"));

        var posX = window.pageXOffset + matrix.e;
        var posY =  window.pageYOffset + matrix.f;

        tooltip = $(React.renderToStaticMarkup(
          <Tooltip>
            Value: {d.value}

            Date: {d.date.toString()}

          </Tooltip>
        ));

        // Set position on next tick.
        // Otherwise the tooltip has no spatiality.
        setTimeout(function() {
          var containerW = $('#site-canvas').outerWidth();
          var sizeW = tooltip.outerWidth();
          var sizeH = tooltip.outerHeight();

          var leftOffset = posX  - sizeW / 2;
          var topOffset = posY - sizeH - 8;

          // If the tooltip would be to appear outside the window on the right
          // move it to the left by that amount.
          // And add some padding.
          var overflowR = (leftOffset + sizeW) - containerW ;
          if (overflowR > 0) {
            leftOffset -= overflowR + 16;
          }

          // Same for the left side.
          if (leftOffset < 0) {
            leftOffset = 16;
          }

          tooltip
          .css('left', leftOffset + 'px')
          .css('top', topOffset + 'px');
        }, 1);

        $('#site-canvas').append(tooltip);

      })
      .on("mouseout", function() {
        d3.select(this).transition().attr('r', 6)
          .style('opacity', 0 );
        tooltip.remove();
      });

    // Append Axis.
    svg.select(".x.axis")
      .attr("transform", "translate(" + margin.left + "," + (_height + 32) + ")").transition()
      .call(xAxis);

    if (this.xData && this.xData.label) {
      svg.select(".x.axis .label")
        .text(this.xData.label)
        .transition()
        .attr("x", _width + margin.right)
        .attr("y", 30);
    }

    svg.select(".y.axis")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")").transition()
      .call(yAxis);

    if (this.yData && this.yData.label) {
      svg.select(".y.axis .label")
        .text(this.yData.label)
        .transition()
        .attr("x", 0)
        .attr("y", -15);
    }

    console.log(this.$el);
  };

  this.destroy = function() {

  };

  //--------------------------------------------------------------------------//
  // 3... 2... 1... GO...

  this._init();
  this.setData(data);
};

var Tooltip = React.createClass({
  render: function() {
    return (

        {this.props.children}

    );
  }
});
<commit_msg>Fix data problem<commit_after>'use strict';
var React = require('react/addons');
var d3 = require('d3');
var _ = require('lodash');
var $ = require('jquery');

var LineChart = module.exports = React.createClass({
  chart: null,

  onWindowResize: function() {
    this.chart.update();
  },

  componentDidMount: function() {
    console.log('LineChart componentDidMount');
    // Debounce event.
    this.onWindowResize = _.debounce(this.onWindowResize, 200);

    window.addEventListener('resize', this.onWindowResize);
    this.chart = new d3LineChart(this.getDOMNode(), this.props);
  },

  componentWillUnmount: function() {
    console.log('LineChart componentWillUnmount');
    window.removeEventListener('resize', this.onWindowResize);
    this.chart.destroy();
  },

  componentDidUpdate: function(/*prevProps, prevState*/) {
    console.log('LineChart componentDidUpdate');
    this.chart.setData(this.props);
  },

  render: function() {
    return (

    );
  }
});

var d3LineChart = function(el, data) {
  this.$el = d3.select(el);

  this.data = null;
  this.xData = null;
  this.yData = null;

  // Var declaration.
  var margin = {top: 30, right: 32, bottom: 50, left: 50};
  // width and height refer to the data canvas. To know the svg size the margins
  // must be added.
  var _width, _height;
  // Scales, Axis, line and area functions.
  var x, y, xAxis, yAxis, line, area;
  // Elements.
  var svg, dataCanvas, tooltip;

  var parseDate = d3.time.format("%Y-%m-%d").parse;

  this._calcSize = function() {
    _width = parseInt(this.$el.style('width'), 10) - margin.left - margin.right;
    _height = parseInt(this.$el.style('height'), 10) - margin.top - margin.bottom;
  };

  this.setData = function(data) {
    var _data = _.cloneDeep(data);
    this.data = _data.data;
    this.xData = _data.x;
    this.yData = _data.y;

    this.data.forEach(function(d) {
      d.date = parseDate(d.date);
    });

    this.update();
  };

  this._init = function() {
    this._calcSize();

    // The svg.
    svg = this.$el.append('svg')
        .attr('class', 'chart');

    // X scale. Range updated in function.
    x = d3.time.scale();

    // Y scale. Range updated in function.
    y = d3.scale.linear();

    // Define xAxis function.
    xAxis = d3.svg.axis()
      .scale(x)
      .ticks(6)
      .orient("bottom");

    // Define yAxis function.
    yAxis = d3.svg.axis()
      .scale(y)
      .orient("left");

    // Line function.
    line = d3.svg.line()
      .x(function(d) { return x(d.date); })
      .y(function(d) { return y(d.value); });

    // Area function.
    area = d3.svg.area()
      .x(function(d) { return x(d.date); })
      .y0(function(d) { return y(d.value); })
      .y1(function(d) { return _height; });

    // Chart elements.
    dataCanvas = svg.append("g")
        .attr('class', 'data-canvas')
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    svg.append("g")
      .attr("class", "x axis")
      .append("text")
      .attr("class", "label")
      .attr("text-anchor", "end");

    svg.append("g")
      .attr("class", "y axis")
      .append("text")
      .attr("class", "label")
      .attr("text-anchor", "middle");

    dataCanvas.append("path")
      .attr("class", "area");

    dataCanvas.append("path")
      .attr("class", "line");

    dataCanvas.append("g")
      .attr("class", "focus-circles");
  };

  this.update = function() {
    this._calcSize();

    x.range([0, _width])
      .domain(d3.extent(this.data, function(d) { return d.date; }));
      //.domain(this.data.map(function(d) { return d.date; }));

    y.range([_height, 0])
      .domain(this.yData.domain);

    svg
      .attr('width', _width + margin.left + margin.right)
      .attr('height', _height + margin.top + margin.bottom);

    dataCanvas
      .attr('width', _width)
      .attr('height', _height);

    var pathLine = dataCanvas.select(".line")
      .datum(this.data)
      .attr("d", line);

    var pathArea = dataCanvas.select(".area")
      .datum(this.data)
      .attr("d", area);

    var focusCirlces = dataCanvas.select(".focus-circles").selectAll('.focus-circle')
      .data(this.data);

    focusCirlces.enter()
      .append('circle')
      .attr('class', 'focus-circle')
      .attr('cx', function(d) { return x(d.date);})
      .attr('cy', function(d) { return y(d.value);})
      .style('opacity', 0)
      .attr('r', 6);

    focusCirlces
      .attr('cx', function(d) { return x(d.date);})
      .attr('cy', function(d) { return y(d.value);})
      .style('opacity', 0)
      .attr('r', 6);

    focusCirlces.exit()
      .remove();

    focusCirlces
      .on("mouseover", function(d) {
        var $this = $(this);
        var cr = d3.select(this);
        cr.transition().attr('r', 8)
      .style('opacity', 1);

        var matrix = this.getScreenCTM()
          .translate(this.getAttribute("cx"), this.getAttribute("cy"));

        var posX = window.pageXOffset + matrix.e;
        var posY =  window.pageYOffset + matrix.f;

        tooltip = $(React.renderToStaticMarkup(
          <Tooltip>
            Value: {d.value}

            Date: {d.date.toString()}

          </Tooltip>
        ));

        // Set position on next tick.
        // Otherwise the tooltip has no spatiality.
        setTimeout(function() {
          var containerW = $('#site-canvas').outerWidth();
          var sizeW = tooltip.outerWidth();
          var sizeH = tooltip.outerHeight();

          var leftOffset = posX  - sizeW / 2;
          var topOffset = posY - sizeH - 8;

          // If the tooltip would be to appear outside the window on the right
          // move it to the left by that amount.
          // And add some padding.
          var overflowR = (leftOffset + sizeW) - containerW ;
          if (overflowR > 0) {
            leftOffset -= overflowR + 16;
          }

          // Same for the left side.
          if (leftOffset < 0) {
            leftOffset = 16;
          }

          tooltip
          .css('left', leftOffset + 'px')
          .css('top', topOffset + 'px');
        }, 1);

        $('#site-canvas').append(tooltip);

      })
      .on("mouseout", function() {
        d3.select(this).transition().attr('r', 6)
          .style('opacity', 0 );
        tooltip.remove();
      });

    // Append Axis.
    svg.select(".x.axis")
      .attr("transform", "translate(" + margin.left + "," + (_height + 32) + ")").transition()
      .call(xAxis);

    if (this.xData && this.xData.label) {
      svg.select(".x.axis .label")
        .text(this.xData.label)
        .transition()
        .attr("x", _width + margin.right)
        .attr("y", 30);
    }

    svg.select(".y.axis")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")").transition()
      .call(yAxis);

    if (this.yData && this.yData.label) {
      svg.select(".y.axis .label")
        .text(this.yData.label)
        .transition()
        .attr("x", 0)
        .attr("y", -15);
    }

  };

  this.destroy = function() {

  };

  //--------------------------------------------------------------------------//
  // 3... 2... 1... GO...

  this._init();
  this.setData(data);
};

var Tooltip = React.createClass({
  render: function() {
    return (

        {this.props.children}

    );
  }
});
<|endoftext|>