<commit_before>// Copyright 2015-2017, University of Colorado Boulder

/**
 * Model for a momentary button: on when pressed, off when released.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ButtonModel = require( 'SUN/buttons/ButtonModel' );
  var Emitter = require( 'AXON/Emitter' );
  var inherit = require( 'PHET_CORE/inherit' );
  var PhetioObject = require( 'TANDEM/PhetioObject' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );

  /**
   * @param {Object} valueOff - value when the button is in the off state
   * @param {Object} valueOn - value when the button is in the on state
   * @param {Property} valueProperty
   * @param {Object} [options]
   * @constructor
   */
  function MomentaryButtonModel( valueOff, valueOn, valueProperty, options ) {
    var self = this;

    // Tandem support
    options = _.extend( {
      tandem: Tandem.required,
      phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60
    }, options );

    ButtonModel.call( self, options );

    // @private
    this.pressedEmitter = new Emitter( {
      tandem: options.tandem.createTandem( 'pressedEmitter' ),
      phetioDocumentation: 'Emits when the button is pressed',
      phetioReadOnly: options.phetioReadOnly
    } );

    // @private
    this.releasedEmitter = new Emitter( {
      tandem: options.tandem.createTandem( 'releasedEmitter' ),
      phetioDocumentation: 'Emits when the button is released',
      phetioReadOnly: options.phetioReadOnly
    } );

    // add listeners
    var setValueOn = function() {
      valueProperty.set( valueOn );
    };
    this.pressedEmitter.addListener( setValueOn );

    var setValueOff = function() {
      valueProperty.set( valueOff );
    };
    this.releasedEmitter.addListener( setValueOff );

    var downListener = function( down ) {

      // turn on when pressed (if enabled)
      if ( down ) {
        if ( self.enabledProperty.get() ) {
          self.pressedEmitter.emit();
        }
      }
      else {
        self.releasedEmitter.emit();
      }
    };
    this.downProperty.lazyLink( downListener );

    // @private: just for dispose.  Named based on the type name so it won't have a name collision with parent/child ones
    this.disposeMomentaryButtonModel = function() {
      self.downProperty.unlink( downListener );
      this.pressedEmitter.removeListener( setValueOn );
      this.releasedEmitter.removeListener( setValueOff );
    };
  }

  sun.register( 'MomentaryButtonModel', MomentaryButtonModel );

  return inherit( ButtonModel, MomentaryButtonModel, {

    // @public - Ensures that this model is eligible for GC.
    dispose: function() {
      this.disposeMomentaryButtonModel();
      ButtonModel.prototype.dispose.call( this ); //TODO fails with assertions enabled, see sun#212
    }
  } );
} );<commit_msg>Removed redundant Emitters, see https://github.com/phetsims/sun/issues/396<commit_after>// Copyright 2015-2017, University of Colorado Boulder

/**
 * Model for a momentary button: on when pressed, off when released.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ButtonModel = require( 'SUN/buttons/ButtonModel' );
  var inherit = require( 'PHET_CORE/inherit' );
  var PhetioObject = require( 'TANDEM/PhetioObject' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );

  /**
   * @param {Object} valueOff - value when the button is in the off state
   * @param {Object} valueOn - value when the button is in the on state
   * @param {Property} valueProperty
   * @param {Object} [options]
   * @constructor
   */
  function MomentaryButtonModel( valueOff, valueOn, valueProperty, options ) {
    var self = this;

    // Tandem support
    options = _.extend( {
      tandem: Tandem.required,
      phetioReadOnly: PhetioObject.DEFAULT_OPTIONS.phetioReadOnly // to support properly passing this to children, see https://github.com/phetsims/tandem/issues/60
    }, options );

    ButtonModel.call( self, options );

    var downListener = function( down ) {

      // turn on when pressed (if enabled)
      if ( down ) {
        if ( self.enabledProperty.get() ) {
          valueProperty.set( valueOn );
        }
      }
      else {
        valueProperty.set( valueOff );
      }
    };
    this.downProperty.lazyLink( downListener );

    // @private: just for dispose.  Named based on the type name so it won't have a name collision with parent/child ones
    this.disposeMomentaryButtonModel = function() {
      self.downProperty.unlink( downListener );
    };
  }

  sun.register( 'MomentaryButtonModel', MomentaryButtonModel );

  return inherit( ButtonModel, MomentaryButtonModel, {

    // @public - Ensures that this model is eligible for GC.
    dispose: function() {
      this.disposeMomentaryButtonModel();
      ButtonModel.prototype.dispose.call( this ); //TODO fails with assertions enabled, see sun#212
    }
  } );
} );<|endoftext|>


<commit_before>function TimelineController(timelineDataService, $location, $element, $scope) {

    var _this = this;

    // These variables should be constant variables. Set to var because of an error with safari.
    var margin = 15;
    var maxZoomYears = 5;
    var minStartYear = -10000;
    var maxStartYear = new Date().getFullYear();
    var barHeight = 20;
    var buttonZoomFactor = 0.5;
    // -------

    var timeline;
    var canvas;
    var axis, axisElement;
    var bars, barPaths, barTexts;
    var tooltip;
    var zoom, drag;
    var x, y;

    var timelineData;

    var totalXDomain = [];
    var startXDomain = [];
    var startYDomain = [];

    var width;
    var height;

    var initialized = false;

    var hoverPeriod;

    d3.selection.prototype.moveToFront = function() {

        return this.each(function() {
            this.parentNode.appendChild(this);
        });
    };

    this.$onChanges = function() {

        if (this.periods) this.initialize();
    };

    this.initialize = function() {

        width = this.getWidth();
        height = this.getHeight();

        y = d3.scale.linear()
            .domain([0, barHeight * 20])
            .range([0, parseInt(height) - 30]);

        timelineData = timelineDataService.getTimelineData(this.periods);
        totalXDomain = timelineData.xDomain;

        this.setStartDomains(timelineData.periodsMap);

        x = d3.scale.linear()
            .domain(startXDomain)
            .range([0, parseInt(width)]);

        y.domain(startYDomain);

        timeline = d3.select('#timeline').append('svg')
            .attr('width', parseInt(width))
            .attr('height', parseInt(height))
            .classed('timeline', true);

        if (this.inactive) timeline.classed('inactive', true);

        canvas = timeline.append('svg')
            .attr('width', parseInt(width))
            .attr('height', parseInt(height) - 30);

        axis = d3.svg.axis()
            .scale(x)
            .orient('bottom')
            .ticks(parseInt(this.axisTicks))
            .tickSize(10, 0);

        axisElement = timeline.append('svg')
            .attr('y', parseInt(height) - 30)
            .attr('width', parseInt(width))
            .classed('axis', true)
            .call(axis);

        if (!this.inactive) {
            var minZoom = (startXDomain[1] - startXDomain[0])
                / (totalXDomain[1] - totalXDomain[0]);
            var maxZoom = (startXDomain[1] - startXDomain[0]) / maxZoomYears;

            zoom = d3.behavior.zoom()
                .x(x)
                .scaleExtent([minZoom, maxZoom])
                .on('zoom', function () {
                    axisElement.call(axis);
                    _this.updateBars();
                });

            drag = d3.behavior.drag()
                .on('drag', function () {
                    var domain = y.domain();
                    domain[0] -= d3.event.dy;
                    domain[1] -= d3.event.dy;
                    y.domain(domain);
                    axisElement.call(axis);
                    _this.updateBars();
                });

            timeline.call(zoom).call(drag);

            tooltip = d3.select('body')
                .append('div')
                .classed('timeline-tooltip', true);
        }

        bars = canvas.selectAll('g').data(timelineData.periods).enter();
        barPaths = bars.append('g')
            .attr('id', function(d) { return 'bar-path-' + d.id; })
            .attr('class', function(d) {
                var barClass = 'bar level' + (d.level + 1);
                if (_this.inactive) {
                    barClass += ' inactive';
                } else {
                    barClass += ' group' + d.colorGroup;
                }
                return barClass;
            })
            .append('path');

        if (!this.inactive) {
            barPaths.on('click', _this.showPeriod);
            this.addHoverBehavior(barPaths);
        }

        if (this.selectedPeriodId) {
            barPaths.filter(function(d) {
                return d.id === _this.selectedPeriodId;
            }).classed('selected', true);
        }

        barTexts = canvas.selectAll('g')
            .append('text')
            .classed('text', true)
            .attr('id', function(d) { return 'bar-text-' + d.id; })
            .on('click', _this.showPeriod);
        if (this.inactive) {
            barTexts.classed('inactive', true);
        } else {
            this.addHoverBehavior(barTexts);
        }

        this.updateBars();

        d3.select(window).on('resize', _this.resize);

        initialized = true;
    };

    this.resize = function() {

        if (!initialized) return;

        var width = _this.getWidth();
        var height = _this.getHeight();

        y.range([0, parseInt(height) - 30]);
        x.range([0, parseInt(width)]);

        timeline.attr('width', parseInt(width))
            .attr('height', parseInt(height));

        canvas.attr('width', parseInt(width))
            .attr('height', parseInt(height) - 30);

        axisElement.attr('y', parseInt(height) - 30);
        axisElement.attr('width', parseInt(width));
        axisElement.call(axis);

        _this.updateBars();
    };

    this.getWidth = function() {

        return $element[0].parentNode.clientWidth - margin;
    };

    this.getHeight = function() {

        return $element[0].parentNode.clientHeight - margin;
    };

    this.updateBars = function() {

        barPaths.attr('d', function(data) { return _this.computeBarPaths(data); });

        barTexts.attr('x', function(data) {
            return x(data.from) + (_this.getBarWidth(data)) / 2;
        })
            .attr('y', function(data) {
                return y(data.row) + data.row * (barHeight + 5) + barHeight / 2 + 5;
            })
            .text(function(data) {
                if (_this.doesTextFitInBar(data.name, _this.getBarWidth(data))) {
                    data.textVisible = true;
                    return data.name;
                } else {
                    data.textVisible = false;
                    return '';
                }
            });

        axisElement.selectAll('.tick text')
            .text(function() { return _this.formatTickText(d3.select(this).text()); });
    };

    this.computeBarPaths = function(data) {

        var topY = _this.getPathYPosition(data);
        var bottomY = topY + barHeight;
        var edgeRadius = Math.min(Math.floor(_this.getBarWidth(data) / 2), 5);

        return _this.computeLeftEndPathDefinition(data, topY, bottomY, edgeRadius) + ' '
            + _this.computeRightEndPathDefinition(data, topY, bottomY, edgeRadius) + 'Z';
    };

    this.computeLeftEndPathDefinition = function(data, topY, bottomY, edgeRadius) {

        if (data.earliestFrom) {
            return 'M' + (x(data.earliestFrom) + 1) + ' ' + bottomY + ' '
                + 'L' + (x(data.from) + 1) + ' ' + topY;
        } else if (edgeRadius > 0) {
            return 'M' + (x(data.from) + edgeRadius) + ' ' + bottomY + ' '
                + 'Q' + x(data.from) + ' ' + bottomY + ' ' + x(data.from) + ' ' + (bottomY - edgeRadius)
                + 'L' + x(data.from) + ' ' + (topY + edgeRadius)
                + 'Q' + x(data.from) + ' ' + topY + ' ' + (x(data.from) + edgeRadius) + ' ' + topY
        } else {
            return 'M' + x(data.from) + ' ' + bottomY + ' '
                + 'L' + x(data.from) + ' ' + topY;
        }
    };

    this.computeRightEndPathDefinition = function(data, topY, bottomY, edgeRadius) {

        if (data.latestTo || edgeRadius === 0) {
            return 'L' + x(data.latestTo || data.to) + ' ' + topY + ' '
                + 'L' + x(data.to) + ' ' + bottomY;
        } else {
            return 'L' + (x(data.to) - edgeRadius) + ' ' + topY + ' '
                + 'Q' + x(data.to) + ' ' + topY + ' ' + x(data.to) + ' ' + (topY + edgeRadius)
                + 'L' + x(data.to) + ' ' + (bottomY - edgeRadius)
                + 'Q' + x(data.to) + ' ' + bottomY + ' ' + (x(data.to) - edgeRadius) + ' ' + bottomY;
        }
    };

    this.getPathYPosition = function(data) {

        return y(data.row) + data.row * (barHeight + 5);
    };

    this.getBarWidth = function(data) {

        return x(data.to) - x(data.from);
    };

    this.doesTextFitInBar = function(text, barWidth) {

        return !(this.getApproximatedTextLength(text) > barWidth);
    };

    this.getApproximatedTextLength = function(text) {

        return text.length * 7;
    };

    this.showPeriod = function(period) {

        tooltip.style('visibility', 'hidden');
        $location.path('/period/' + period.id);
        console.log('show period!');
        $scope.$apply();
    };

    this.formatTickText = function(text) {

        text = text.split('.').join('$');
        text = text.split(',').join('.');
        text = text.split('$').join(',');

        if (text.length < 6 || (text.indexOf('-') > -1 && text.length < 7)) {
            text = text.replace('.', '');
        }

        return text;
    };

    this.addHoverBehavior = function(selection) {

        selection.on('mouseover', function(period) {
            d3.select('#bar-path-' + period.id).classed('hover', true);
            if (period !== hoverPeriod) {
                d3.select('#bar-path-' + period.id).moveToFront();
                d3.select('#bar-text-' + period.id).moveToFront();
                hoverPeriod = period;
            }
            if (period.textVisible) return;
            tooltip.text(period.name);
            return tooltip.style('visibility', 'visible');
        })
            .on('mousemove', function() {
                return tooltip.style('top', (d3.event.pageY - 10) + 'px')
                    .style('left', (d3.event.pageX + 10) + 'px');
            })
            .on('mouseout', function(period) {
                d3.select('#bar-path-' + period.id).classed('hover', false);
                return tooltip.style('visibility', 'hidden');
            });
    };

    this.setStartDomains = function(periodsMap) {

        if (this.selectedPeriodId && periodsMap[this.selectedPeriodId])
            this.setStartDomainsToSelection(periodsMap[this.selectedPeriodId]);
        else
            this.setStandardStartDomains();
    };

    this.setStartDomainsToSelection = function(selectedPeriod) {

        var span = selectedPeriod.to - selectedPeriod.from;
        var offset = (span < maxZoomYears) ? (maxZoomYears - span) / 2 : span / 2;
        var from = selectedPeriod.from - offset;
        var to = selectedPeriod.to + offset;
        if (from < totalXDomain[0]) from = totalXDomain[0];
        if (to > totalXDomain[1]) to = totalXDomain[1];
        startXDomain = [from, to];

        var centralRow = selectedPeriod.row;
        if (centralRow < 5) centralRow = 5;
        var yPos = centralRow + y.invert(centralRow * (barHeight + 5)) - 5;
        startYDomain = [yPos - barHeight * 10, yPos + barHeight * 10];
    };

    this.setStandardStartDomains = function() {

        startXDomain[0] = totalXDomain[0];
        startXDomain[1] = totalXDomain[1];
        if (startXDomain[0] < minStartYear)
            startXDomain[0] = minStartYear;
        if (startXDomain[1] > maxStartYear)
            startXDomain[1] = maxStartYear;

        startYDomain = [0, barHeight * 20];
    };

    this.zoomIn = function(event) {

        event.target.blur();
        this.zoom(true);
    };

    this.zoomOut = function(event) {

        event.target.blur();
        this.zoom(false);
    };

    this.zoom = function(zoomIn) {

        var center = [width / 2, height / 2];
        var view = { x: zoom.translate()[0], y: zoom.translate()[1], k: zoom.scale() };

        var targetZoom = zoom.scale() * (1 + buttonZoomFactor * (zoomIn ? 1 : -1));

        if (targetZoom < zoom.scaleExtent()[0] || targetZoom > zoom.scaleExtent()[1]) return false;

        view.x += center[0] - (((center[0] - view.x) / view.k) * targetZoom + view.x);
        view.y += center[1] - (((center[1] - view.y) / view.k) * targetZoom + view.y);
        view.k = targetZoom;

        this.interpolateZoom([view.x, view.y], view.k);
    };

    this.interpolateZoom = function(translate, scale) {

        return d3.transition().duration(350).tween('zoom', function() {

            var interpolatedTranslate = d3.interpolate(zoom.translate(), translate),
                interpolatedScale = d3.interpolate(zoom.scale(), scale);

            return function(time) {
                zoom.scale(interpolatedScale(time))
                    .translate(interpolatedTranslate(time));
                axisElement.call(axis);
                _this.updateBars();
            };
        });
    };
}

angular.module('chronontology.components').component('timeline', {
    templateUrl: '../../partials/timeline.html',
    bindings: {
        periods: '<',
        selectedPeriodId: '@',
        axisTicks: '@',
        inactive: '<'
    },
    controller: TimelineController
});
<commit_msg>show larger margin between periods with earliestFrom/latestTo on all zoom levels<commit_after>function TimelineController(timelineDataService, $location, $element, $scope) {

    var _this = this;

    // These variables should be constant variables. Set to var because of an error with safari.
    var margin = 15;
    var maxZoomYears = 5;
    var minStartYear = -10000;
    var maxStartYear = new Date().getFullYear();
    var barHeight = 20;
    var buttonZoomFactor = 0.5;
    // -------

    var timeline;
    var canvas;
    var axis, axisElement;
    var bars, barPaths, barTexts;
    var tooltip;
    var zoom, drag;
    var x, y;

    var timelineData;

    var totalXDomain = [];
    var startXDomain = [];
    var startYDomain = [];

    var width;
    var height;

    var initialized = false;

    var hoverPeriod;

    d3.selection.prototype.moveToFront = function() {

        return this.each(function() {
            this.parentNode.appendChild(this);
        });
    };

    this.$onChanges = function() {

        if (this.periods) this.initialize();
    };

    this.initialize = function() {

        width = this.getWidth();
        height = this.getHeight();

        y = d3.scale.linear()
            .domain([0, barHeight * 20])
            .range([0, parseInt(height) - 30]);

        timelineData = timelineDataService.getTimelineData(this.periods);
        totalXDomain = timelineData.xDomain;

        this.setStartDomains(timelineData.periodsMap);

        x = d3.scale.linear()
            .domain(startXDomain)
            .range([0, parseInt(width)]);

        y.domain(startYDomain);

        timeline = d3.select('#timeline').append('svg')
            .attr('width', parseInt(width))
            .attr('height', parseInt(height))
            .classed('timeline', true);

        if (this.inactive) timeline.classed('inactive', true);

        canvas = timeline.append('svg')
            .attr('width', parseInt(width))
            .attr('height', parseInt(height) - 30);

        axis = d3.svg.axis()
            .scale(x)
            .orient('bottom')
            .ticks(parseInt(this.axisTicks))
            .tickSize(10, 0);

        axisElement = timeline.append('svg')
            .attr('y', parseInt(height) - 30)
            .attr('width', parseInt(width))
            .classed('axis', true)
            .call(axis);

        if (!this.inactive) {
            var minZoom = (startXDomain[1] - startXDomain[0])
                / (totalXDomain[1] - totalXDomain[0]);
            var maxZoom = (startXDomain[1] - startXDomain[0]) / maxZoomYears;

            zoom = d3.behavior.zoom()
                .x(x)
                .scaleExtent([minZoom, maxZoom])
                .on('zoom', function () {
                    axisElement.call(axis);
                    _this.updateBars();
                });

            drag = d3.behavior.drag()
                .on('drag', function () {
                    var domain = y.domain();
                    domain[0] -= d3.event.dy;
                    domain[1] -= d3.event.dy;
                    y.domain(domain);
                    axisElement.call(axis);
                    _this.updateBars();
                });

            timeline.call(zoom).call(drag);

            tooltip = d3.select('body')
                .append('div')
                .classed('timeline-tooltip', true);
        }

        bars = canvas.selectAll('g').data(timelineData.periods).enter();
        barPaths = bars.append('g')
            .attr('id', function(d) { return 'bar-path-' + d.id; })
            .attr('class', function(d) {
                var barClass = 'bar level' + (d.level + 1);
                if (_this.inactive) {
                    barClass += ' inactive';
                } else {
                    barClass += ' group' + d.colorGroup;
                }
                return barClass;
            })
            .append('path');

        if (!this.inactive) {
            barPaths.on('click', _this.showPeriod);
            this.addHoverBehavior(barPaths);
        }

        if (this.selectedPeriodId) {
            barPaths.filter(function(d) {
                return d.id === _this.selectedPeriodId;
            }).classed('selected', true);
        }

        barTexts = canvas.selectAll('g')
            .append('text')
            .classed('text', true)
            .attr('id', function(d) { return 'bar-text-' + d.id; })
            .on('click', _this.showPeriod);
        if (this.inactive) {
            barTexts.classed('inactive', true);
        } else {
            this.addHoverBehavior(barTexts);
        }

        this.updateBars();

        d3.select(window).on('resize', _this.resize);

        initialized = true;
    };

    this.resize = function() {

        if (!initialized) return;

        var width = _this.getWidth();
        var height = _this.getHeight();

        y.range([0, parseInt(height) - 30]);
        x.range([0, parseInt(width)]);

        timeline.attr('width', parseInt(width))
            .attr('height', parseInt(height));

        canvas.attr('width', parseInt(width))
            .attr('height', parseInt(height) - 30);

        axisElement.attr('y', parseInt(height) - 30);
        axisElement.attr('width', parseInt(width));
        axisElement.call(axis);

        _this.updateBars();
    };

    this.getWidth = function() {

        return $element[0].parentNode.clientWidth - margin;
    };

    this.getHeight = function() {

        return $element[0].parentNode.clientHeight - margin;
    };

    this.updateBars = function() {

        barPaths.attr('d', function(data) { return _this.computeBarPaths(data); });

        barTexts.attr('x', function(data) {
            return x(data.from) + (_this.getBarWidth(data)) / 2;
        })
            .attr('y', function(data) {
                return y(data.row) + data.row * (barHeight + 5) + barHeight / 2 + 5;
            })
            .text(function(data) {
                if (_this.doesTextFitInBar(data.name, _this.getBarWidth(data))) {
                    data.textVisible = true;
                    return data.name;
                } else {
                    data.textVisible = false;
                    return '';
                }
            });

        axisElement.selectAll('.tick text')
            .text(function() { return _this.formatTickText(d3.select(this).text()); });
    };

    this.computeBarPaths = function(data) {

        var topY = _this.getPathYPosition(data);
        var bottomY = topY + barHeight;
        var edgeRadius = Math.min(Math.floor(_this.getBarWidth(data) / 2), 5);

        return _this.computeLeftEndPathDefinition(data, topY, bottomY, edgeRadius) + ' '
            + _this.computeRightEndPathDefinition(data, topY, bottomY, edgeRadius) + 'Z';
    };

    this.computeLeftEndPathDefinition = function(data, topY, bottomY, edgeRadius) {

        if (data.earliestFrom) {
            return 'M' + (x(data.earliestFrom) + ((x(data.from) - x(data.earliestFrom)) / 10)) + ' ' + bottomY + ' '
                + 'L' + (x(data.from) + + ((x(data.from) - x(data.earliestFrom)) / 10)) + ' ' + topY;
        } else if (edgeRadius > 0) {
            return 'M' + (x(data.from) + edgeRadius) + ' ' + bottomY + ' '
                + 'Q' + x(data.from) + ' ' + bottomY + ' ' + x(data.from) + ' ' + (bottomY - edgeRadius)
                + 'L' + x(data.from) + ' ' + (topY + edgeRadius)
                + 'Q' + x(data.from) + ' ' + topY + ' ' + (x(data.from) + edgeRadius) + ' ' + topY
        } else {
            return 'M' + x(data.from) + ' ' + bottomY + ' '
                + 'L' + x(data.from) + ' ' + topY;
        }
    };

    this.computeRightEndPathDefinition = function(data, topY, bottomY, edgeRadius) {

        if (data.latestTo || edgeRadius === 0) {
            return 'L' + x(data.latestTo || data.to) + ' ' + topY + ' '
                + 'L' + x(data.to) + ' ' + bottomY;
        } else {
            return 'L' + (x(data.to) - edgeRadius) + ' ' + topY + ' '
                + 'Q' + x(data.to) + ' ' + topY + ' ' + x(data.to) + ' ' + (topY + edgeRadius)
                + 'L' + x(data.to) + ' ' + (bottomY - edgeRadius)
                + 'Q' + x(data.to) + ' ' + bottomY + ' ' + (x(data.to) - edgeRadius) + ' ' + bottomY;
        }
    };

    this.getPathYPosition = function(data) {

        return y(data.row) + data.row * (barHeight + 5);
    };

    this.getBarWidth = function(data) {

        return x(data.to) - x(data.from);
    };

    this.doesTextFitInBar = function(text, barWidth) {

        return !(this.getApproximatedTextLength(text) > barWidth);
    };

    this.getApproximatedTextLength = function(text) {

        return text.length * 7;
    };

    this.showPeriod = function(period) {

        tooltip.style('visibility', 'hidden');
        $location.path('/period/' + period.id);
        console.log('show period!');
        $scope.$apply();
    };

    this.formatTickText = function(text) {

        text = text.split('.').join('$');
        text = text.split(',').join('.');
        text = text.split('$').join(',');

        if (text.length < 6 || (text.indexOf('-') > -1 && text.length < 7)) {
            text = text.replace('.', '');
        }

        return text;
    };

    this.addHoverBehavior = function(selection) {

        selection.on('mouseover', function(period) {
            d3.select('#bar-path-' + period.id).classed('hover', true);
            if (period !== hoverPeriod) {
                d3.select('#bar-path-' + period.id).moveToFront();
                d3.select('#bar-text-' + period.id).moveToFront();
                hoverPeriod = period;
            }
            if (period.textVisible) return;
            tooltip.text(period.name);
            return tooltip.style('visibility', 'visible');
        })
            .on('mousemove', function() {
                return tooltip.style('top', (d3.event.pageY - 10) + 'px')
                    .style('left', (d3.event.pageX + 10) + 'px');
            })
            .on('mouseout', function(period) {
                d3.select('#bar-path-' + period.id).classed('hover', false);
                return tooltip.style('visibility', 'hidden');
            });
    };

    this.setStartDomains = function(periodsMap) {

        if (this.selectedPeriodId && periodsMap[this.selectedPeriodId])
            this.setStartDomainsToSelection(periodsMap[this.selectedPeriodId]);
        else
            this.setStandardStartDomains();
    };

    this.setStartDomainsToSelection = function(selectedPeriod) {

        var span = selectedPeriod.to - selectedPeriod.from;
        var offset = (span < maxZoomYears) ? (maxZoomYears - span) / 2 : span / 2;
        var from = selectedPeriod.from - offset;
        var to = selectedPeriod.to + offset;
        if (from < totalXDomain[0]) from = totalXDomain[0];
        if (to > totalXDomain[1]) to = totalXDomain[1];
        startXDomain = [from, to];

        var centralRow = selectedPeriod.row;
        if (centralRow < 5) centralRow = 5;
        var yPos = centralRow + y.invert(centralRow * (barHeight + 5)) - 5;
        startYDomain = [yPos - barHeight * 10, yPos + barHeight * 10];
    };

    this.setStandardStartDomains = function() {

        startXDomain[0] = totalXDomain[0];
        startXDomain[1] = totalXDomain[1];
        if (startXDomain[0] < minStartYear)
            startXDomain[0] = minStartYear;
        if (startXDomain[1] > maxStartYear)
            startXDomain[1] = maxStartYear;

        startYDomain = [0, barHeight * 20];
    };

    this.zoomIn = function(event) {

        event.target.blur();
        this.zoom(true);
    };

    this.zoomOut = function(event) {

        event.target.blur();
        this.zoom(false);
    };

    this.zoom = function(zoomIn) {

        var center = [width / 2, height / 2];
        var view = { x: zoom.translate()[0], y: zoom.translate()[1], k: zoom.scale() };

        var targetZoom = zoom.scale() * (1 + buttonZoomFactor * (zoomIn ? 1 : -1));

        if (targetZoom < zoom.scaleExtent()[0] || targetZoom > zoom.scaleExtent()[1]) return false;

        view.x += center[0] - (((center[0] - view.x) / view.k) * targetZoom + view.x);
        view.y += center[1] - (((center[1] - view.y) / view.k) * targetZoom + view.y);
        view.k = targetZoom;

        this.interpolateZoom([view.x, view.y], view.k);
    };

    this.interpolateZoom = function(translate, scale) {

        return d3.transition().duration(350).tween('zoom', function() {

            var interpolatedTranslate = d3.interpolate(zoom.translate(), translate),
                interpolatedScale = d3.interpolate(zoom.scale(), scale);

            return function(time) {
                zoom.scale(interpolatedScale(time))
                    .translate(interpolatedTranslate(time));
                axisElement.call(axis);
                _this.updateBars();
            };
        });
    };
}

angular.module('chronontology.components').component('timeline', {
    templateUrl: '../../partials/timeline.html',
    bindings: {
        periods: '<',
        selectedPeriodId: '@',
        axisTicks: '@',
        inactive: '<'
    },
    controller: TimelineController
});
<|endoftext|>


<commit_before>import { normalize, Schema, arrayOf } from 'normalizr'
import humps from 'humps'

// Transforms deeply nested structure of report:
// {
//  "report": {
//  "id": 18,
//  "type": "Investigation",
//  "name": "Test Sequence",
//  "children": [
//  {
//      "id": 29,
//      "type" "Activity",
//      "children": [
//      {
//        "id": 101,
//        "type": "Section"
//        (...)
// into normalized form where objects are grouped by ID.
// See: https://github.com/gaearon/normalizr
export default function transformJSONResponse(json) {
  const camelizedJson = humps.camelizeKeys(json)

  const student = new Schema('students')
  const investigation = new Schema('investigations')
  const activity = new Schema('activities')
  const section = new Schema('sections')
  const page = new Schema('pages')
  const question = new Schema('questions', {idAttribute: (q) => q.key})
  // Answers don't have unique ID so generate it. Embeddable key + student ID works.
  const answer = new Schema('answers', {idAttribute: (a) => `${a.embeddableKey}-${a.studentId}`})
  investigation.define({
    children: arrayOf(activity)
  })
  activity.define({
    children: arrayOf(section)
  })
  section.define({
    children: arrayOf(page)
  })
  page.define({
    children: arrayOf(question)
  })
  question.define({
    answers: arrayOf(answer)
  })

  const reportType = camelizedJson.report.type
  const response = normalize(camelizedJson, {
    report: reportType === 'Investigation' ? investigation : activity,
    'class': {
      students: arrayOf(student)
    }
  })
  // Post-process response:
  // Provide fake investigation if it's not present to simplify app logic.
  if (reportType === 'Activity') {
    response.entities.investigations = {
      1: {
        id: 1,
        name: '',
        type: 'Investigation',
        children: [response.result.report]
      }
    }
  }
  applyVisibilityFilter(response.entities.questions, response.result.visibilityFilter)
  copyAnswerKeysToObjects(response.entities.answers)
  saveStudentsRealNames(response.entities.students)
  return response
}

function applyVisibilityFilter(questions, visibilityFilter) {
  Object.values(questions).forEach(question => {
    question.selected = false
    question.visible = false
  })
  visibilityFilter.questions.forEach(key => {
    questions[key].selected = true
    questions[key].visible = true
  })
}

// Keys are generated by normalizr above, so just copy them to the object hashes.
function copyAnswerKeysToObjects(answers) {
  Object.keys(answers).forEach(key => {
    answers[key].key = key
  })
}

// Provide additional property in student hash, it's useful for anonymization.
function saveStudentsRealNames(students) {
  Object.values(students).forEach(student => {
    student.realName = student.name
  })
}
<commit_msg>The report should handle case where no students have yet started the activity.<commit_after>import { normalize, Schema, arrayOf } from 'normalizr'
import humps from 'humps'

// Transforms deeply nested structure of report:
// {
//  "report": {
//  "id": 18,
//  "type": "Investigation",
//  "name": "Test Sequence",
//  "children": [
//  {
//      "id": 29,
//      "type" "Activity",
//      "children": [
//      {
//        "id": 101,
//        "type": "Section"
//        (...)
// into normalized form where objects are grouped by ID.
// See: https://github.com/gaearon/normalizr
export default function transformJSONResponse(json) {
  const camelizedJson = humps.camelizeKeys(json)

  const student = new Schema('students')
  const investigation = new Schema('investigations')
  const activity = new Schema('activities')
  const section = new Schema('sections')
  const page = new Schema('pages')
  const question = new Schema('questions', {idAttribute: (q) => q.key})
  // Answers don't have unique ID so generate it. Embeddable key + student ID works.
  const answer = new Schema('answers', {idAttribute: (a) => `${a.embeddableKey}-${a.studentId}`})
  investigation.define({
    children: arrayOf(activity)
  })
  activity.define({
    children: arrayOf(section)
  })
  section.define({
    children: arrayOf(page)
  })
  page.define({
    children: arrayOf(question)
  })
  question.define({
    answers: arrayOf(answer)
  })

  const reportType = camelizedJson.report.type
  const response = normalize(camelizedJson, {
    report: reportType === 'Investigation' ? investigation : activity,
    'class': {
      students: arrayOf(student)
    }
  })
  // Post-process response:
  // Provide fake investigation if it's not present to simplify app logic.
  if (reportType === 'Activity') {
    response.entities.investigations = {
      1: {
        id: 1,
        name: '',
        type: 'Investigation',
        children: [response.result.report]
      }
    }
  }
  applyVisibilityFilter(response.entities.questions, response.result.visibilityFilter)
  copyAnswerKeysToObjects(response.entities.answers || [])
  saveStudentsRealNames(response.entities.students)
  return response
}

function applyVisibilityFilter(questions, visibilityFilter) {
  Object.values(questions).forEach(question => {
    question.selected = false
    question.visible = false
  })
  visibilityFilter.questions.forEach(key => {
    questions[key].selected = true
    questions[key].visible = true
  })
}

// Keys are generated by normalizr above, so just copy them to the object hashes.
function copyAnswerKeysToObjects(answers) {
  Object.keys(answers).forEach(key => {
    answers[key].key = key
  })
}

// Provide additional property in student hash, it's useful for anonymization.
function saveStudentsRealNames(students) {
  Object.values(students).forEach(student => {
    student.realName = student.name
  })
}
<|endoftext|>


<commit_before>var people = [
 {
   "title": "Mayor",
   "district": "n/a",
   "name": {
     "first": "John ",
     "last": "Giles",
     "fullname": "John Giles"
 },
   "bio": "John Giles was elected the 40th Mayor of Mesa, Arizona in August, 2014. Giles earned degrees from Brigham Young University in Political Science and Arizona State University's Sandra Day O'Connor College of Law. He has managed his own law firm in downtown Mesa for the past 20 years. In 1996, Giles was elected to the Mesa City Council and from 1998 to 2000, he served as Vice Mayor.  Mayor Giles is committed to taking Mesa to the next level with his NextMesa vision.",
   "social": [
     {
       "platform": "facebook",
       "name": "MesaMayorsOffice"
     },
     {
       "platform": "twitter",
       "name": "MayorGiles"
     }
   ],
   "phone": "480-644-2388",
   "email": "mayor@Mesaaz.gov",
   "website_url": "http://www.mesaaz.gov/mayor/",
   "photo_url": "https://techieshark.github.io/council-info/img/John_Giles_sm.jpg"
 },
 {
   "title": "City Councilmember",
   "district": "1",
   "name": {
     "first": "Mark",
     "last": "Freeman",
     "fullname": "Mark Freeman"
   },
   "bio": "Elected to the Mesa City Council in August 2016, Councilmember Mark Freeman began his first term representing District 1 in January of 2017. His term on the Council runs until January of 2021. Freeman’s Mesa roots run deep, as his ancestor, Charles Crismon, was one of the founding fathers of Mesa.  His family has farmed in the Valley since 1878 and Freeman continues that tradition today, growing alfalfa and sweet corn and running a summer farmer’s market at Brown and Center Street. In addition to farming, he spent 31 years with the Mesa Fire and Medical Department as a Captain Paramedic before retiring in 2011. Mark has always been active in the community, serving on the Mesa Board of Adjustment as well as serving as the Lehi area Board President for 12 years. In addition, he has been involved with the Maricopa County and Arizona Farm Bureau organizations for many years. Mark has lived in Mesa for 61 years.  He and his wife, LeeAnn, have been married for 38 years and have a strong family value, with great love for their 3 children and 8 grandchildren.  His oldest son is currently serving in the U.S. Air Force as a Captain. Their other children reside in Mesa.",
   "social": [
     {
       "platform": "facebook",
       "name": "CouncilmemberMarkFreeman"
     },
     {
       "platform": "twitter",
       "name": "mesadistrict1",
       "widget": "465935532589993984"
     }
   ],
   "phone": "480-644-4002",
   "email": "District1@mesaaz.gov",
   "website_url": "http://www.mesaaz.gov/council/Freeman/default.aspx",
   "photo_url": "https://cityofmesaaz.github.io/council-info/img/District1MarkFreeman_sm.jpg"
 },
 {
  "title": "City Councilmember",
  "district": "2",
  "name": { "first": "Jeremy",
           "last": "Whittaker",
           "fullname": "Jeremy Whittaker"
   },
  "bio": "Elected to the Mesa City Council in November of 2016, Councilmember Jeremy Whittaker began his first term representing District 2 in January of 2017. His term on the Council runs until January of 2021.  Whittaker is the founder and president of Velocity Technologies, a technology solutions company for small to medium size businesses. He has worked in the IT consulting and technology industry for more than 15 years, owning his own firm since 2004.  For the past five years Whittaker has been a member of the Vista Villages II Home Owners Association Board of Directors, including four years as the Vice President. He also has served as an Adjunct Faculty member for Maricopa Community Colleges, teaching a course on creating profitable strategies for trading the currency market on a fully automated basis.  Whittaker’s family moved to Arizona more than 20 years ago, and he and his wife Roshana have lived in Mesa since 2005. ",
  "social": [
   {
   "platform": "facebook",
   "name": "CouncilmemberJeremyWhittaker"
   },
      { "platform": "twitter",
        "name": "MesaDistrict2",
        "widget": "465941398470922240"
      }
  ],
  "phone": "480-644-3772",
  "email": "councilmember.whittaker@Mesaaz.gov",
  "website_url": "http://www.mesaaz.gov/council/whittaker/",
  "photo_url": "https://cityofmesaaz.github.io/council-info/img/District2JeremyWhittaker_sm.jpg"
 },
 {
   "title": "City Councilmember",
   "district": "3",
   "name": {
     "first": "Dennis",
     "last": "Kavanaugh ",
     "fullname": "Dennis Kavanaugh "
   },
   "bio": "Dennis Kavanaugh was elected to serve as the Mesa City Councilmember for District 3 in June of 2008 and was re-elected in August of 2012. He was named the Vice Mayor of Mesa in January of 2015. Kavanaugh also was a member of the City Council from 1996 until 2004, serving as Vice Mayor from 2002 to 2004. Kavanaugh sits on several different committees and boards both nationally and regionally. Councilmember Kavanaugh received his J.D. degree from Arizona State University and a B.A. in Political Science from St. Louis University.",
   "social": [
     {
       "platform": "facebook",
       "name": "councilmember.kavanaugh"
     },
     {
       "platform": "twitter",
       "name": "MesaDistrict3",
       "widget": "465941592985968641"
     }
   ],
   "phone": "480-644-3003",
   "email": "District3@mesaaz.gov",
   "website_url": "http://www.mesaaz.gov/council/kavanaugh/default.aspx",
   "photo_url": "https://techieshark.github.io/council-info/img/Dennis_Kavanaugh_sm.jpg"
 },
 {
   "title": "City Councilmember",
   "district": "4",
   "name": {
     "first": "Chris",
     "last": "Glover",
     "fullname": "Chris Glover"
   },
   "bio": "The youngest person ever to serve on the Mesa City Council following his election at age 23 in 2011, Councilmember Chris Glover was re-elected to a second term representing District 4 in August of 2014. He was named Vice Mayor in April 2014, serving in the position until January of 2015.  Glover is a native of Mesa, attending Mesa Public Schools, graduating with honors from Mesa High School and he was a member of the Mayor's Youth Committee. Vice Mayor Glover earned a degree in Political Science with minors in History and Spanish and a Master of Science in Management from Arizona State University.",
   "social": [
     {
       "platform": "facebook",
       "name": "CouncilmemberChrisGlover"
     },
     {
       "platform": "twitter",
       "name": "MesaDistrict4",
       "widget": "465941739635613696"
     }
   ],
   "phone": "480-644- 3004",
   "email": "councilmember.glover@Mesaaz.gov",
   "website_url": "http://www.mesaaz.gov/council/glover/",
   "photo_url": "https://techieshark.github.io/council-info/img/Chris_Glover_sm.jpg"
 },
 {
   "title": "City Councilmember",
   "district": "5",
   "name": {
     "first": "David",
     "last": "Luna",
     "fullname": "David Luna"
   },
   "bio": "Appointed to fill the vacant District 5 seat in September of 2013, David Luna became the first Hispanic elected to the Mesa City Council when voters elected him in August of 2014. His term on the Mesa City Council runs until January of 2019. Luna has been recognized for his community involvement with numerous awards, including the Mesa United Way Volunteer of the Year Award in 2006, the City of Mesa National Emergency Preparedness Recognition in 2007, the Valle del Sol Hall of Fame Award in 2011 and the Mesa United Way Spirit of Mesa Award in 2012 and the City of Mesa's Citizen of the Year in 2012. Luna was also inducted into the Silver Circle Society (National Academy of Arts and Science) Rocky Mountain Chapter Councilmember Luna earned his Bachelor of Arts in Radio/Television from the University of Arizona, a Master of Mass Communication from Arizona State University and his Doctorate in Educational Leadership from Northern Arizona University in 2012. ",
   "social": [
     {
       "platform": "facebook",
       "name": "CouncilmemberDavidLuna"
     },
     {
       "platform": "twitter",
       "name": "MesaDistrict5",
       "widget": "465941843922792448"
     }
   ],
   "phone": "480-644-3771",
   "email": "councilmember.luna@Mesaaz.gov",
   "website_url": "http://www.mesaaz.gov/council/luna/default.aspx",
   "photo_url": "https://techieshark.github.io/council-info/img/David_Luna_sm.jpg"
 },
 {
   "title": "City Councilmember",
   "district": "6",
   "name": {
     "first": "Kevin",
     "last": "Thompson",
     "fullname": "Kevin Thompson"
   },
   "bio": "Elected to the Mesa City Council in August 2014, Councilmember Kevin Thompson began his first term representing District 6 in January of 2015. His term on the Council runs until January of 2019.  Thompson is very active in the community, serving on the Board of Directors for the Mesa Chamber of Commerce and the Centers for Habilitation. He was a member of the Military and Veterans Affairs Committee at the Arizona Chamber of Commerce, is the past chair of the Government Affairs Committee for the American Public Works Association and the chair of the National Utility Contractors Association. He is also the secretary of the Tonto Recreation Alliance and is a 2012 graduate of the Mesa Leadership Training and Development program.  A veteran of the United States Air Force, Thompson spent eight years in the service, including serving in Operation Desert Storm. He earned a bachelor’s degree in Mechanical Engineering from the University of Nevada-Las Vegas. Kevin and his wife Donna have two children.",
   "social": [
     {
       "platform": "facebook",
       "name": "CouncilmemberKevinThompson"
     },
     {
       "platform": "twitter",
       "name": "MesaDistrict6",
       "widget": "465941964072828928"
     }
   ],
   "phone": "480-644-4003",
   "email": "councilmember.thompson@Mesaaz.gov",
   "website_url": "http://www.mesaaz.gov/council/thompson/default.aspx",
   "photo_url": "https://techieshark.github.io/council-info/img/Kevin_Thompson_sm.jpg"
 },
 {
   "title": "City Manager",
   "district": "n/a",
   "name": {
     "first": "Chris",
     "last": "Brady",
     "fullname": "Chris Brady"
   },
   "bio": "The City Manager, as the City's chief administrative officer, administers the functions of the City in accordance with the City Charter, Ordinances, and established policies.  The City Manager’s Office provides centralized direction and leadership for the effective administration and operations of all municipal services for the City of Mesa as directed by the City Council and serves as the focal point for the management of the City staff departments.  The City Manager’s Office prepares and submits to the City Council a balanced budget of municipal services in adherence with the policy goals and objectives by the City Council while employing such managerial techniques as needed to assure efficient and effective utilization of the City’s resources.",
   "social": [
     {
       "platform": "facebook",
       "name": "CityofMesa"
     },
     {
       "platform": "twitter",
       "name": "MesaCityManager"
     }
    ],
   "phone": "480-644-2066",
   "email": "COMmanager@Mesaaz.gov",
   "website_url": "http://www.mesaaz.gov/mayor/",
   "photo_url": "https://techieshark.github.io/council-info/img/Chris_Brady_sm.jpg"
  },
]
<commit_msg>Changed District 3 Councilmember <commit_after>var people = [
 {
   "title": "Mayor",
   "district": "n/a",
   "name": {
     "first": "John ",
     "last": "Giles",
     "fullname": "John Giles"
 },
   "bio": "John Giles was elected the 40th Mayor of Mesa, Arizona in August, 2014. Giles earned degrees from Brigham Young University in Political Science and Arizona State University's Sandra Day O'Connor College of Law. He has managed his own law firm in downtown Mesa for the past 20 years. In 1996, Giles was elected to the Mesa City Council and from 1998 to 2000, he served as Vice Mayor.  Mayor Giles is committed to taking Mesa to the next level with his NextMesa vision.",
   "social": [
     {
       "platform": "facebook",
       "name": "MesaMayorsOffice"
     },
     {
       "platform": "twitter",
       "name": "MayorGiles"
     }
   ],
   "phone": "480-644-2388",
   "email": "mayor@Mesaaz.gov",
   "website_url": "http://www.mesaaz.gov/mayor/",
   "photo_url": "https://techieshark.github.io/council-info/img/John_Giles_sm.jpg"
 },
 {
   "title": "City Councilmember",
   "district": "1",
   "name": {
     "first": "Mark",
     "last": "Freeman",
     "fullname": "Mark Freeman"
   },
   "bio": "Elected to the Mesa City Council in August 2016, Councilmember Mark Freeman began his first term representing District 1 in January of 2017. His term on the Council runs until January of 2021. Freeman’s Mesa roots run deep, as his ancestor, Charles Crismon, was one of the founding fathers of Mesa.  His family has farmed in the Valley since 1878 and Freeman continues that tradition today, growing alfalfa and sweet corn and running a summer farmer’s market at Brown and Center Street. In addition to farming, he spent 31 years with the Mesa Fire and Medical Department as a Captain Paramedic before retiring in 2011. Mark has always been active in the community, serving on the Mesa Board of Adjustment as well as serving as the Lehi area Board President for 12 years. In addition, he has been involved with the Maricopa County and Arizona Farm Bureau organizations for many years. Mark has lived in Mesa for 61 years.  He and his wife, LeeAnn, have been married for 38 years and have a strong family value, with great love for their 3 children and 8 grandchildren.  His oldest son is currently serving in the U.S. Air Force as a Captain. Their other children reside in Mesa.",
   "social": [
     {
       "platform": "facebook",
       "name": "CouncilmemberMarkFreeman"
     },
     {
       "platform": "twitter",
       "name": "mesadistrict1",
       "widget": "465935532589993984"
     }
   ],
   "phone": "480-644-4002",
   "email": "District1@mesaaz.gov",
   "website_url": "http://www.mesaaz.gov/council/Freeman/default.aspx",
   "photo_url": "https://cityofmesaaz.github.io/council-info/img/District1MarkFreeman_sm.jpg"
 },
 {
  "title": "City Councilmember",
  "district": "2",
  "name": { "first": "Jeremy",
           "last": "Whittaker",
           "fullname": "Jeremy Whittaker"
   },
  "bio": "Elected to the Mesa City Council in November of 2016, Councilmember Jeremy Whittaker began his first term representing District 2 in January of 2017. His term on the Council runs until January of 2021.  Whittaker is the founder and president of Velocity Technologies, a technology solutions company for small to medium size businesses. He has worked in the IT consulting and technology industry for more than 15 years, owning his own firm since 2004.  For the past five years Whittaker has been a member of the Vista Villages II Home Owners Association Board of Directors, including four years as the Vice President. He also has served as an Adjunct Faculty member for Maricopa Community Colleges, teaching a course on creating profitable strategies for trading the currency market on a fully automated basis.  Whittaker’s family moved to Arizona more than 20 years ago, and he and his wife Roshana have lived in Mesa since 2005. ",
  "social": [
   {
   "platform": "facebook",
   "name": "CouncilmemberJeremyWhittaker"
   },
      { "platform": "twitter",
        "name": "MesaDistrict2",
        "widget": "465941398470922240"
      }
  ],
  "phone": "480-644-3772",
  "email": "councilmember.whittaker@Mesaaz.gov",
  "website_url": "http://www.mesaaz.gov/council/whittaker/",
  "photo_url": "https://cityofmesaaz.github.io/council-info/img/District2JeremyWhittaker_sm.jpg"
 },
 {
  "title": "City Councilmember",
  "district": "3",
  "name":
  { "first": "Ryan",
   "last": "Winkle ",
   "fullname": "Ryan Winkle "
  },
  "bio": "Elected to the Mesa City Council in August 2016, Councilmember Ryan Winkle began his first term representing District 3 in January of 2017. His term on the Council runs until January of 2021. Winkle is one of the co-founders of the Mesa Urban Garden, a community organization that provides fresh produce directly to the community and local food banks. He was a key voice behind turning an empty dirt lot into a point of pride for Downtown Mesa. He is also a co-founder of RAILMesa, a registered neighborhood group that advocates for increased citizen participation, responsible development of housing, transit options and the creation of quality jobs along Mesa’s Light Rail Corridor. Winkle works as a Community Economic Development Consultant, dealing with underserved communities around the East Valley in the areas of housing options, workforce development, neighborhood revitalization, public outreach and transit-oriented development.  Winkle holds a both a Bachelor of Arts in Sustainable Urban Dynamics and a Bachelor of Sciences in Urban Planning from Arizona State University and a Masters of Business Administration from Wilkes University Mesa. Raised in Mesa, Ryan and his wife Ericka have one son, Rian Erick.",
  "social": [
   { "platform": "facebook",
    "name": "councilmemberRyanWinkle"
   },
   { "platform": "twitter",
    "name": "MesaDistrict3",
    "widget": "465941592985968641"
   }
  ],
  "phone": "480-644-3003",
  "email": "District3@mesaaz.gov",
  "website_url": "http://www.mesaaz.gov/council/winkle/default.aspx",
  "photo_url": "https://cityofmesaaz.github.io/council-info/img/District3RyanWinkle_sm.jpg"
 },
 {
   "title": "City Councilmember",
   "district": "4",
   "name": {
     "first": "Chris",
     "last": "Glover",
     "fullname": "Chris Glover"
   },
   "bio": "The youngest person ever to serve on the Mesa City Council following his election at age 23 in 2011, Councilmember Chris Glover was re-elected to a second term representing District 4 in August of 2014. He was named Vice Mayor in April 2014, serving in the position until January of 2015.  Glover is a native of Mesa, attending Mesa Public Schools, graduating with honors from Mesa High School and he was a member of the Mayor's Youth Committee. Vice Mayor Glover earned a degree in Political Science with minors in History and Spanish and a Master of Science in Management from Arizona State University.",
   "social": [
     {
       "platform": "facebook",
       "name": "CouncilmemberChrisGlover"
     },
     {
       "platform": "twitter",
       "name": "MesaDistrict4",
       "widget": "465941739635613696"
     }
   ],
   "phone": "480-644- 3004",
   "email": "councilmember.glover@Mesaaz.gov",
   "website_url": "http://www.mesaaz.gov/council/glover/",
   "photo_url": "https://techieshark.github.io/council-info/img/Chris_Glover_sm.jpg"
 },
 {
   "title": "City Councilmember",
   "district": "5",
   "name": {
     "first": "David",
     "last": "Luna",
     "fullname": "David Luna"
   },
   "bio": "Appointed to fill the vacant District 5 seat in September of 2013, David Luna became the first Hispanic elected to the Mesa City Council when voters elected him in August of 2014. His term on the Mesa City Council runs until January of 2019. Luna has been recognized for his community involvement with numerous awards, including the Mesa United Way Volunteer of the Year Award in 2006, the City of Mesa National Emergency Preparedness Recognition in 2007, the Valle del Sol Hall of Fame Award in 2011 and the Mesa United Way Spirit of Mesa Award in 2012 and the City of Mesa's Citizen of the Year in 2012. Luna was also inducted into the Silver Circle Society (National Academy of Arts and Science) Rocky Mountain Chapter Councilmember Luna earned his Bachelor of Arts in Radio/Television from the University of Arizona, a Master of Mass Communication from Arizona State University and his Doctorate in Educational Leadership from Northern Arizona University in 2012. ",
   "social": [
     {
       "platform": "facebook",
       "name": "CouncilmemberDavidLuna"
     },
     {
       "platform": "twitter",
       "name": "MesaDistrict5",
       "widget": "465941843922792448"
     }
   ],
   "phone": "480-644-3771",
   "email": "councilmember.luna@Mesaaz.gov",
   "website_url": "http://www.mesaaz.gov/council/luna/default.aspx",
   "photo_url": "https://techieshark.github.io/council-info/img/David_Luna_sm.jpg"
 },
 {
   "title": "City Councilmember",
   "district": "6",
   "name": {
     "first": "Kevin",
     "last": "Thompson",
     "fullname": "Kevin Thompson"
   },
   "bio": "Elected to the Mesa City Council in August 2014, Councilmember Kevin Thompson began his first term representing District 6 in January of 2015. His term on the Council runs until January of 2019.  Thompson is very active in the community, serving on the Board of Directors for the Mesa Chamber of Commerce and the Centers for Habilitation. He was a member of the Military and Veterans Affairs Committee at the Arizona Chamber of Commerce, is the past chair of the Government Affairs Committee for the American Public Works Association and the chair of the National Utility Contractors Association. He is also the secretary of the Tonto Recreation Alliance and is a 2012 graduate of the Mesa Leadership Training and Development program.  A veteran of the United States Air Force, Thompson spent eight years in the service, including serving in Operation Desert Storm. He earned a bachelor’s degree in Mechanical Engineering from the University of Nevada-Las Vegas. Kevin and his wife Donna have two children.",
   "social": [
     {
       "platform": "facebook",
       "name": "CouncilmemberKevinThompson"
     },
     {
       "platform": "twitter",
       "name": "MesaDistrict6",
       "widget": "465941964072828928"
     }
   ],
   "phone": "480-644-4003",
   "email": "councilmember.thompson@Mesaaz.gov",
   "website_url": "http://www.mesaaz.gov/council/thompson/default.aspx",
   "photo_url": "https://techieshark.github.io/council-info/img/Kevin_Thompson_sm.jpg"
 },
 {
   "title": "City Manager",
   "district": "n/a",
   "name": {
     "first": "Chris",
     "last": "Brady",
     "fullname": "Chris Brady"
   },
   "bio": "The City Manager, as the City's chief administrative officer, administers the functions of the City in accordance with the City Charter, Ordinances, and established policies.  The City Manager’s Office provides centralized direction and leadership for the effective administration and operations of all municipal services for the City of Mesa as directed by the City Council and serves as the focal point for the management of the City staff departments.  The City Manager’s Office prepares and submits to the City Council a balanced budget of municipal services in adherence with the policy goals and objectives by the City Council while employing such managerial techniques as needed to assure efficient and effective utilization of the City’s resources.",
   "social": [
     {
       "platform": "facebook",
       "name": "CityofMesa"
     },
     {
       "platform": "twitter",
       "name": "MesaCityManager"
     }
    ],
   "phone": "480-644-2066",
   "email": "COMmanager@Mesaaz.gov",
   "website_url": "http://www.mesaaz.gov/mayor/",
   "photo_url": "https://techieshark.github.io/council-info/img/Chris_Brady_sm.jpg"
  },
]
<|endoftext|>


<commit_before>//Configure ajax and CSRF

// See https://docs.djangoproject.com/en/1.5/ref/contrib/csrf/#ajax
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
    crossDomain: false,
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type)) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});

//UX Functions

function saveStory(storyField){
    storyId = $(storyField).data('id');
    storyData = {
        title: $(storyField).val(),
        time: 0
    };
    if (storyId){
        $.ajax({
            type: 'POST',
            url: 'story/'+storyId+'/',
            data: storyData
        });
    }else{
        $.ajax({
            type: 'POST',
            url: 'story/add/',
            data: storyData
        });
    }
}

function saveTask(taskField){
    taskId = $(taskField).data('id');
    taskData = {
        title: $(taskField).val(),
        time: 0,
        story_id: 1
    };
    if (taskId){
        $.ajax({
            type: 'POST',
            url: 'task/'+taskId+'/',
            data: taskData
        });
    }else{
        $.ajax({
            type: 'POST',
            url: 'task/add/',
            data: taskData
        });
    }
}

function addSprint(projectId){

}

function sprintAddTask(sprintArea){

}

function sprintDelTask(taskId){

}

function saveProject(){
    projectData = {
        title: $('#project_title').val(),
        description: $('#project_description').html()
    };
    $.ajax({
        url: 'update/',
        type: 'POST',
        data: projectData
    });
}

function addNewStoryField(){
    // debugger;
    storyUl = $('#stories ul')
    newField = $('
<input class="whiteboard_field text_font"></input>
');
    newField.appendTo(storyUl);
    return newField;
}

function addNewTaskField(){
    taskUl = $('#tasks ul')
    newField = $('
<input class="whiteboard_field text_font"></input>
');
    newField.appendTo(taskUl);
    return newField;
}

function addNewSprint(){

}

function syncStories(data){

}

function syncTasks(data){

}

function syncSprints(data){

}

function syncProject(){

}

//Events functions
function onHeaderFieldKeydown(e){
    if(e.which == 13 && e.target.value != '' && e.shiftKey == false){
        event.preventDefault();
        storyField = addNewStoryField();
        storyField.find('input').focus();
    }
}

function onHeaderFieldChange(e){
    saveProject();
}

function onStoryFieldKeydown(e){
    if(e.which == 13 && e.target.value != ''){
        event.preventDefault();
        storyField = addNewStoryField();
        storyField.find('input').focus();
    }
}

function onStoryFieldChange(e){
    saveStory(e.target);
}

function onTaskFieldKeydown(e){
    if(e.which == 13 && e.target.value != ''){
        event.preventDefault();
        taskField = addNewTaskField();
        taskField.find('input').focus();
    }
}

function onTaskFieldChange(e){
    saveTask(e.target);
}

//TODO: Add the sprint drag&drop events

$(function() {
    $('header .whiteboard_field').change(onHeaderFieldChange);
    $('header .whiteboard_field').keydown(onHeaderFieldKeydown);

    $('#stories').on('change','input',onStoryFieldChange);
    $('#stories').on('keydown','input',onStoryFieldKeydown);

    $('#tasks').on('change','input',onTaskFieldChange);
    $('#tasks').on('keydown','input',onTaskFieldKeydown);

    $( "#tasks li" ).draggable({
        appendTo: "body",
        helper: "clone"
    });
    $( ".sprint_tasks" ).droppable({
        activeClass: "ui-state-default",
        hoverClass: "ui-state-hover",
        accept: ":not(.ui-sortable-helper)",
        drop: function( event, ui ) {
            $( this ).find( ".placeholder" ).remove();
            $( "

" ).text( ui.draggable.find('input').val() ).appendTo( this );
        }
    }).sortable({
        items: "li:not(.placeholder)",
        sort: function() {
            // gets added unintentionally by droppable interacting with sortable
            // using connectWithSortable fixes this, but doesn't allow you to customize active/hoverClass options
            $( this ).removeClass( "ui-state-default" );
        }
    });
});

<commit_msg>Change field name<commit_after>//Configure ajax and CSRF

// See https://docs.djangoproject.com/en/1.5/ref/contrib/csrf/#ajax
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
    crossDomain: false,
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type)) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});

//UX Functions

function saveStory(storyField){
    storyId = $(storyField).data('id');
    storyData = {
        title: $(storyField).val(),
        estimated_time: 0
    };
    if (storyId){
        $.ajax({
            type: 'POST',
            url: 'story/'+storyId+'/',
            data: storyData
        });
    }else{
        $.ajax({
            type: 'POST',
            url: 'story/add/',
            data: storyData
        });
    }
}

function saveTask(taskField){
    taskId = $(taskField).data('id');
    taskData = {
        title: $(taskField).val(),
        estimated_time: 0,
        story: 1
    };
    if (taskId){
        $.ajax({
            type: 'POST',
            url: 'task/'+taskId+'/',
            data: taskData
        });
    }else{
        $.ajax({
            type: 'POST',
            url: 'task/add/',
            data: taskData
        });
    }
}

function addSprint(projectId){

}

function sprintAddTask(sprintArea){

}

function sprintDelTask(taskId){

}

function saveProject(){
    projectData = {
        title: $('#project_title').val(),
        description: $('#project_description').html()
    };
    $.ajax({
        url: 'update/',
        type: 'POST',
        data: projectData
    });
}

function addNewStoryField(){
    // debugger;
    storyUl = $('#stories ul')
    newField = $('
<input class="whiteboard_field text_font"></input>
');
    newField.appendTo(storyUl);
    return newField;
}

function addNewTaskField(){
    taskUl = $('#tasks ul')
    newField = $('
<input class="whiteboard_field text_font"></input>
');
    newField.appendTo(taskUl);
    return newField;
}

function addNewSprint(){

}

function syncStories(data){

}

function syncTasks(data){

}

function syncSprints(data){

}

function syncProject(){

}

//Events functions
function onHeaderFieldKeydown(e){
    if(e.which == 13 && e.target.value != '' && e.shiftKey == false){
        event.preventDefault();
        storyField = addNewStoryField();
        storyField.find('input').focus();
    }
}

function onHeaderFieldChange(e){
    saveProject();
}

function onStoryFieldKeydown(e){
    if(e.which == 13 && e.target.value != ''){
        event.preventDefault();
        storyField = addNewStoryField();
        storyField.find('input').focus();
    }
}

function onStoryFieldChange(e){
    saveStory(e.target);
}

function onTaskFieldKeydown(e){
    if(e.which == 13 && e.target.value != ''){
        event.preventDefault();
        taskField = addNewTaskField();
        taskField.find('input').focus();
    }
}

function onTaskFieldChange(e){
    saveTask(e.target);
}

//TODO: Add the sprint drag&drop events

$(function() {
    $('header .whiteboard_field').change(onHeaderFieldChange);
    $('header .whiteboard_field').keydown(onHeaderFieldKeydown);

    $('#stories').on('change','input',onStoryFieldChange);
    $('#stories').on('keydown','input',onStoryFieldKeydown);

    $('#tasks').on('change','input',onTaskFieldChange);
    $('#tasks').on('keydown','input',onTaskFieldKeydown);

    $( "#tasks li" ).draggable({
        appendTo: "body",
        helper: "clone"
    });
    $( ".sprint_tasks" ).droppable({
        activeClass: "ui-state-default",
        hoverClass: "ui-state-hover",
        accept: ":not(.ui-sortable-helper)",
        drop: function( event, ui ) {
            $( this ).find( ".placeholder" ).remove();
            $( "

" ).text( ui.draggable.find('input').val() ).appendTo( this );
        }
    }).sortable({
        items: "li:not(.placeholder)",
        sort: function() {
            // gets added unintentionally by droppable interacting with sortable
            // using connectWithSortable fixes this, but doesn't allow you to customize active/hoverClass options
            $( this ).removeClass( "ui-state-default" );
        }
    });
});

<|endoftext|>


<commit_before>;(function ($, window, document, undefined) {
  'use strict';

  Foundation.libs.reveal = {
    name : 'reveal',

    version : '{{VERSION}}',

    locked : false,

    settings : {
      animation: 'fadeAndPop',
      animation_speed: 250,
      close_on_background_click: true,
      close_on_esc: true,
      dismiss_modal_class: 'close-reveal-modal',
      bg_class: 'reveal-modal-bg',
      root_element: 'body',
      open: function(){},
      opened: function(){},
      close: function(){},
      closed: function(){},
      bg : $('.reveal-modal-bg'),
      css : {
        open : {
          'opacity': 0,
          'visibility': 'visible',
          'display' : 'block'
        },
        close : {
          'opacity': 1,
          'visibility': 'hidden',
          'display': 'none'
        }
      }
    },

    init : function (scope, method, options) {
      $.extend(true, this.settings, method, options);
      this.bindings(method, options);
    },

    events : function (scope) {
      var self = this,
          S = self.S;

      S(this.scope)
        .off('.reveal')
        .on('click.fndtn.reveal', '[' + this.add_namespace('data-reveal-id') + ']:not([disabled])', function (e) {
          e.preventDefault();

          if (!self.locked) {
            var element = S(this),
                ajax = element.data(self.data_attr('reveal-ajax'));

            self.locked = true;

            if (typeof ajax === 'undefined') {
              self.open.call(self, element);
            } else {
              var url = ajax === true ? element.attr('href') : ajax;

              self.open.call(self, element, {url: url});
            }
          }
        });

      S(document)
        .on('click.fndtn.reveal', this.close_targets(), function (e) {

          e.preventDefault();

          if (!self.locked) {
            var settings = S('[' + self.attr_name() + '].open').data(self.attr_name(true) + '-init') || self.settings,
                bg_clicked = S(e.target)[0] === S('.' + settings.bg_class)[0];

            if (bg_clicked) {
              if (settings.close_on_background_click) {
                e.stopPropagation();
              } else {
                return;
              }
            }

            self.locked = true;
            self.close.call(self, bg_clicked ? S('[' + self.attr_name() + '].open') : S(this).closest('[' + self.attr_name() + ']'));
          }
        });

      if(S('[' + self.attr_name() + ']', this.scope).length > 0) {
        S(this.scope)
          // .off('.reveal')
          .on('open.fndtn.reveal', this.settings.open)
          .on('opened.fndtn.reveal', this.settings.opened)
          .on('opened.fndtn.reveal', this.open_video)
          .on('close.fndtn.reveal', this.settings.close)
          .on('closed.fndtn.reveal', this.settings.closed)
          .on('closed.fndtn.reveal', this.close_video);
      } else {
        S(this.scope)
          // .off('.reveal')
          .on('open.fndtn.reveal', '[' + self.attr_name() + ']', this.settings.open)
          .on('opened.fndtn.reveal', '[' + self.attr_name() + ']', this.settings.opened)
          .on('opened.fndtn.reveal', '[' + self.attr_name() + ']', this.open_video)
          .on('close.fndtn.reveal', '[' + self.attr_name() + ']', this.settings.close)
          .on('closed.fndtn.reveal', '[' + self.attr_name() + ']', this.settings.closed)
          .on('closed.fndtn.reveal', '[' + self.attr_name() + ']', this.close_video);
      }

      return true;
    },

    // PATCH #3: turning on key up capture only when a reveal window is open
    key_up_on : function (scope) {
      var self = this;

      // PATCH #1: fixing multiple keyup event trigger from single key press
      self.S('body').off('keyup.fndtn.reveal').on('keyup.fndtn.reveal', function ( event ) {
        var open_modal = self.S('[' + self.attr_name() + '].open'),
            settings = open_modal.data(self.attr_name(true) + '-init') || self.settings ;
        // PATCH #2: making sure that the close event can be called only while unlocked,
        //           so that multiple keyup.fndtn.reveal events don't prevent clean closing of the reveal window.
        if ( settings && event.which === 27  && settings.close_on_esc && !self.locked) { // 27 is the keycode for the Escape key
          self.close.call(self, open_modal);
        }
      });

      return true;
    },

    // PATCH #3: turning on key up capture only when a reveal window is open
    key_up_off : function (scope) {
      this.S('body').off('keyup.fndtn.reveal');
      return true;
    },

    open : function (target, ajax_settings) {
      var self = this,
          modal;

      if (target) {
        if (typeof target.selector !== 'undefined') {
          // Find the named node; only use the first one found, since the rest of the code assumes there's only one node
          modal = self.S('#' + target.data(self.data_attr('reveal-id'))).first();
        } else {
          modal = self.S(this.scope);

          ajax_settings = target;
        }
      } else {
        modal = self.S(this.scope);
      }

      var settings = modal.data(self.attr_name(true) + '-init');
      settings = settings || this.settings;

      if (modal.hasClass('open') && target.attr('data-reveal-id') == modal.attr('id')) {
        return self.close(modal);
      }

      if (!modal.hasClass('open')) {
        var open_modal = self.S('[' + self.attr_name() + '].open');

        if (typeof modal.data('css-top') === 'undefined') {
          modal.data('css-top', parseInt(modal.css('top'), 10))
            .data('offset', this.cache_offset(modal));
        }

        this.key_up_on(modal);    // PATCH #3: turning on key up capture only when a reveal window is open
        modal.trigger('open').trigger('open.fndtn.reveal');

        if (open_modal.length < 1) {
          this.toggle_bg(modal, true);
        }

        if (typeof ajax_settings === 'string') {
          ajax_settings = {
            url: ajax_settings
          };
        }

        if (typeof ajax_settings === 'undefined' || !ajax_settings.url) {
          if (open_modal.length > 0) {
            this.hide(open_modal, settings.css.close);
          }

          this.show(modal, settings.css.open);
        } else {
          var old_success = typeof ajax_settings.success !== 'undefined' ? ajax_settings.success : null;

          $.extend(ajax_settings, {
            success: function (data, textStatus, jqXHR) {
              if ( $.isFunction(old_success) ) {
                old_success(data, textStatus, jqXHR);
              }

              modal.html(data);
              self.S(modal).foundation('section', 'reflow');
              self.S(modal).children().foundation();

              if (open_modal.length > 0) {
                self.hide(open_modal, settings.css.close);
              }
              self.show(modal, settings.css.open);
            }
          });

          $.ajax(ajax_settings);
        }
      }
      self.S(window).trigger('resize');
    },

    close : function (modal) {
      var modal = modal && modal.length ? modal : this.S(this.scope),
          open_modals = this.S('[' + this.attr_name() + '].open'),
          settings = modal.data(this.attr_name(true) + '-init') || this.settings;

      if (open_modals.length > 0) {
        this.locked = true;
        this.key_up_off(modal);   // PATCH #3: turning on key up capture only when a reveal window is open
        modal.trigger('close').trigger('close.fndtn.reveal');
        this.toggle_bg(modal, false);
        this.hide(open_modals, settings.css.close, settings);
      }
    },

    close_targets : function () {
      var base = '.' + this.settings.dismiss_modal_class;

      if (this.settings.close_on_background_click) {
        return base + ', .' + this.settings.bg_class;
      }

      return base;
    },

    toggle_bg : function (modal, state) {
      if (this.S('.' + this.settings.bg_class).length === 0) {
        this.settings.bg = $('<div />', {'class': this.settings.bg_class})
          .appendTo('body').hide();
      }

      var visible = this.settings.bg.filter(':visible').length > 0;
      if ( state != visible ) {
        if ( state == undefined ? visible : !state ) {
          this.hide(this.settings.bg);
        } else {
          this.show(this.settings.bg);
        }
      }
    },

    show : function (el, css) {
      // is modal
      if (css) {
        var settings = el.data(this.attr_name(true) + '-init') || this.settings,
            root_element = settings.root_element;

        if (el.parent(root_element).length === 0) {
          var placeholder = el.wrap('<div style="display: none;" />').parent();

          el.on('closed.fndtn.reveal.wrapped', function() {
            el.detach().appendTo(placeholder);
            el.unwrap().unbind('closed.fndtn.reveal.wrapped');
          });

          el.detach().appendTo(root_element);
        }

        var animData = getAnimationData(settings.animation);
        if (!animData.animate) {
          this.locked = false;
        }
        if (animData.pop) {
          css.top = $(window).scrollTop() - el.data('offset') + 'px';
          var end_css = {
            top: $(window).scrollTop() + el.data('css-top') + 'px',
            opacity: 1
          };

          return setTimeout(function () {
            return el
              .css(css)
              .animate(end_css, settings.animation_speed, 'linear', function () {
                this.locked = false;
                el.trigger('opened').trigger('opened.fndtn.reveal');
              }.bind(this))
              .addClass('open');
          }.bind(this), settings.animation_speed / 2);
        }

        if (animData.fade) {
          css.top = $(window).scrollTop() + el.data('css-top') + 'px';
          var end_css = {opacity: 1};

          return setTimeout(function () {
            return el
              .css(css)
              .animate(end_css, settings.animation_speed, 'linear', function () {
                this.locked = false;
                el.trigger('opened').trigger('opened.fndtn.reveal');
              }.bind(this))
              .addClass('open');
          }.bind(this), settings.animation_speed / 2);
        }

        return el.css(css).show().css({opacity: 1}).addClass('open').trigger('opened').trigger('opened.fndtn.reveal');
      }

      var settings = this.settings;

      // should we animate the background?
      if (getAnimationData(settings.animation).fade) {
        return el.fadeIn(settings.animation_speed / 2);
      }

      this.locked = false;

      return el.show();
    },

    hide : function (el, css) {
      // is modal
      if (css) {
        var settings = el.data(this.attr_name(true) + '-init');
        settings = settings || this.settings;

        var animData = getAnimationData(settings.animation);
        if (!animData.animate) {
          this.locked = false;
        }
        if (animData.pop) {
          var end_css = {
            top: - $(window).scrollTop() - el.data('offset') + 'px',
            opacity: 0
          };

          return setTimeout(function () {
            return el
              .animate(end_css, settings.animation_speed, 'linear', function () {
                this.locked = false;
                el.css(css).trigger('closed').trigger('closed.fndtn.reveal');
              }.bind(this))
              .removeClass('open');
          }.bind(this), settings.animation_speed / 2);
        }

        if (animData.fade) {
          var end_css = {opacity: 0};

          return setTimeout(function () {
            return el
              .animate(end_css, settings.animation_speed, 'linear', function () {
                this.locked = false;
                el.css(css).trigger('closed').trigger('closed.fndtn.reveal');
              }.bind(this))
              .removeClass('open');
          }.bind(this), settings.animation_speed / 2);
        }

        return el.hide().css(css).removeClass('open').trigger('closed').trigger('closed.fndtn.reveal');
      }

      var settings = this.settings;

      // should we animate the background?
      if (getAnimationData(settings.animation).fade) {
        return el.fadeOut(settings.animation_speed / 2);
      }

      return el.hide();
    },

    close_video : function (e) {
      var video = $('.flex-video', e.target),
          iframe = $('iframe', video);

      if (iframe.length > 0) {
        iframe.attr('data-src', iframe[0].src);
        iframe.attr('src', iframe.attr('src'));
        video.hide();
      }
    },

    open_video : function (e) {
      var video = $('.flex-video', e.target),
          iframe = video.find('iframe');

      if (iframe.length > 0) {
        var data_src = iframe.attr('data-src');
        if (typeof data_src === 'string') {
          iframe[0].src = iframe.attr('data-src');
        } else {
          var src = iframe[0].src;
          iframe[0].src = undefined;
          iframe[0].src = src;
        }
        video.show();
      }
    },

    data_attr: function (str) {
      if (this.namespace.length > 0) {
        return this.namespace + '-' + str;
      }

      return str;
    },

    cache_offset : function (modal) {
      var offset = modal.show().height() + parseInt(modal.css('top'), 10);

      modal.hide();

      return offset;
    },

    off : function () {
      $(this.scope).off('.fndtn.reveal');
    },

    reflow : function () {}
  };

  /*
   * getAnimationData('popAndFade') // {animate: true,  pop: true,  fade: true}
   * getAnimationData('fade')       // {animate: true,  pop: false, fade: true}
   * getAnimationData('pop')        // {animate: true,  pop: true,  fade: false}
   * getAnimationData('foo')        // {animate: false, pop: false, fade: false}
   * getAnimationData(null)         // {animate: false, pop: false, fade: false}
   */
  function getAnimationData(str) {
    var fade = /fade/i.test(str);
    var pop = /pop/i.test(str);
    return {
      animate: fade || pop,
      pop: pop,
      fade: fade
    };
  }
}(jQuery, window, window.document));
<commit_msg>[foundation.reveal.js] replace bind(this) with local context variable<commit_after>;(function ($, window, document, undefined) {
  'use strict';

  Foundation.libs.reveal = {
    name : 'reveal',

    version : '{{VERSION}}',

    locked : false,

    settings : {
      animation: 'fadeAndPop',
      animation_speed: 250,
      close_on_background_click: true,
      close_on_esc: true,
      dismiss_modal_class: 'close-reveal-modal',
      bg_class: 'reveal-modal-bg',
      root_element: 'body',
      open: function(){},
      opened: function(){},
      close: function(){},
      closed: function(){},
      bg : $('.reveal-modal-bg'),
      css : {
        open : {
          'opacity': 0,
          'visibility': 'visible',
          'display' : 'block'
        },
        close : {
          'opacity': 1,
          'visibility': 'hidden',
          'display': 'none'
        }
      }
    },

    init : function (scope, method, options) {
      $.extend(true, this.settings, method, options);
      this.bindings(method, options);
    },

    events : function (scope) {
      var self = this,
          S = self.S;

      S(this.scope)
        .off('.reveal')
        .on('click.fndtn.reveal', '[' + this.add_namespace('data-reveal-id') + ']:not([disabled])', function (e) {
          e.preventDefault();

          if (!self.locked) {
            var element = S(this),
                ajax = element.data(self.data_attr('reveal-ajax'));

            self.locked = true;

            if (typeof ajax === 'undefined') {
              self.open.call(self, element);
            } else {
              var url = ajax === true ? element.attr('href') : ajax;

              self.open.call(self, element, {url: url});
            }
          }
        });

      S(document)
        .on('click.fndtn.reveal', this.close_targets(), function (e) {

          e.preventDefault();

          if (!self.locked) {
            var settings = S('[' + self.attr_name() + '].open').data(self.attr_name(true) + '-init') || self.settings,
                bg_clicked = S(e.target)[0] === S('.' + settings.bg_class)[0];

            if (bg_clicked) {
              if (settings.close_on_background_click) {
                e.stopPropagation();
              } else {
                return;
              }
            }

            self.locked = true;
            self.close.call(self, bg_clicked ? S('[' + self.attr_name() + '].open') : S(this).closest('[' + self.attr_name() + ']'));
          }
        });

      if(S('[' + self.attr_name() + ']', this.scope).length > 0) {
        S(this.scope)
          // .off('.reveal')
          .on('open.fndtn.reveal', this.settings.open)
          .on('opened.fndtn.reveal', this.settings.opened)
          .on('opened.fndtn.reveal', this.open_video)
          .on('close.fndtn.reveal', this.settings.close)
          .on('closed.fndtn.reveal', this.settings.closed)
          .on('closed.fndtn.reveal', this.close_video);
      } else {
        S(this.scope)
          // .off('.reveal')
          .on('open.fndtn.reveal', '[' + self.attr_name() + ']', this.settings.open)
          .on('opened.fndtn.reveal', '[' + self.attr_name() + ']', this.settings.opened)
          .on('opened.fndtn.reveal', '[' + self.attr_name() + ']', this.open_video)
          .on('close.fndtn.reveal', '[' + self.attr_name() + ']', this.settings.close)
          .on('closed.fndtn.reveal', '[' + self.attr_name() + ']', this.settings.closed)
          .on('closed.fndtn.reveal', '[' + self.attr_name() + ']', this.close_video);
      }

      return true;
    },

    // PATCH #3: turning on key up capture only when a reveal window is open
    key_up_on : function (scope) {
      var self = this;

      // PATCH #1: fixing multiple keyup event trigger from single key press
      self.S('body').off('keyup.fndtn.reveal').on('keyup.fndtn.reveal', function ( event ) {
        var open_modal = self.S('[' + self.attr_name() + '].open'),
            settings = open_modal.data(self.attr_name(true) + '-init') || self.settings ;
        // PATCH #2: making sure that the close event can be called only while unlocked,
        //           so that multiple keyup.fndtn.reveal events don't prevent clean closing of the reveal window.
        if ( settings && event.which === 27  && settings.close_on_esc && !self.locked) { // 27 is the keycode for the Escape key
          self.close.call(self, open_modal);
        }
      });

      return true;
    },

    // PATCH #3: turning on key up capture only when a reveal window is open
    key_up_off : function (scope) {
      this.S('body').off('keyup.fndtn.reveal');
      return true;
    },

    open : function (target, ajax_settings) {
      var self = this,
          modal;

      if (target) {
        if (typeof target.selector !== 'undefined') {
          // Find the named node; only use the first one found, since the rest of the code assumes there's only one node
          modal = self.S('#' + target.data(self.data_attr('reveal-id'))).first();
        } else {
          modal = self.S(this.scope);

          ajax_settings = target;
        }
      } else {
        modal = self.S(this.scope);
      }

      var settings = modal.data(self.attr_name(true) + '-init');
      settings = settings || this.settings;

      if (modal.hasClass('open') && target.attr('data-reveal-id') == modal.attr('id')) {
        return self.close(modal);
      }

      if (!modal.hasClass('open')) {
        var open_modal = self.S('[' + self.attr_name() + '].open');

        if (typeof modal.data('css-top') === 'undefined') {
          modal.data('css-top', parseInt(modal.css('top'), 10))
            .data('offset', this.cache_offset(modal));
        }

        this.key_up_on(modal);    // PATCH #3: turning on key up capture only when a reveal window is open
        modal.trigger('open').trigger('open.fndtn.reveal');

        if (open_modal.length < 1) {
          this.toggle_bg(modal, true);
        }

        if (typeof ajax_settings === 'string') {
          ajax_settings = {
            url: ajax_settings
          };
        }

        if (typeof ajax_settings === 'undefined' || !ajax_settings.url) {
          if (open_modal.length > 0) {
            this.hide(open_modal, settings.css.close);
          }

          this.show(modal, settings.css.open);
        } else {
          var old_success = typeof ajax_settings.success !== 'undefined' ? ajax_settings.success : null;

          $.extend(ajax_settings, {
            success: function (data, textStatus, jqXHR) {
              if ( $.isFunction(old_success) ) {
                old_success(data, textStatus, jqXHR);
              }

              modal.html(data);
              self.S(modal).foundation('section', 'reflow');
              self.S(modal).children().foundation();

              if (open_modal.length > 0) {
                self.hide(open_modal, settings.css.close);
              }
              self.show(modal, settings.css.open);
            }
          });

          $.ajax(ajax_settings);
        }
      }
      self.S(window).trigger('resize');
    },

    close : function (modal) {
      var modal = modal && modal.length ? modal : this.S(this.scope),
          open_modals = this.S('[' + this.attr_name() + '].open'),
          settings = modal.data(this.attr_name(true) + '-init') || this.settings;

      if (open_modals.length > 0) {
        this.locked = true;
        this.key_up_off(modal);   // PATCH #3: turning on key up capture only when a reveal window is open
        modal.trigger('close').trigger('close.fndtn.reveal');
        this.toggle_bg(modal, false);
        this.hide(open_modals, settings.css.close, settings);
      }
    },

    close_targets : function () {
      var base = '.' + this.settings.dismiss_modal_class;

      if (this.settings.close_on_background_click) {
        return base + ', .' + this.settings.bg_class;
      }

      return base;
    },

    toggle_bg : function (modal, state) {
      if (this.S('.' + this.settings.bg_class).length === 0) {
        this.settings.bg = $('<div />', {'class': this.settings.bg_class})
          .appendTo('body').hide();
      }

      var visible = this.settings.bg.filter(':visible').length > 0;
      if ( state != visible ) {
        if ( state == undefined ? visible : !state ) {
          this.hide(this.settings.bg);
        } else {
          this.show(this.settings.bg);
        }
      }
    },

    show : function (el, css) {
      // is modal
      if (css) {
        var settings = el.data(this.attr_name(true) + '-init') || this.settings,
            root_element = settings.root_element,
            context = this;

        if (el.parent(root_element).length === 0) {
          var placeholder = el.wrap('<div style="display: none;" />').parent();

          el.on('closed.fndtn.reveal.wrapped', function() {
            el.detach().appendTo(placeholder);
            el.unwrap().unbind('closed.fndtn.reveal.wrapped');
          });

          el.detach().appendTo(root_element);
        }

        var animData = getAnimationData(settings.animation);
        if (!animData.animate) {
          this.locked = false;
        }
        if (animData.pop) {
          css.top = $(window).scrollTop() - el.data('offset') + 'px';
          var end_css = {
            top: $(window).scrollTop() + el.data('css-top') + 'px',
            opacity: 1
          };

          return setTimeout(function () {
            return el
              .css(css)
              .animate(end_css, settings.animation_speed, 'linear', function () {
                context.locked = false;
                el.trigger('opened').trigger('opened.fndtn.reveal');
              })
              .addClass('open');
          }, settings.animation_speed / 2);
        }

        if (animData.fade) {
          css.top = $(window).scrollTop() + el.data('css-top') + 'px';
          var end_css = {opacity: 1};

          return setTimeout(function () {
            return el
              .css(css)
              .animate(end_css, settings.animation_speed, 'linear', function () {
                context.locked = false;
                el.trigger('opened').trigger('opened.fndtn.reveal');
              })
              .addClass('open');
          }, settings.animation_speed / 2);
        }

        return el.css(css).show().css({opacity: 1}).addClass('open').trigger('opened').trigger('opened.fndtn.reveal');
      }

      var settings = this.settings;

      // should we animate the background?
      if (getAnimationData(settings.animation).fade) {
        return el.fadeIn(settings.animation_speed / 2);
      }

      this.locked = false;

      return el.show();
    },

    hide : function (el, css) {
      // is modal
      if (css) {
        var settings = el.data(this.attr_name(true) + '-init'),
            context = this;
        settings = settings || this.settings;

        var animData = getAnimationData(settings.animation);
        if (!animData.animate) {
          this.locked = false;
        }
        if (animData.pop) {
          var end_css = {
            top: - $(window).scrollTop() - el.data('offset') + 'px',
            opacity: 0
          };

          return setTimeout(function () {
            return el
              .animate(end_css, settings.animation_speed, 'linear', function () {
                context.locked = false;
                el.css(css).trigger('closed').trigger('closed.fndtn.reveal');
              })
              .removeClass('open');
          }, settings.animation_speed / 2);
        }

        if (animData.fade) {
          var end_css = {opacity: 0};

          return setTimeout(function () {
            return el
              .animate(end_css, settings.animation_speed, 'linear', function () {
                context.locked = false;
                el.css(css).trigger('closed').trigger('closed.fndtn.reveal');
              })
              .removeClass('open');
          }, settings.animation_speed / 2);
        }

        return el.hide().css(css).removeClass('open').trigger('closed').trigger('closed.fndtn.reveal');
      }

      var settings = this.settings;

      // should we animate the background?
      if (getAnimationData(settings.animation).fade) {
        return el.fadeOut(settings.animation_speed / 2);
      }

      return el.hide();
    },

    close_video : function (e) {
      var video = $('.flex-video', e.target),
          iframe = $('iframe', video);

      if (iframe.length > 0) {
        iframe.attr('data-src', iframe[0].src);
        iframe.attr('src', iframe.attr('src'));
        video.hide();
      }
    },

    open_video : function (e) {
      var video = $('.flex-video', e.target),
          iframe = video.find('iframe');

      if (iframe.length > 0) {
        var data_src = iframe.attr('data-src');
        if (typeof data_src === 'string') {
          iframe[0].src = iframe.attr('data-src');
        } else {
          var src = iframe[0].src;
          iframe[0].src = undefined;
          iframe[0].src = src;
        }
        video.show();
      }
    },

    data_attr: function (str) {
      if (this.namespace.length > 0) {
        return this.namespace + '-' + str;
      }

      return str;
    },

    cache_offset : function (modal) {
      var offset = modal.show().height() + parseInt(modal.css('top'), 10);

      modal.hide();

      return offset;
    },

    off : function () {
      $(this.scope).off('.fndtn.reveal');
    },

    reflow : function () {}
  };

  /*
   * getAnimationData('popAndFade') // {animate: true,  pop: true,  fade: true}
   * getAnimationData('fade')       // {animate: true,  pop: false, fade: true}
   * getAnimationData('pop')        // {animate: true,  pop: true,  fade: false}
   * getAnimationData('foo')        // {animate: false, pop: false, fade: false}
   * getAnimationData(null)         // {animate: false, pop: false, fade: false}
   */
  function getAnimationData(str) {
    var fade = /fade/i.test(str);
    var pop = /pop/i.test(str);
    return {
      animate: fade || pop,
      pop: pop,
      fade: fade
    };
  }
}(jQuery, window, window.document));
<|endoftext|>