<commit_before>var pdata;
var ppath;

d3.csv("data/group-avgs.csv", function(error, csv) {
  var data = csv;

  for(var i=0; i<data.length; i++){
    data[i].educ = +data[i].educ;
    data[i].leisure = +data[i].leisure;
    data[i].pcare = +data[i].pcare;
    data[i].social = +data[i].social;
    data[i].sports = +data[i].sports;
    data[i].work = +data[i].work;
  }

  pdata=data;

  createVis();
});

// from http://bl.ocks.org/jasondavies/1341281
function createVis() {
  var margin = {top: 30, right: 10, bottom: 10, left: 10},
    width = 800 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

  var x = d3.scale.ordinal().rangePoints([0, width], 1),
    y = {},
    dragging = {};

  var line = d3.svg.line(),
    axis = d3.svg.axis().orient("left"),
    background,
    foreground;

  var svg = d3.select("#parallel-coordinates").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  // Extract the list of dimensions and create a scale for each.
  x.domain(dimensions = d3.keys(pdata[0]).filter(function(d) {
    return d != "Group.1" && d != "type" && (y[d] = d3.scale.linear()
        .domain(d3.extent(pdata, function(p) { return +p[d]; }))
        .range([height, 0]));
  }));

  // Add grey background lines for context.
  background = svg.append("g")
    .attr("class", "background")
    .selectAll("path")
    .data(pdata)
    .enter().append("path")
    .attr("d", path);

  //console.log($("a:checked"));

  var displayData = pdata.filter(function(item){
    return (item["Group.1"]=="Female" || item["Group.1"]=="Male");
  })

  //console.log(displayData);

  // Add blue foreground lines for focus.
  foreground = svg.append("g")
    .attr("class", "foreground")
    .selectAll("path")
    .data(displayData)
    .enter().append("path")
    .attr("class", "forepath")
    .attr("stroke", function(d, i) {return color(i);})
    .attr("d", path);

  ppath=foreground["0"]["0"];

  // Add a group element for each dimension.
  var g = svg.selectAll(".dimension")
    .data(dimensions)
    .enter().append("g")
    .attr("class", "dimension")
    .attr("transform", function(d) { return "translate(" + x(d) + ")"; })
    .call(d3.behavior.drag()
      .origin(function(d) { return {x: x(d)}; })
      .on("dragstart", function(d) {
        dragging[d] = x(d);
        background.attr("visibility", "hidden");
      })
      .on("drag", function(d) {
        dragging[d] = Math.min(width, Math.max(0, d3.event.x));
        foreground.attr("d", path);
        dimensions.sort(function(a, b) { return position(a) - position(b); });
        x.domain(dimensions);
        g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
      })
      .on("dragend", function(d) {
        delete dragging[d];
        transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
        transition(foreground).attr("d", path);
        background
          .attr("d", path)
          .transition()
          .delay(500)
          .duration(0)
          .attr("visibility", null);
      }));

  // Add an axis and title.
  g.append("g")
    .attr("class", "axis")
    .each(function(d) { d3.select(this).call(axis.scale(y[d])); })
    .append("text")
    .style("text-anchor", "middle")
    .attr("y", -9)
    .text(function(d) { return d; });

  // Add and store a brush for each axis.
  g.append("g")
    .attr("class", "brush")
    .each(function(d) {
      d3.select(this).call(y[d].brush = d3.svg.brush().y(y[d]).on("brushstart", brushstart).on("brush", brush));
    })
    .selectAll("rect")
    .attr("x", -8)
    .attr("width", 16);

  function position(d) {
    var v = dragging[d];
    return v == null ? x(d) : v;
  }

  function transition(g) {
    return g.transition().duration(500);
  }

  // Returns the path for a given data point.
  function path(d) {
    return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
  }

  function brushstart() {
    d3.event.sourceEvent.stopPropagation();
  }

  // Handles a brush event, toggling the display of foreground lines.
  function brush() {
    var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
      extents = actives.map(function(p) { return y[p].brush.extent(); });
    foreground.style("display", function(d) {
      return actives.every(function(p, i) {
        return extents[i][0] <= d[p] && d[p] <= extents[i][1];
      }) ? null : "none";
    });
  }

}

function updatePath(selectedValue) {

  var displayData = pdata.filter(function(item){
    return (item["type"]==selectedValue);
  });

  //d3.select("#parallel-coordinates").remove();
  var lines = d3.select("#parallel-coordinates").selectAll("*");

  lines.remove();

  // Add blue foreground lines for focus.
  //lines
  //  .selectAll("path")
  //  .data(displayData)
  //  .enter().append("path")
  //  .attr("class", "forepath")
  //  .attr("stroke", function(d, i) {return color(i);})
  //  .attr("d", path);
  //var lines = svg.selectAll(".forepath").data(displayData);
  //
  //
  //lines
  //  .enter().append("path")
  //  .attr("class", "forepath")
  //  .attr("stroke", function(d, i) {return color(i);})
  //  .attr("d", ppath);
  //
  //
  //lines.transition().duration(1100)
  //  .attr("stroke", "red");

  //lines.exit().remove();
  var margin = {top: 30, right: 10, bottom: 10, left: 10},
    width = 800 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

  var x = d3.scale.ordinal().rangePoints([0, width], 1),
    y = {},
    dragging = {};

  var line = d3.svg.line(),
    axis = d3.svg.axis().orient("left"),
    background,
    foreground;

  var svg = d3.select("#parallel-coordinates").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  // Extract the list of dimensions and create a scale for each.
  x.domain(dimensions = d3.keys(pdata[0]).filter(function(d) {
    return d != "Group.1" && d != "type" && (y[d] = d3.scale.linear()
        .domain(d3.extent(pdata, function(p) { return +p[d]; }))
        .range([height, 0]));
  }));

  // Add grey background lines for context.
  background = svg.append("g")
    .attr("class", "background")
    .selectAll("path")
    .data(pdata)
    .enter().append("path")
    .attr("d", path);

  // Add blue foreground lines for focus.
  foreground = svg.append("g")
    .attr("class", "foreground")
    .selectAll("path.forepath")
    .data(displayData)
    .enter().append("path")
    .attr("class", "forepath")
    .attr("stroke", function(d, i) {return color(i);})
    .attr("d", path);

  // Add a group element for each dimension.
  var g = svg.selectAll(".dimension")
    .data(dimensions)
    .enter().append("g")
    .attr("class", "dimension")
    .attr("transform", function(d) { return "translate(" + x(d) + ")"; })
    .call(d3.behavior.drag()
      .origin(function(d) { return {x: x(d)}; })
      .on("dragstart", function(d) {
        dragging[d] = x(d);
        background.attr("visibility", "hidden");
      })
      .on("drag", function(d) {
        dragging[d] = Math.min(width, Math.max(0, d3.event.x));
        foreground.attr("d", path);
        dimensions.sort(function(a, b) { return position(a) - position(b); });
        x.domain(dimensions);
        g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
      })
      .on("dragend", function(d) {
        delete dragging[d];
        transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
        transition(foreground).attr("d", path);
        background
          .attr("d", path)
          .transition()
          .delay(500)
          .duration(0)
          .attr("visibility", null);
      }));

  // Add an axis and title.
  g.append("g")
    .attr("class", "axis")
    .each(function(d) { d3.select(this).call(axis.scale(y[d])); })
    .append("text")
    .style("text-anchor", "middle")
    .attr("y", -9)
    .text(function(d) { return d; });

  // Add and store a brush for each axis.
  g.append("g")
    .attr("class", "brush")
    .each(function(d) {
      d3.select(this).call(y[d].brush = d3.svg.brush().y(y[d]).on("brushstart", brushstart).on("brush", brush));
    })
    .selectAll("rect")
    .attr("x", -8)
    .attr("width", 16);

  function position(d) {
    var v = dragging[d];
    return v == null ? x(d) : v;
  }

  function transition(g) {
    return g.transition().duration(500);
  }

  // Returns the path for a given data point.
  function path(d) {
    return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
  }

  function brushstart() {
    d3.event.sourceEvent.stopPropagation();
  }

  // Handles a brush event, toggling the display of foreground lines.
  function brush() {
    var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
      extents = actives.map(function(p) { return y[p].brush.extent(); });
    foreground.style("display", function(d) {
      return actives.every(function(p, i) {
        return extents[i][0] <= d[p] && d[p] <= extents[i][1];
      }) ? null : "none";
    });
  }

}
<commit_msg>Updated tick format<commit_after>var pdata;
var ppath;
var format = d3.format("%")

d3.csv("data/group-avgs.csv", function(error, csv) {
  var data = csv;

  for(var i=0; i<data.length; i++){
    data[i].educ = +data[i].educ/1440;
    data[i].leisure = +data[i].leisure/1440;
    data[i].pcare = +data[i].pcare/1440;
    data[i].social = +data[i].social/1440;
    data[i].sports = +data[i].sports/1440;
    data[i].work = +data[i].work/1440;
  }

  pdata=data;

  updatePath("gender");
});

// from http://bl.ocks.org/jasondavies/1341281
function updatePath(selectedValue) {

  var displayData = pdata.filter(function(item){
    return (item["type"]==selectedValue);
  });

  // http://stackoverflow.com/questions/14422198/how-do-i-remove-all-children-elements-from-a-node-and-them-apply-them-again-with
  d3.select("#parallel-coordinates").selectAll("*").remove();

  var margin = {top: 30, right: 10, bottom: 10, left: 10},
    width = 800 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

  var x = d3.scale.ordinal().rangePoints([0, width], 1),
    y = {},
    dragging = {};

  var line = d3.svg.line(),
    axis = d3.svg.axis().orient("left"),
    background,
    foreground;

  var svg = d3.select("#parallel-coordinates").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  // Extract the list of dimensions and create a scale for each.
  x.domain(dimensions = d3.keys(pdata[0]).filter(function(d) {
    return d != "Group.1" && d != "type" && (y[d] = d3.scale.linear()
        .domain(d3.extent(pdata, function(p) { return +p[d]; }))
        .range([height, 0]));
  }));

  // Add grey background lines for context.
  background = svg.append("g")
    .attr("class", "background")
    .selectAll("path")
    .data(pdata)
    .enter().append("path")
    .attr("d", path);

  // Add blue foreground lines for focus.
  foreground = svg.append("g")
    .attr("class", "foreground")
    .selectAll("path.forepath")
    .data(displayData)
    .enter().append("path")
    //.attr("stroke", function(d, i) {return "#ddd";})
    .transition()
    .duration(800)
    .attr("class", "forepath")
    .attr("stroke", function(d, i) {return color(i);})
    .attr("d", path);

  // Add a group element for each dimension.
  var g = svg.selectAll(".dimension")
    .data(dimensions)
    .enter().append("g")
    .attr("class", "dimension")
    .attr("transform", function(d) { return "translate(" + x(d) + ")"; })
    .call(d3.behavior.drag()
      .origin(function(d) { return {x: x(d)}; })
      .on("dragstart", function(d) {
        dragging[d] = x(d);
        background.attr("visibility", "hidden");
      })
      .on("drag", function(d) {
        dragging[d] = Math.min(width, Math.max(0, d3.event.x));
        foreground.attr("d", path);
        dimensions.sort(function(a, b) { return position(a) - position(b); });
        x.domain(dimensions);
        g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
      })
      .on("dragend", function(d) {
        delete dragging[d];
        transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
        transition(foreground).attr("d", path);
        background
          .attr("d", path)
          .transition()
          .delay(500)
          .duration(0)
          .attr("visibility", null);
      }));

  // Add an axis and title.
  g.append("g")
    .attr("class", "axis")
    .each(function(d) { d3.select(this).call(axis.scale(y[d]).tickFormat(format)); })
    .append("text")
    .style("text-anchor", "middle")
    .attr("y", -9)
    .text(function(d) { return d; });

  // Add and store a brush for each axis.
  g.append("g")
    .attr("class", "brush")
    .each(function(d) {
      d3.select(this).call(y[d].brush = d3.svg.brush().y(y[d]).on("brushstart", brushstart).on("brush", brush));
    })
    .selectAll("rect")
    .attr("x", -8)
    .attr("width", 16);

  function position(d) {
    var v = dragging[d];
    return v == null ? x(d) : v;
  }

  function transition(g) {
    return g.transition().duration(500);
  }

  // Returns the path for a given data point.
  function path(d) {
    return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
  }

  function brushstart() {
    d3.event.sourceEvent.stopPropagation();
  }

  // Handles a brush event, toggling the display of foreground lines.
  function brush() {
    var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
      extents = actives.map(function(p) { return y[p].brush.extent(); });
    foreground.style("display", function(d) {
      return actives.every(function(p, i) {
        return extents[i][0] <= d[p] && d[p] <= extents[i][1];
      }) ? null : "none";
    });
  }

}
<|endoftext|>


<commit_before>// Copyright 2019-2021, University of Colorado Boulder

/**
 * This singleton is responsible for ensuring that the PhET-iO API is correct through the lifetime of the simulation.
 * The PhET-iO API is defined through multiple preloaded files. The "elements baseline" API holds an exact match of
 * what PhetioObject instances/metadata the sim should create on startup, where the "elements overrides" file is a
 * sparse list that can overwrite metadata without changing the code. See `grunt generate-phet-io-api` for
 * more information. The complete list of checks was decided on in https://github.com/phetsims/phet-io/issues/1453
 * (and later trimmed down) and is as follows:
 *
 * 1. After startup, only dynamic instances prescribed by the baseline API can be registered.
 * 2. Any static, registered PhetioObject can never be deregistered.
 * 3. Any schema entries in the overrides file must exist in the baseline API
 * 4. Any schema entries in the overrides file must be different from its baseline counterpart
 * 5. Dynamic element metadata should match the archetype in the API.
 *
 * Terminology:
 * schema: specified through preloads. The full schema is the baseline plus the overrides, but those parts can be
 *         referred to separately.
 * registered: the process of instrumenting a PhetioObject and it "becoming" a PhET-iO Element on the wrapper side.
 * static PhetioObject: A registered PhetioObject that exists for the lifetime of the sim. It should not be removed
 *                      (even intermittently) and must be created during startup so that it is immediately interoperable.
 * dynamic PhetioObject: A registered PhetioObject that can be created and/or destroyed at any point. Only dynamic
 *                       PhetioObjects can be created after startup.
 *
 * See https://github.com/phetsims/phet-io/issues/1443#issuecomment-484306552 for an explanation of how to maintain the
 * PhET-iO API for a simulation.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Chris Klusendorf (PhET Interactive Simulations)
 */

import DynamicTandem from './DynamicTandem.js';
import Tandem from './Tandem.js';
import tandemNamespace from './tandemNamespace.js';

// constants
// The API-tracked and validated metadata keys
const KEYS_TO_CHECK = [
  'phetioDynamicElement',
  'phetioEventType',
  'phetioIsArchetype',
  'phetioPlayback',
  'phetioReadOnly',
  'phetioState',
  'phetioTypeName'
];

class PhetioAPIValidation {

  constructor() {

    /**
     * {Object[]} - Each object holds a single "API mismatch" with the following keys:
     *                phetioID: {string}
     *                stack: {string} - for a stack trace
     *                ruleInViolation: {string} - one of the numbered list in the header doc.
     *                [message]: {string} - specific problem
     *
     * Feel free to add any other JSONifyable keys to this to make the error more clear! All mismatches are printed
     * at once for clarity, see PhetioEngine.
     * @private
     */
    this.apiMismatches = [];

    // @private - keep track of when the sim has started.
    this.simHasStarted = false;

    // @public {boolean} - settable by qunitStart.js. Validation is only enabled when all screens are present.
    this.enabled = assert && Tandem.VALIDATION;

    // @private {Object.<typeName:string, IOType>} - this must be all phet-io types so that the
    // following would fail: add a phetioType, then remove it, then add a different one under the same typeName.
    // A Note about memory: Every IOType that is loaded as a module is already loaded on the namespace. Therefore
    // this map doesn't add any memory by storing these. The exception to this is parametric IOTypes. It should be
    // double checked that anything being passed into a parametric type is memory safe. As of this writing, only IOTypes
    // are passed to parametric IOTypes, so this pattern remains memory leak free. Furthermore, this list is only
    // populated when `this.enabled`.
    this.everyPhetioType = {};
  }

  /**
   * Callback when the simulation is ready to go, and all static PhetioObjects have been created.
   * @param {PhetioEngine} phetioEngine
   * @public
   */
  onSimStarted( phetioEngine ) {
    this.simHasStarted = true;
    if ( this.enabled && phet.joist.sim.allScreensCreated ) {
      this.validateOverridesFile();
    }
  }

  /**
   * Checks if a removed phetioObject is part of a Group
   * @param {PhetioObject} phetioObject
   * @public
   */
  onPhetioObjectRemoved( phetioObject ) {
    if ( !this.enabled ) {
      return;
    }

    const phetioID = phetioObject.tandem.phetioID;

    // if it isn't dynamic, then it shouldn't be removed during the lifetime of the sim.
    if ( !phetioObject.phetioDynamicElement ) {
      this.assertAPIError( {
        phetioID: phetioID,
        ruleInViolation: '2. Any static, registered PhetioObject can never be deregistered.'
      } );
    }
  }

  /**
   * Should be called from phetioEngine when a PhetioObject is added to the PhET-iO
   * @param {PhetioObject} phetioObject
   * @public
   */
  onPhetioObjectAdded( phetioObject ) {
    if ( !this.enabled ) {
      return;
    }

    const newPhetioType = phetioObject.phetioType;
    const oldPhetioType = this.everyPhetioType[ newPhetioType.typeName ];

    if ( !oldPhetioType ) { // This may not be necessary, but may be helpful so that we don't overwrite if rule 10 is in violation
      this.everyPhetioType[ newPhetioType.typeName ] = newPhetioType;
    }

    if ( this.simHasStarted ) {

      // Here we need to kick this validation to the next frame to support construction in any order. Parent first, or
      // child first. Use namespace to avoid because timer is a PhetioObject.
      phet.axon.animationFrameTimer.runOnNextTick( () => {

        // The only instances that it's OK to create after startup are "dynamic instances" which are marked as such.
        if ( !phetioObject.phetioDynamicElement ) {
          this.assertAPIError( {
            phetioID: phetioObject.tandem.phetioID,
            ruleInViolation: '1. After startup, only dynamic instances prescribed by the baseline file can be registered.'
          } );
        }
        else {

          // Compare the dynamic element to the archetype if creating them this runtime.
          if ( phet.preloads.phetio.createArchetypes ) {
            const archetypeID = phetioObject.tandem.getArchetypalPhetioID();
            const archetypeMetadata = phet.phetio.phetioEngine.getPhetioObject( archetypeID ).getMetadata();

            // Compare to the simulation-defined archetype
            checkDynamicInstanceAgainstArchetype( this, phetioObject, archetypeMetadata, 'simulation archetype' );
          }
        }
      } );
    }
  }

  /**
   * @private
   */
  validateOverridesFile() {

    // import phetioEngine causes a cycle and cannot be used, hence we must use the namespace
    const entireBaseline = phet.phetio.phetioEngine.getPhetioElementsBaseline();

    for ( const phetioID in window.phet.preloads.phetio.phetioElementsOverrides ) {
      const isArchetype = phetioID.indexOf( DynamicTandem.DYNAMIC_ARCHETYPE_NAME ) >= 0;
      if ( !phet.preloads.phetio.createArchetypes && !entireBaseline.hasOwnProperty( phetioID ) ) {
        assert && assert( isArchetype, `phetioID missing from the baseline that was not an archetype: ${phetioID}` );
      }
      else {
        if ( !entireBaseline.hasOwnProperty( phetioID ) ) {
          this.assertAPIError( {
            phetioID: phetioID,
            ruleInViolation: '3. Any schema entries in the overrides file must exist in the baseline file.',
            message: 'phetioID expected in the baseline file but does not exist'
          } );
        }
        else {

          const override = window.phet.preloads.phetio.phetioElementsOverrides[ phetioID ];
          const baseline = entireBaseline[ phetioID ];

          if ( Object.keys( override ).length === 0 ) {
            this.assertAPIError( {
              phetioID: phetioID,
              ruleInViolation: '4. Any schema entries in the overrides file must be different from its baseline counterpart.',
              message: 'no metadata keys found for this override.'
            } );
          }

          for ( const metadataKey in override ) {
            if ( !baseline.hasOwnProperty( metadataKey ) ) {
              this.assertAPIError( {
                phetioID: phetioID,
                ruleInViolation: '8. Any schema entries in the overrides file must be different from its baseline counterpart.',
                message: `phetioID metadata key not found in the baseline: ${metadataKey}`
              } );
            }

            if ( override[ metadataKey ] === baseline[ metadataKey ] ) {
              this.assertAPIError( {
                phetioID: phetioID,
                ruleInViolation: '8. Any schema entries in the overrides file must be different from its baseline counterpart.',
                message: 'phetioID metadata override value is the same as the corresponding metadata value in the baseline.'
              } );
            }
          }
        }
      }
    }
  }

  /**
   * Assert out the failed API validation rule.
   * @param {Object} apiErrorObject - see doc for this.apiMismatches
   * @private
   */
  assertAPIError( apiErrorObject ) {

    const mismatchMessage = apiErrorObject.phetioID ? `${apiErrorObject.phetioID}:  ${apiErrorObject.ruleInViolation}` :
                            `${apiErrorObject.ruleInViolation}`;

    console.log( 'error data:', apiErrorObject );
    assert && assert( false, `PhET-iO API error:\n${mismatchMessage}` );
  }
}

/**
 * Compare a dynamic phetioObject's metadata to the expected metadata
 * @param {phetioAPIValidation} phetioAPIValidation
 * @param {PhetioObject} phetioObject
 * @param {Object} archetypeMetadata - from an archetype of the dynamic element
 * @param {string} source - where the archetype came from, for debugging
 */
const checkDynamicInstanceAgainstArchetype = ( phetioAPIValidation, phetioObject, archetypeMetadata, source ) => {
  const actualMetadata = phetioObject.getMetadata();
  KEYS_TO_CHECK.forEach( key => {

    // These attributes are different for archetype vs actual
    if ( key !== 'phetioDynamicElement' && key !== 'phetioArchetypePhetioID' && key !== 'phetioIsArchetype' ) {

      if ( archetypeMetadata[ key ] !== actualMetadata[ key ] ) {
        phetioAPIValidation.assertAPIError( {
          phetioID: phetioObject.tandem.phetioID,
          ruleInViolation: '5. Dynamic element metadata should match the archetype in the API.',
          source: source,
          message: `mismatched metadata: ${key}`
        } );
      }
    }
  } );
};

const phetioAPIValidation = new PhetioAPIValidation();
tandemNamespace.register( 'phetioAPIValidation', phetioAPIValidation );
export default phetioAPIValidation;<commit_msg>don't worry about validating newly created dynamic elements that have already been deleted by next frame, https://github.com/phetsims/natural-selection/issues/311 https://github.com/phetsims/tandem/issues/264<commit_after>// Copyright 2019-2021, University of Colorado Boulder

/**
 * This singleton is responsible for ensuring that the PhET-iO API is correct through the lifetime of the simulation.
 * The PhET-iO API is defined through multiple preloaded files. The "elements baseline" API holds an exact match of
 * what PhetioObject instances/metadata the sim should create on startup, where the "elements overrides" file is a
 * sparse list that can overwrite metadata without changing the code. See `grunt generate-phet-io-api` for
 * more information. The complete list of checks was decided on in https://github.com/phetsims/phet-io/issues/1453
 * (and later trimmed down) and is as follows:
 *
 * 1. After startup, only dynamic instances prescribed by the baseline API can be registered.
 * 2. Any static, registered PhetioObject can never be deregistered.
 * 3. Any schema entries in the overrides file must exist in the baseline API
 * 4. Any schema entries in the overrides file must be different from its baseline counterpart
 * 5. Dynamic element metadata should match the archetype in the API.
 *
 * Terminology:
 * schema: specified through preloads. The full schema is the baseline plus the overrides, but those parts can be
 *         referred to separately.
 * registered: the process of instrumenting a PhetioObject and it "becoming" a PhET-iO Element on the wrapper side.
 * static PhetioObject: A registered PhetioObject that exists for the lifetime of the sim. It should not be removed
 *                      (even intermittently) and must be created during startup so that it is immediately interoperable.
 * dynamic PhetioObject: A registered PhetioObject that can be created and/or destroyed at any point. Only dynamic
 *                       PhetioObjects can be created after startup.
 *
 * See https://github.com/phetsims/phet-io/issues/1443#issuecomment-484306552 for an explanation of how to maintain the
 * PhET-iO API for a simulation.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Chris Klusendorf (PhET Interactive Simulations)
 */

import DynamicTandem from './DynamicTandem.js';
import Tandem from './Tandem.js';
import tandemNamespace from './tandemNamespace.js';

// constants
// The API-tracked and validated metadata keys
const KEYS_TO_CHECK = [
  'phetioDynamicElement',
  'phetioEventType',
  'phetioIsArchetype',
  'phetioPlayback',
  'phetioReadOnly',
  'phetioState',
  'phetioTypeName'
];

class PhetioAPIValidation {

  constructor() {

    /**
     * {Object[]} - Each object holds a single "API mismatch" with the following keys:
     *                phetioID: {string}
     *                stack: {string} - for a stack trace
     *                ruleInViolation: {string} - one of the numbered list in the header doc.
     *                [message]: {string} - specific problem
     *
     * Feel free to add any other JSONifyable keys to this to make the error more clear! All mismatches are printed
     * at once for clarity, see PhetioEngine.
     * @private
     */
    this.apiMismatches = [];

    // @private - keep track of when the sim has started.
    this.simHasStarted = false;

    // @public {boolean} - settable by qunitStart.js. Validation is only enabled when all screens are present.
    this.enabled = assert && Tandem.VALIDATION;

    // @private {Object.<typeName:string, IOType>} - this must be all phet-io types so that the
    // following would fail: add a phetioType, then remove it, then add a different one under the same typeName.
    // A Note about memory: Every IOType that is loaded as a module is already loaded on the namespace. Therefore
    // this map doesn't add any memory by storing these. The exception to this is parametric IOTypes. It should be
    // double checked that anything being passed into a parametric type is memory safe. As of this writing, only IOTypes
    // are passed to parametric IOTypes, so this pattern remains memory leak free. Furthermore, this list is only
    // populated when `this.enabled`.
    this.everyPhetioType = {};
  }

  /**
   * Callback when the simulation is ready to go, and all static PhetioObjects have been created.
   * @param {PhetioEngine} phetioEngine
   * @public
   */
  onSimStarted( phetioEngine ) {
    this.simHasStarted = true;
    if ( this.enabled && phet.joist.sim.allScreensCreated ) {
      this.validateOverridesFile();
    }
  }

  /**
   * Checks if a removed phetioObject is part of a Group
   * @param {PhetioObject} phetioObject
   * @public
   */
  onPhetioObjectRemoved( phetioObject ) {
    if ( !this.enabled ) {
      return;
    }

    const phetioID = phetioObject.tandem.phetioID;

    // if it isn't dynamic, then it shouldn't be removed during the lifetime of the sim.
    if ( !phetioObject.phetioDynamicElement ) {
      this.assertAPIError( {
        phetioID: phetioID,
        ruleInViolation: '2. Any static, registered PhetioObject can never be deregistered.'
      } );
    }
  }

  /**
   * Should be called from phetioEngine when a PhetioObject is added to the PhET-iO
   * @param {PhetioObject} phetioObject
   * @public
   */
  onPhetioObjectAdded( phetioObject ) {
    if ( !this.enabled ) {
      return;
    }

    const newPhetioType = phetioObject.phetioType;
    const oldPhetioType = this.everyPhetioType[ newPhetioType.typeName ];

    if ( !oldPhetioType ) { // This may not be necessary, but may be helpful so that we don't overwrite if rule 10 is in violation
      this.everyPhetioType[ newPhetioType.typeName ] = newPhetioType;
    }

    if ( this.simHasStarted ) {

      // Here we need to kick this validation to the next frame to support construction in any order. Parent first, or
      // child first. Use namespace to avoid because timer is a PhetioObject.
      phet.axon.animationFrameTimer.runOnNextTick( () => {

        // The only instances that it's OK to create after startup are "dynamic instances" which are marked as such.
        if ( !phetioObject.phetioDynamicElement ) {
          this.assertAPIError( {
            phetioID: phetioObject.tandem.phetioID,
            ruleInViolation: '1. After startup, only dynamic instances prescribed by the baseline file can be registered.'
          } );
        }
        else {

          // Compare the dynamic element to the archetype if creating them this runtime. Don't check this if it has
          // already been disposed.
          if ( phet.preloads.phetio.createArchetypes && !phetioObject.isDisposed ) {
            const archetypeID = phetioObject.tandem.getArchetypalPhetioID();
            const archetypeMetadata = phet.phetio.phetioEngine.getPhetioObject( archetypeID ).getMetadata();

            // Compare to the simulation-defined archetype
            checkDynamicInstanceAgainstArchetype( this, phetioObject, archetypeMetadata, 'simulation archetype' );
          }
        }
      } );
    }
  }

  /**
   * @private
   */
  validateOverridesFile() {

    // import phetioEngine causes a cycle and cannot be used, hence we must use the namespace
    const entireBaseline = phet.phetio.phetioEngine.getPhetioElementsBaseline();

    for ( const phetioID in window.phet.preloads.phetio.phetioElementsOverrides ) {
      const isArchetype = phetioID.indexOf( DynamicTandem.DYNAMIC_ARCHETYPE_NAME ) >= 0;
      if ( !phet.preloads.phetio.createArchetypes && !entireBaseline.hasOwnProperty( phetioID ) ) {
        assert && assert( isArchetype, `phetioID missing from the baseline that was not an archetype: ${phetioID}` );
      }
      else {
        if ( !entireBaseline.hasOwnProperty( phetioID ) ) {
          this.assertAPIError( {
            phetioID: phetioID,
            ruleInViolation: '3. Any schema entries in the overrides file must exist in the baseline file.',
            message: 'phetioID expected in the baseline file but does not exist'
          } );
        }
        else {

          const override = window.phet.preloads.phetio.phetioElementsOverrides[ phetioID ];
          const baseline = entireBaseline[ phetioID ];

          if ( Object.keys( override ).length === 0 ) {
            this.assertAPIError( {
              phetioID: phetioID,
              ruleInViolation: '4. Any schema entries in the overrides file must be different from its baseline counterpart.',
              message: 'no metadata keys found for this override.'
            } );
          }

          for ( const metadataKey in override ) {
            if ( !baseline.hasOwnProperty( metadataKey ) ) {
              this.assertAPIError( {
                phetioID: phetioID,
                ruleInViolation: '8. Any schema entries in the overrides file must be different from its baseline counterpart.',
                message: `phetioID metadata key not found in the baseline: ${metadataKey}`
              } );
            }

            if ( override[ metadataKey ] === baseline[ metadataKey ] ) {
              this.assertAPIError( {
                phetioID: phetioID,
                ruleInViolation: '8. Any schema entries in the overrides file must be different from its baseline counterpart.',
                message: 'phetioID metadata override value is the same as the corresponding metadata value in the baseline.'
              } );
            }
          }
        }
      }
    }
  }

  /**
   * Assert out the failed API validation rule.
   * @param {Object} apiErrorObject - see doc for this.apiMismatches
   * @private
   */
  assertAPIError( apiErrorObject ) {

    const mismatchMessage = apiErrorObject.phetioID ? `${apiErrorObject.phetioID}:  ${apiErrorObject.ruleInViolation}` :
                            `${apiErrorObject.ruleInViolation}`;

    console.log( 'error data:', apiErrorObject );
    assert && assert( false, `PhET-iO API error:\n${mismatchMessage}` );
  }
}

/**
 * Compare a dynamic phetioObject's metadata to the expected metadata
 * @param {phetioAPIValidation} phetioAPIValidation
 * @param {PhetioObject} phetioObject
 * @param {Object} archetypeMetadata - from an archetype of the dynamic element
 * @param {string} source - where the archetype came from, for debugging
 */
const checkDynamicInstanceAgainstArchetype = ( phetioAPIValidation, phetioObject, archetypeMetadata, source ) => {
  const actualMetadata = phetioObject.getMetadata();
  KEYS_TO_CHECK.forEach( key => {

    // These attributes are different for archetype vs actual
    if ( key !== 'phetioDynamicElement' && key !== 'phetioArchetypePhetioID' && key !== 'phetioIsArchetype' ) {

      if ( archetypeMetadata[ key ] !== actualMetadata[ key ] ) {
        phetioAPIValidation.assertAPIError( {
          phetioID: phetioObject.tandem.phetioID,
          ruleInViolation: '5. Dynamic element metadata should match the archetype in the API.',
          source: source,
          message: `mismatched metadata: ${key}`
        } );
      }
    }
  } );
};

const phetioAPIValidation = new PhetioAPIValidation();
tandemNamespace.register( 'phetioAPIValidation', phetioAPIValidation );
export default phetioAPIValidation;<|endoftext|>


<commit_before>

/**
 * HYP : i < j
 */

var hoare = function ( compare , a , i , j ) {

	var x , t , o ;

	o = i ;
	x = a[o] ;

	while ( true ) {

		while ( true ) {

			--j ;

			if ( i >= j ) {
				a[o] = a[j] ;
				a[j] = x ;
				return j ;
			}

			else if ( compare( a[j] , x ) <= 0 ) {
				break ;
			}
		}

		while ( true ) {

			++i ;

			if ( i >= j ) {
				a[o] = a[j] ;
				a[j] = x ;
				return j ;
			}

			else if ( compare( x , a[i] ) <= 0 ) {
				break ;
			}
		}

		// invariant i < j

		t    = a[i] ;
		a[i] = a[j] ;
		a[j] =    t ;

	}

} ;

exports.hoare = hoare ;
exports.partition = hoare ;
<commit_msg>use >= rather than <=<commit_after>

/**
 * HYP : i < j
 */

var hoare = function ( compare , a , i , j ) {

	var x , t , o ;

	o = i ;
	x = a[o] ;

	while ( true ) {

		while ( true ) {

			--j ;

			if ( i >= j ) {
				a[o] = a[j] ;
				a[j] = x ;
				return j ;
			}

			else if ( compare( x , a[j] ) >= 0 ) {
				break ;
			}
		}

		while ( true ) {

			++i ;

			if ( i >= j ) {
				a[o] = a[j] ;
				a[j] = x ;
				return j ;
			}

			else if ( compare( x , a[i] ) <= 0 ) {
				break ;
			}
		}

		// invariant i < j

		t    = a[i] ;
		a[i] = a[j] ;
		a[j] =    t ;

	}

} ;

exports.hoare = hoare ;
exports.partition = hoare ;
<|endoftext|>


<commit_before>define(['logger','jquery', 'backbone', 'underscore', 'voc', 'userParams'],
function (Logger, $, Backbone, _, Voc, userParams) {
    return {
        LOG: Logger.get('EntityHelpers'),
        isSharedEpisode: function(episode) {
            var circleTypes = episode.get(Voc.circleTypes);

            if ( !_.isEmpty(circleTypes) ) {
                circleTypes = ( _.isArray(circleTypes) ) ? circleTypes : [circleTypes];
                if ( _.indexOf(circleTypes, 'group') !== -1 ) {
                    return true;
                }
            }

            return false;
        },
        getEpisodeVisibility: function(episode) {
            if ( this.isSharedEpisode(episode) ) {
                return 'shared';
            }

            return 'private';
        },
        getSharedWithNames: function(episode) {
            var sharedWithNames = [],
                sharedWith = episode.get(Voc.hasUsers);

            if ( !_.isEmpty(sharedWith) ) {
                sharedWith = ( _.isArray(sharedWith) ) ? sharedWith : [sharedWith];
                var currentUserUri = userParams.user;

                _.each(sharedWith, function(user) {
                    if ( user.getSubject() !== currentUserUri ) {
                        sharedWithNames.push(user.get(Voc.label));
                    }
                });
            }

            return sharedWithNames;
        },
        addBelongsToEpisode: function(entity, episode) {
            var belongsToEpisode = entity.get(Voc.belongsToEpisode),
                episodeSubject = episode.getSubject(),
                belongsToEpisodeUris = [];

            if ( _.isEmpty(belongsToEpisode) ) {
                entity.set(Voc.belongsToEpisode, episodeSubject);

                return true;
            } else {
                if ( !_.isArray(belongsToEpisode) ) {
                    belongsToEpisode = [belongsToEpisode];
                }

                _.each(belongsToEpisode, function(single) {
                    belongsToEpisodeUris.push(single.getSubject());
                });

                belongsToEpisodeUris.push(episodeSubject);
                entity.set(Voc.belongsToEpisode, belongsToEpisodeUris);

                return true;
            }

            return false;
        },
        removeBelongsToEpisode: function(entity, episode) {
            var belongsToEpisode = entity.get(Voc.belongsToEpisode),
                episodeSubject = episode.getSubject(),
                belongsToEpisodeUris = [];

            if ( _.isEmpty(belongsToEpisode) ) {
                return false;
            }

            if ( !_.isArray(belongsToEpisode) ) {
                belongsToEpisode = [belongsToEpisode];
            }

            _.each(belongsToEpisode, function(single) {
                belongsToEpisodeUris.push(single.getSubject());
            });

            var subjectIndex = _.indexOf(belongsToEpisodeUris, episodeSubject);
            if ( subjectIndex !== -1 ) {
                belongsToEpisodeUris.splice(subjectIndex, 1);
                entity.set(belongsToEpisodeUris);

                return true;
            }

            return false;
        }
    };
});
<commit_msg>LL-925 #comment Fixed a bug.<commit_after>define(['logger','jquery', 'backbone', 'underscore', 'voc', 'userParams'],
function (Logger, $, Backbone, _, Voc, userParams) {
    return {
        LOG: Logger.get('EntityHelpers'),
        isSharedEpisode: function(episode) {
            var circleTypes = episode.get(Voc.circleTypes);

            if ( !_.isEmpty(circleTypes) ) {
                circleTypes = ( _.isArray(circleTypes) ) ? circleTypes : [circleTypes];
                if ( _.indexOf(circleTypes, 'group') !== -1 ) {
                    return true;
                }
            }

            return false;
        },
        getEpisodeVisibility: function(episode) {
            if ( this.isSharedEpisode(episode) ) {
                return 'shared';
            }

            return 'private';
        },
        getSharedWithNames: function(episode) {
            var sharedWithNames = [],
                sharedWith = episode.get(Voc.hasUsers);

            if ( !_.isEmpty(sharedWith) ) {
                sharedWith = ( _.isArray(sharedWith) ) ? sharedWith : [sharedWith];
                var currentUserUri = userParams.user;

                _.each(sharedWith, function(user) {
                    if ( user.getSubject() !== currentUserUri ) {
                        sharedWithNames.push(user.get(Voc.label));
                    }
                });
            }

            return sharedWithNames;
        },
        addBelongsToEpisode: function(entity, episode) {
            var belongsToEpisode = entity.get(Voc.belongsToEpisode),
                episodeSubject = episode.getSubject(),
                belongsToEpisodeUris = [];

            if ( _.isEmpty(belongsToEpisode) ) {
                entity.set(Voc.belongsToEpisode, episodeSubject);

                return true;
            } else {
                if ( !_.isArray(belongsToEpisode) ) {
                    belongsToEpisode = [belongsToEpisode];
                }

                _.each(belongsToEpisode, function(single) {
                    belongsToEpisodeUris.push(single.getSubject());
                });

                belongsToEpisodeUris.push(episodeSubject);
                entity.set(Voc.belongsToEpisode, belongsToEpisodeUris);

                return true;
            }

            return false;
        },
        removeBelongsToEpisode: function(entity, episode) {
            var belongsToEpisode = entity.get(Voc.belongsToEpisode),
                episodeSubject = episode.getSubject(),
                belongsToEpisodeUris = [];

            if ( _.isEmpty(belongsToEpisode) ) {
                return false;
            }

            if ( !_.isArray(belongsToEpisode) ) {
                belongsToEpisode = [belongsToEpisode];
            }

            _.each(belongsToEpisode, function(single) {
                belongsToEpisodeUris.push(single.getSubject());
            });

            var subjectIndex = _.indexOf(belongsToEpisodeUris, episodeSubject);
            if ( subjectIndex !== -1 ) {
                belongsToEpisodeUris.splice(subjectIndex, 1);
                entity.set(Voc.belongsToEpisode, belongsToEpisodeUris);

                return true;
            }

            return false;
        }
    };
});
<|endoftext|>


<commit_before>/**
 * Created by Bill on 2016/10/18.
 */

define([], function(){

    var Bin = {
        on: function(element, type, handler, userCapture){
            if(document.attachEvent){
                element.attachEvent("on" + type, handler);
            }else if(document.addEventListener){
                element.addEventListener(type, handler, userCapture);
            }
        },

        off: function(element, type, handler, userCapture){
            if(document.detachEvent){
                element.detachEvent("on" + type, handler);
            }else if(document.removeEventListener){
                element.removeEventListener(type, handler, userCapture);
            }
        },

        debounce: function(idle, action){
            var last;
            return function(){
                clearTimeout(last);
                var ctx = this;
                var arg = arguments;
                last = setTimeout(function(){
                    return action.apply(ctx, arg);
                }, idle);
            };
        },

        throttle: function(delay, action){
            var last = 0;
            return function(){
                var curr = +new Date();
                if(curr-last > delay){
                    last = curr;
                    return action.apply(this, arguments);
                }
            };
        },

        ready: function(fn){
            if(document.attachEvent){
                document.attachEvent("onreadystatechange", function(){
                    if(document.readyState === "completed"){
                        document.detachEvent("onreadystatechange", arguments.callee);
                        fn();
                    }
                })
            }else if(document.addEventListener){
                document.addEventListener("DOMContentLoaded", function(){
                    document.removeEventListener("DOMContentLoaded", arguments.callee, false);
                    fn();
                }, false);
            }
        },

        customAjaxCall: function(method, data, url, async){
            if(method.toLowerCase() == "get"){
                return new Promise(function(resolve, reject){
                    var xhr = new XMLHttpRequest();
                    //xhr.timeout = 3000;
                    xhr.responseType = "json";
                    xhr.onreadystatechange = function(){
                        if(xhr.readyState == 4){
                            if(xhr.status == 200 || xhr.status == 304){
                                resolve(xhr.response);
                            }else{
                                reject(xhr.status);
                            }
                        }
                    };
                    xhr.open("GET", url, async);
                    xhr.send(null);
                });
            }else if(method.toLowerCase() == "post"){
                return new Promise(function(resolve, reject){
                    var xhr = new XMLHttpRequest();
                    //xhr.timeout = 3000;
                    xhr.responseType = "json";
                    xhr.onreadystatechange = function(){
                        if(xhr.readyState == 4){
                            if(xhr.status == 200 || xhr.status == 304){
                                resolve(xhr.response);
                            }else{
                                reject(xhr.status);
                            }
                        }
                    };
                    xhr.open("POST", url, async);
                    //xhr.setRequestHeader("Content-Type", "application/json");
                    //xhr.setRequestHeader("Content-Type", "mutipart/form-data");
                    xhr.send(data);
                });
            }
        },

        parseHTMLString: function(html, ctx, beforeNode){
            var divTemp = document.createElement("div");
            var nodes;
            var fragment = document.createDocumentFragment();

            divTemp.innerHTML = html;
            nodes = divTemp.childNodes;
            for(var i= 0, len=nodes.length; i<len; i++){
                fragment.appendChild(nodes[i].cloneNode(true));
            }
            ctx.insertBefore(fragment, beforeNode);

            nodes = null;
            fragment = null;
        }
    };

    var ScrollLoading = function(options){
        /*
         *  options parameters:
         *  1. viewport element
         *  2. spin element
         *  3. Ajax method
         *  4. Ajax url
         *  5. Ajax async
         *  6. Page
         */

        //变量初始化
        var page = options["page"] || 1;
        var loading = false;
        var viewport = options["viewport"] || document.querySelector(".viewport");
        var spin = options["spin"] || viewport.querySelector(".spin");
        var viewportHeight = viewport !== window ? viewport.clientHeight : window.innerHeight;

        //判断是否需要对页面进行scrollloading处理
        var contentHeight = viewport !== window ? viewport.scrollHeight : document.body.scrollHeight;
        if(viewportHeight === contentHeight){
            return;
        }

        //spin隐藏切换
        var toggleSpin = function(){
            var style = spin.getAttribute("class");
            if(style.indexOf("hidden") < 0){
                spin.setAttribute("class", style.replace(/(^\s+)|(\s+$)/g, "") + " hidden");
            }else{
                spin.setAttribute("class", style.replace(/hidden/, "").replace(/(^\s+)|(\s+$)/g, ""));
            }
        };

        //viewport滚动事件回调函数定义
        var scrollFn = function(){
            var scrollTop;
            if(viewport === window){
                scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
            }else{
                scrollTop = viewport.scrollTop;
            }

            var contentHeight = viewport !== window ? viewport.scrollHeight : document.body.scrollHeight;

            if((contentHeight - viewportHeight - scrollTop)/contentHeight < 0.01 && !loading){

                loading = true;

                if(spin.getAttribute("class").indexOf("hidden") >= 0){
                    spin.setAttribute("class", spin.getAttribute("class").replace(/hidden/, "").replace(/(^\s+)|(\s+$)/g, ""));
                }

                Bin.customAjaxCall("get", null, "/Bill.io/json/scrollLoading.json", true).then(function(value){
                    if(value["content"]){
                        var arrContent = value["content"];
                        var strContent = "";
                        arrContent.forEach(function(value, index){
                            strContent += '

' + (page * 20 + +value["content"])+ '

'
                        });
                        //console.log(strContent);
                        Bin.parseHTMLString(strContent, viewport, spin);
                        spin.setAttribute("class", spin.getAttribute("class").replace(/(^\s+)|(\s+$)/g, "") + " hidden");
                        page++;
                        loading = false;
                    }
                }, function(reason){
                    if(reason){
                        var strContent = '
加载错误>_<
';
                        console.log(reason);
                        Bin.parseHTMLString(strContent, viewport, spin);
                        spin.setAttribute("class", spin.getAttribute("class").replace(/(^\s+)|(\s+$)/g, "") + " hidden");
                    }
                });
            }
        };

        //函数节流处理
        var debouncedFn = Bin.debounce(20, scrollFn);

        //事件绑定
        Bin.on(viewport, "scroll", debouncedFn, false);
    };

    return ScrollLoading;

});
<commit_msg>scrollLoading issue fix<commit_after>/**
 * Created by Bill on 2016/10/18.
 */

define([], function(){

    var Bin = {
        on: function(element, type, handler, userCapture){
            if(document.attachEvent){
                element.attachEvent("on" + type, handler);
            }else if(document.addEventListener){
                element.addEventListener(type, handler, userCapture);
            }
        },

        off: function(element, type, handler, userCapture){
            if(document.detachEvent){
                element.detachEvent("on" + type, handler);
            }else if(document.removeEventListener){
                element.removeEventListener(type, handler, userCapture);
            }
        },

        debounce: function(idle, action){
            var last;
            return function(){
                clearTimeout(last);
                var ctx = this;
                var arg = arguments;
                last = setTimeout(function(){
                    return action.apply(ctx, arg);
                }, idle);
            };
        },

        throttle: function(delay, action){
            var last = 0;
            return function(){
                var curr = +new Date();
                if(curr-last > delay){
                    last = curr;
                    return action.apply(this, arguments);
                }
            };
        },

        ready: function(fn){
            if(document.attachEvent){
                document.attachEvent("onreadystatechange", function(){
                    if(document.readyState === "completed"){
                        document.detachEvent("onreadystatechange", arguments.callee);
                        fn();
                    }
                })
            }else if(document.addEventListener){
                document.addEventListener("DOMContentLoaded", function(){
                    document.removeEventListener("DOMContentLoaded", arguments.callee, false);
                    fn();
                }, false);
            }
        },

        customAjaxCall: function(method, data, url, async){
            if(method.toLowerCase() == "get"){
                return new Promise(function(resolve, reject){
                    var xhr = new XMLHttpRequest();
                    //xhr.timeout = 3000;
                    xhr.responseType = "json";
                    xhr.onreadystatechange = function(){
                        if(xhr.readyState == 4){
                            if(xhr.status == 200 || xhr.status == 304){
                                resolve(xhr.response);
                            }else{
                                reject(xhr.status);
                            }
                        }
                    };
                    xhr.open("GET", url, async);
                    xhr.send(null);
                });
            }else if(method.toLowerCase() == "post"){
                return new Promise(function(resolve, reject){
                    var xhr = new XMLHttpRequest();
                    //xhr.timeout = 3000;
                    xhr.responseType = "json";
                    xhr.onreadystatechange = function(){
                        if(xhr.readyState == 4){
                            if(xhr.status == 200 || xhr.status == 304){
                                resolve(xhr.response);
                            }else{
                                reject(xhr.status);
                            }
                        }
                    };
                    xhr.open("POST", url, async);
                    //xhr.setRequestHeader("Content-Type", "application/json");
                    //xhr.setRequestHeader("Content-Type", "mutipart/form-data");
                    xhr.send(data);
                });
            }
        },

        parseHTMLString: function(html, ctx, beforeNode){
            var divTemp = document.createElement("div");
            var nodes;
            var fragment = document.createDocumentFragment();

            divTemp.innerHTML = html;
            nodes = divTemp.childNodes;
            for(var i= 0, len=nodes.length; i<len; i++){
                fragment.appendChild(nodes[i].cloneNode(true));
            }
            ctx.insertBefore(fragment, beforeNode);

            nodes = null;
            fragment = null;
        }
    };

    var ScrollLoading = function(options){
        /*
         *  options parameters:
         *  1. viewport element
         *  2. spin element
         *  3. Ajax method
         *  4. Ajax url
         *  5. Ajax async
         *  6. Page
         */

        //变量初始化
        var page = options["page"] || 1;
        var loading = false;
        var viewport = options["viewport"] || document.querySelector(".viewport");
        var spin = options["spin"] || viewport.querySelector(".spin");
        var viewportHeight = viewport !== window ? viewport.clientHeight : window.innerHeight;

        //判断是否需要对页面进行scrollloading处理
        var contentHeight = viewport !== window ? viewport.scrollHeight : document.body.scrollHeight;
        if(viewportHeight === contentHeight){
            return;
        }

        //spin隐藏切换
        var toggleSpin = function(){
            var style = spin.getAttribute("class");
            if(style.indexOf("hidden") < 0){
                spin.setAttribute("class", style.replace(/(^\s+)|(\s+$)/g, "") + " hidden");
            }else{
                spin.setAttribute("class", style.replace(/hidden/, "").replace(/(^\s+)|(\s+$)/g, ""));
            }
        };

        //viewport滚动事件回调函数定义
        var scrollFn = function(){
            var scrollTop;
            if(viewport === window){
                scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
            }else{
                scrollTop = viewport.scrollTop;
            }

            var contentHeight = viewport !== window ? viewport.scrollHeight : document.body.scrollHeight;

            if((contentHeight - viewportHeight - scrollTop)/contentHeight < 0.01 && !loading){

                loading = true;

                if(spin.getAttribute("class").indexOf("hidden") >= 0){
                    spin.setAttribute("class", spin.getAttribute("class").replace(/hidden/, "").replace(/(^\s+)|(\s+$)/g, ""));
                }

                setTimeout(function(){
                    Bin.customAjaxCall("get", null, "/Bill.io/json/scrollLoading.json", true).then(function(value){
                        if(value["content"]){
                            var arrContent = value["content"];
                            var strContent = "";
                            arrContent.forEach(function(value, index){
                                strContent += '

' + (page * 20 + +value["content"])+ '

'
                            });
                            //console.log(strContent);
                            Bin.parseHTMLString(strContent, viewport, spin);
                            spin.setAttribute("class", spin.getAttribute("class").replace(/(^\s+)|(\s+$)/g, "") + " hidden");
                            page++;
                            loading = false;
                        }
                    }, function(reason){
                        if(reason){
                            var strContent = '
加载错误>_<
';
                            console.log(reason);
                            Bin.parseHTMLString(strContent, viewport, spin);
                            spin.setAttribute("class", spin.getAttribute("class").replace(/(^\s+)|(\s+$)/g, "") + " hidden");
                            loading = false;
                        }
                    });
                }, 600);

            }
        };

        //函数节流处理
        var debouncedFn = Bin.debounce(20, scrollFn);

        //事件绑定
        Bin.on(viewport, "scroll", debouncedFn, false);
    };

    return ScrollLoading;

});
<|endoftext|>


<commit_before>var XenAuction = {};

XenAuction.Config = {
	currentTime: 0,
	phrases: {
		'secondsLeft': 	'Seconds Left',
		'minutesLeft': 	'Minutes Left',
		'hoursLeft': 	'Hours Left',
		'daysLeft': 	'Days Left',

		'errorInvalidQuantity': 	'You are trying to buy a higher quantity than there is available'
	}
};

XenAuction.List = function() { this.__construct(); };
XenAuction.List.prototype = {
	__construct: function()
	{

		this.parseTimeleft();

		$(document).ready(function()
		{

			if ($(".chzn-select").length > 0)
			{
				$(".chzn-select").chosen();
			}

		});

	},

	parseTimeleft: function()
	{

		$(".auctionItem .timeLeft").each(function() {

			if ($(this).parents('.status_active').length == 0 || $(this).data('processed'))
			{
				return;
			}

			var time = $(this).text();
			var left = time - XenAuction.Config.currentTime;

			if (left < 0)
			{
				$(this).parents(".auctionItem").hide();
				return;
			}
			else if (left < 60)
			{
				var p = [left, XenAuction.Config.phrases.secondsLeft];
			}
			else if (left < 3600)
			{
				var p = [left / 60, XenAuction.Config.phrases.minutesLeft];
			}
			else if (left < 86400)
			{
				var p = [left / 3600, XenAuction.Config.phrases.hoursLeft];
			}
			else
			{
				var p = [left / 86400, XenAuction.Config.phrases.daysLeft];
			}

			$(this).html("");

			$(this).append($("
").text(Math.ceil(p[0])));
			$(this).append($("
").text(p[1]));

			$(this).data('processed', true);

		});

	}
};

XenAuction.Widget = function() { this.__construct(); };
XenAuction.Widget.prototype = {
	__construct: function()
	{
		this.renderWidget();
		//var checkCsrf = setInterval($.context(function() {
		//	if (XenForo._csrfToken != '')
		//	{
		//		clearInterval(checkCsrf);
		//		this.loadWidget();
		//	}
		//}, this));
	},

	loadWidget: function()
	{
		XenForo.ajax($("base").attr("href") + 'auctions/random', [{name: '_xfToken', value: XenForo._csrfToken}], $.context(this.renderWidget, this));
	},

	renderWidget: function(data)
	{
		//$("#auctionWidgetPlaceholder").replaceWith($(data.templateHtml));
		$(window).resize(this.resizer);
		this.resizer();
	},

	resizer: function()
	{
		var liWidth = $("#auctionWidget > li:first-child").outerWidth(true);

		$("#auctionWidget > li").hide();

		var total = 0;

		for (var i=10;i>0;i--)
		{
			$("#auctionWidget > li:nth-child("+i+")").show();
			total += $("#auctionWidget > li:nth-child("+i+")").outerWidth(true);

			if (total > $("#auctionWidget").innerWidth())
			{
				$("#auctionWidget > li:nth-child("+i+")").hide();
				break;
			}
		}
	}
};

XenAuction.Bid = function() { this.__construct(); };
XenAuction.Bid.prototype = {
	__construct: function()
	{

		$("input[name=quantity]").keyup(this.onChangeQuantity);
		$("input[name=bid]").keyup(this.onChangeBid);

	},

	onChangeBid: function()
	{
		$(this).next('.formValidationInlineError').remove();

		var form = $(this).parents('form.auctionBid');

		var bid 	= parseInt($(this).val());
		var minBid 	= parseInt(form.find("input.min_bid").val());

		if (isNaN(bid) || bid < minBid)
		{
			XenAuction.Helpers.showInputError(this, XenAuction.Config.phrases.errorBidTooLow);
		}
	},

	onChangeQuantity: function()
	{
		$(this).next('.formValidationInlineError').remove();

		var form = $(this).parents('form.auctionBid');

		var quantity 		= $(this).val();
		var cost 			= form.find("input.cost").val();
		var availability 	= form.find("input.availability").val();

		if (isNaN(quantity) || quantity > availability)
		{
			XenAuction.Helpers.showInputError(this, XenAuction.Config.phrases.errorInvalidQuantity);
		}

		var totalCost = parseInt(quantity) * parseFloat(cost);

		form.find('dd.paying').text(number_format(totalCost));
	}

};

XenAuction.Create = function() { this.__construct(); };
XenAuction.Create.prototype = {
	__construct: function()
	{

		$("input[name=bid_enable]").click(this.toggleEnable);
		$("input[name=buyout_enable]").click(this.toggleEnable);
		$(".chzn-select").chosen({allow_option_creation: true});

	},

	toggleEnable: function()
	{

		if ($(this).is(":checked"))
		{
			$(this).prev('input').removeAttr('disabled');
		}
		else
		{
			$(this).prev('input').attr('disabled', 'disabled');
		}

		if ($("#bid_enable").is(":checked"))
		{
			$("#availability_wrap").hide();
			$("#availability_wrap").find("input[name=availability]").val(1);
		}
		else if ($("#buyout_enable").is(":checked"))
		{
			$("#availability_wrap").show();
		}

	},

}

XenAuction.HistoryPanes = function() { this.__construct(); };
XenAuction.HistoryPanes.prototype = {
	__construct: function()
	{
		this.overrideTabConstructor();
		this.$tabs = new XenForo.Tabs($('#HistoryTabs'));
		this.removeParamsFromUrls();

		$(".historySearch form").submit( this.searchSubmit );
	},

	searchSubmit: function()
	{
		var href = window.location.pathname + window.location.search;
		var regex = new RegExp(/(?:\?|\&|\%3F|\&amp\;)page\=\d*/);
		href = href.replace(regex, '');

		$(".historySearch form").attr("action", href + '#' + $("#HistoryPanes li:visible").attr("id"));
	},

	overrideTabConstructor: function()
	{
		XenForo.Tabs.prototype.__construct = function($tabContainer)
		{
			this.$tabContainer = $tabContainer;
			this.$panes = $($tabContainer.data('panes'));

			var index = 0;

			$tabContainer.find('a[href]').each(function()
			{
				var $this = $(this), hrefParts = $this.attr('href').split('#');
				if (location.hash.substr(1) == hrefParts[1])
				{
					$tabContainer.find(".active").removeClass("active");
					$this.addClass("active");
					$this.parent().addClass("active");
					index = $this.parent().index();
				}
			});

			$tabContainer.tabs(this.$panes, {
				current: 'active',
				history: false,
				onBeforeClick: $.context(this, 'onBeforeClick'),
				initialIndex: index
			});
			this.api = $tabContainer.data('tabs');
		}
	},

	removeParamsFromUrls: function()
	{
		var regex = new RegExp(/(?:\?|\&|\%3F|\&amp\;)(?:page|search)\=[A-Za-z0-9 ]*/);

		this.$tabs.$panes.each(function()
		{
			var url = $(this).data('loadUrl');
			url = url.replace(regex, '');
			$(this).data('loadUrl', url);
		});

		$("#HistoryTabs > li > a").each(function()
		{
			var url = $(this).attr("href");
			url = url.replace(regex, '');
			$(this).attr("href", url);
		});
	}
}

XenAuction.Helpers = {

	showInputError: function(input, message)
	{
		var error 	= $(''+message+'').insertAfter(input);
		var coords 	= $(input).coords('outer', 'position');
		error.css({
			top: coords.top,
			left: coords.left + coords.width + 10
		}).show();
	},

	fixOverlay: function()
	{
		var originalOverlay 	= XenForo.createOverlay;
		XenForo.createOverlay 	= function($trigger, templateHtml, extraOptions)
		{
			var overlay = originalOverlay.call(this, $trigger, templateHtml, extraOptions);
			var elem 	= overlay.getOverlay();

			overlay.onLoad = function()
			{
				var position = elem.offset();
				elem.css('position', 'absolute');
				elem.offset(position);
				elem.find(".button[type=reset]").removeAttr('disabled').removeClass('disabled');

				elem.find(".Disabler").click(function() {
					setTimeout(function() {
						elem.find(".button[type=reset]").removeAttr('disabled').removeClass('disabled');
					}, 100);
				});
			};

			overlay.onClose = function()
			{
				elem.find('form').trigger('reset');
			};

			return overlay;
		};
	}

}

$(document).ready(XenAuction.Helpers.fixOverlay);

// helpers

function number_format (number, decimals, dec_point, thousands_sep) {
    // http://kevin.vanzonneveld.net
    number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
    var n = !isFinite(+number) ? 0 : +number,
        prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
        sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,
        dec = (typeof dec_point === 'undefined') ? '.' : dec_point,
        s = '',
        toFixedFix = function (n, prec) {
            var k = Math.pow(10, prec);
            return '' + Math.round(n * k) / k;
        };
    // Fix for IE parseFloat(0.55).toFixed(0) = 0;
    s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
    if (s[0].length > 3) {
        s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
    }
    if ((s[1] || '').length < prec) {
        s[1] = s[1] || '';
        s[1] += new Array(prec - s[1].length + 1).join('0');
    }
    return s.join(dec);
}
<commit_msg>Fix NaN errors<commit_after>var XenAuction = {};

XenAuction.Config = {
	currentTime: 0,
	phrases: {
		'secondsLeft': 	'Seconds Left',
		'minutesLeft': 	'Minutes Left',
		'hoursLeft': 	'Hours Left',
		'daysLeft': 	'Days Left',

		'errorInvalidQuantity': 	'You are trying to buy a higher quantity than there is available'
	}
};

XenAuction.List = function() { this.__construct(); };
XenAuction.List.prototype = {
	__construct: function()
	{

		this.parseTimeleft();

		$(document).ready(function()
		{

			if ($(".chzn-select").length > 0)
			{
				$(".chzn-select").chosen();
			}

		});

	},

	parseTimeleft: function()
	{

		$(".auctionItem .timeLeft").each(function() {

			if ($(this).parents('.status_active').length == 0 || $(this).data('processed'))
			{
				return;
			}

			var time = $(this).text();

			if (isNaN(time))
			{
				return;
			}

			var left = time - XenAuction.Config.currentTime;

			if (left < 0)
			{
				$(this).parents(".auctionItem").hide();
				return;
			}
			else if (left < 60)
			{
				var p = [left, XenAuction.Config.phrases.secondsLeft];
			}
			else if (left < 3600)
			{
				var p = [left / 60, XenAuction.Config.phrases.minutesLeft];
			}
			else if (left < 86400)
			{
				var p = [left / 3600, XenAuction.Config.phrases.hoursLeft];
			}
			else
			{
				var p = [left / 86400, XenAuction.Config.phrases.daysLeft];
			}

			$(this).html("");

			$(this).append($("
").text(Math.ceil(p[0])));
			$(this).append($("
").text(p[1]));

			$(this).data('processed', true);

		});

	}
};

XenAuction.Widget = function() { this.__construct(); };
XenAuction.Widget.prototype = {
	__construct: function()
	{
		this.renderWidget();
		//var checkCsrf = setInterval($.context(function() {
		//	if (XenForo._csrfToken != '')
		//	{
		//		clearInterval(checkCsrf);
		//		this.loadWidget();
		//	}
		//}, this));
	},

	loadWidget: function()
	{
		XenForo.ajax($("base").attr("href") + 'auctions/random', [{name: '_xfToken', value: XenForo._csrfToken}], $.context(this.renderWidget, this));
	},

	renderWidget: function(data)
	{
		//$("#auctionWidgetPlaceholder").replaceWith($(data.templateHtml));
		$(window).resize(this.resizer);
		this.resizer();
	},

	resizer: function()
	{
		var liWidth = $("#auctionWidget > li:first-child").outerWidth(true);

		$("#auctionWidget > li").hide();

		var total = 0;

		for (var i=10;i>0;i--)
		{
			$("#auctionWidget > li:nth-child("+i+")").show();
			total += $("#auctionWidget > li:nth-child("+i+")").outerWidth(true);

			if (total > $("#auctionWidget").innerWidth())
			{
				$("#auctionWidget > li:nth-child("+i+")").hide();
				break;
			}
		}
	}
};

XenAuction.Bid = function() { this.__construct(); };
XenAuction.Bid.prototype = {
	__construct: function()
	{

		$("input[name=quantity]").keyup(this.onChangeQuantity);
		$("input[name=bid]").keyup(this.onChangeBid);

	},

	onChangeBid: function()
	{
		$(this).next('.formValidationInlineError').remove();

		var form = $(this).parents('form.auctionBid');

		var bid 	= parseInt($(this).val());
		var minBid 	= parseInt(form.find("input.min_bid").val());

		if (isNaN(bid) || bid < minBid)
		{
			XenAuction.Helpers.showInputError(this, XenAuction.Config.phrases.errorBidTooLow);
		}
	},

	onChangeQuantity: function()
	{
		$(this).next('.formValidationInlineError').remove();

		var form = $(this).parents('form.auctionBid');

		var quantity 		= $(this).val();
		var cost 			= form.find("input.cost").val();
		var availability 	= form.find("input.availability").val();

		if (isNaN(quantity) || quantity > availability)
		{
			XenAuction.Helpers.showInputError(this, XenAuction.Config.phrases.errorInvalidQuantity);
		}

		var totalCost = parseInt(quantity) * parseFloat(cost);

		form.find('dd.paying').text(number_format(totalCost));
	}

};

XenAuction.Create = function() { this.__construct(); };
XenAuction.Create.prototype = {
	__construct: function()
	{

		$("input[name=bid_enable]").click(this.toggleEnable);
		$("input[name=buyout_enable]").click(this.toggleEnable);
		$(".chzn-select").chosen({allow_option_creation: true});

	},

	toggleEnable: function()
	{

		if ($(this).is(":checked"))
		{
			$(this).prev('input').removeAttr('disabled');
		}
		else
		{
			$(this).prev('input').attr('disabled', 'disabled');
		}

		if ($("#bid_enable").is(":checked"))
		{
			$("#availability_wrap").hide();
			$("#availability_wrap").find("input[name=availability]").val(1);
		}
		else if ($("#buyout_enable").is(":checked"))
		{
			$("#availability_wrap").show();
		}

	},

}

XenAuction.HistoryPanes = function() { this.__construct(); };
XenAuction.HistoryPanes.prototype = {
	__construct: function()
	{
		this.overrideTabConstructor();
		this.$tabs = new XenForo.Tabs($('#HistoryTabs'));
		this.removeParamsFromUrls();

		$(".historySearch form").submit( this.searchSubmit );
	},

	searchSubmit: function()
	{
		var href = window.location.pathname + window.location.search;
		var regex = new RegExp(/(?:\?|\&|\%3F|\&amp\;)page\=\d*/);
		href = href.replace(regex, '');

		$(".historySearch form").attr("action", href + '#' + $("#HistoryPanes li:visible").attr("id"));
	},

	overrideTabConstructor: function()
	{
		XenForo.Tabs.prototype.__construct = function($tabContainer)
		{
			this.$tabContainer = $tabContainer;
			this.$panes = $($tabContainer.data('panes'));

			var index = 0;

			$tabContainer.find('a[href]').each(function()
			{
				var $this = $(this), hrefParts = $this.attr('href').split('#');
				if (location.hash.substr(1) == hrefParts[1])
				{
					$tabContainer.find(".active").removeClass("active");
					$this.addClass("active");
					$this.parent().addClass("active");
					index = $this.parent().index();
				}
			});

			$tabContainer.tabs(this.$panes, {
				current: 'active',
				history: false,
				onBeforeClick: $.context(this, 'onBeforeClick'),
				initialIndex: index
			});
			this.api = $tabContainer.data('tabs');
		}
	},

	removeParamsFromUrls: function()
	{
		var regex = new RegExp(/(?:\?|\&|\%3F|\&amp\;)(?:page|search)\=[A-Za-z0-9 ]*/);

		this.$tabs.$panes.each(function()
		{
			var url = $(this).data('loadUrl');
			url = url.replace(regex, '');
			$(this).data('loadUrl', url);
		});

		$("#HistoryTabs > li > a").each(function()
		{
			var url = $(this).attr("href");
			url = url.replace(regex, '');
			$(this).attr("href", url);
		});
	}
}

XenAuction.Helpers = {

	showInputError: function(input, message)
	{
		var error 	= $(''+message+'').insertAfter(input);
		var coords 	= $(input).coords('outer', 'position');
		error.css({
			top: coords.top,
			left: coords.left + coords.width + 10
		}).show();
	},

	fixOverlay: function()
	{
		var originalOverlay 	= XenForo.createOverlay;
		XenForo.createOverlay 	= function($trigger, templateHtml, extraOptions)
		{
			var overlay = originalOverlay.call(this, $trigger, templateHtml, extraOptions);
			var elem 	= overlay.getOverlay();

			overlay.onLoad = function()
			{
				var position = elem.offset();
				elem.css('position', 'absolute');
				elem.offset(position);
				elem.find(".button[type=reset]").removeAttr('disabled').removeClass('disabled');

				elem.find(".Disabler").click(function() {
					setTimeout(function() {
						elem.find(".button[type=reset]").removeAttr('disabled').removeClass('disabled');
					}, 100);
				});
			};

			overlay.onClose = function()
			{
				elem.find('form').trigger('reset');
			};

			return overlay;
		};
	}

}

$(document).ready(XenAuction.Helpers.fixOverlay);

// helpers

function number_format (number, decimals, dec_point, thousands_sep) {
    // http://kevin.vanzonneveld.net
    number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
    var n = !isFinite(+number) ? 0 : +number,
        prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
        sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,
        dec = (typeof dec_point === 'undefined') ? '.' : dec_point,
        s = '',
        toFixedFix = function (n, prec) {
            var k = Math.pow(10, prec);
            return '' + Math.round(n * k) / k;
        };
    // Fix for IE parseFloat(0.55).toFixed(0) = 0;
    s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
    if (s[0].length > 3) {
        s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
    }
    if ((s[1] || '').length < prec) {
        s[1] = s[1] || '';
        s[1] += new Array(prec - s[1].length + 1).join('0');
    }
    return s.join(dec);
}
<|endoftext|>


<commit_before>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

/**
 * Creates a gap bridge iframe used to notify the native code about queued
 * commands.
 */
var cordova = require('cordova'),
    channel = require('cordova/channel'),
    utils = require('cordova/utils'),
    base64 = require('cordova/base64'),
    // XHR mode does not work on iOS 4.2.
    // XHR mode's main advantage is working around a bug in -webkit-scroll, which
    // doesn't exist only on iOS 5.x devices.
    // IFRAME_NAV is the fastest.
    // IFRAME_HASH could be made to enable synchronous bridge calls if we wanted this feature.
    jsToNativeModes = {
        IFRAME_NAV: 0,
        XHR_NO_PAYLOAD: 1,
        XHR_WITH_PAYLOAD: 2,
        XHR_OPTIONAL_PAYLOAD: 3,
        IFRAME_HASH_NO_PAYLOAD: 4,
        // Bundling the payload turns out to be slower. Probably since it has to be URI encoded / decoded.
        IFRAME_HASH_WITH_PAYLOAD: 5
    },
    bridgeMode,
    execIframe,
    execHashIframe,
    hashToggle = 1,
    execXhr,
    requestCount = 0,
    vcHeaderValue = null,
    commandQueue = [], // Contains pending JS->Native messages.
    isInContextOfEvalJs = 0;

function createExecIframe() {
    var iframe = document.createElement("iframe");
    iframe.style.display = 'none';
    document.body.appendChild(iframe);
    return iframe;
}

function createHashIframe() {
    var ret = createExecIframe();
    // Hash changes don't work on about:blank, so switch it to file:///.
    ret.contentWindow.history.replaceState(null, null, 'file:///#');
    return ret;
}

function shouldBundleCommandJson() {
    if (bridgeMode === jsToNativeModes.XHR_WITH_PAYLOAD) {
        return true;
    }
    if (bridgeMode === jsToNativeModes.XHR_OPTIONAL_PAYLOAD) {
        var payloadLength = 0;
        for (var i = 0; i < commandQueue.length; ++i) {
            payloadLength += commandQueue[i].length;
        }
        // The value here was determined using the benchmark within CordovaLibApp on an iPad 3.
        return payloadLength < 4500;
    }
    return false;
}

function massageArgsJsToNative(args) {
    if (!args || utils.typeName(args) != 'Array') {
        return args;
    }
    var ret = [];
    args.forEach(function(arg, i) {
        if (utils.typeName(arg) == 'ArrayBuffer') {
            ret.push({
                'CDVType': 'ArrayBuffer',
                'data': base64.fromArrayBuffer(arg)
            });
        } else {
            ret.push(arg);
        }
    });
    return ret;
}

function massageMessageNativeToJs(message) {
    if (message.CDVType == 'ArrayBuffer') {
        var stringToArrayBuffer = function(str) {
            var ret = new Uint8Array(str.length);
            for (var i = 0; i < str.length; i++) {
                ret[i] = str.charCodeAt(i);
            }
            return ret.buffer;
        };
        var base64ToArrayBuffer = function(b64) {
            return stringToArrayBuffer(atob(b64));
        };
        message = base64ToArrayBuffer(message.data);
    }
    return message;
}

function convertMessageToArgsNativeToJs(message) {
    var args = [];
    if (!message || !message.hasOwnProperty('CDVType')) {
        args.push(message);
    } else if (message.CDVType == 'MultiPart') {
        message.messages.forEach(function(e) {
            args.push(massageMessageNativeToJs(e));
        });
    } else {
        args.push(massageMessageNativeToJs(message));
    }
    return args;
}

function iOSExec() {
    // Use XHR for iOS 5 to work around a bug in -webkit-scroll.
    // Use IFRAME_NAV elsewhere since it's faster and XHR bridge
    // seems to have bugs in newer OS's (CB-3900, CB-3359, CB-5457, CB-4970, CB-4998, CB-5134)
    if (bridgeMode === undefined) {
        bridgeMode = navigator.userAgent.indexOf(' 5_') == -1 ? jsToNativeModes.IFRAME_NAV: jsToNativeModes.XHR_NO_PAYLOAD;
    }

    var successCallback, failCallback, service, action, actionArgs, splitCommand;
    var callbackId = null;
    if (typeof arguments[0] !== "string") {
        // FORMAT ONE
        successCallback = arguments[0];
        failCallback = arguments[1];
        service = arguments[2];
        action = arguments[3];
        actionArgs = arguments[4];

        // Since we need to maintain backwards compatibility, we have to pass
        // an invalid callbackId even if no callback was provided since plugins
        // will be expecting it. The Cordova.exec() implementation allocates
        // an invalid callbackId and passes it even if no callbacks were given.
        callbackId = 'INVALID';
    } else {
        // FORMAT TWO, REMOVED
        try {
            splitCommand = arguments[0].split(".");
            action = splitCommand.pop();
            service = splitCommand.join(".");
            actionArgs = Array.prototype.splice.call(arguments, 1);

            console.log('The old format of this exec call has been removed (deprecated since 2.1). Change to: ' +
                       "cordova.exec(null, null, \"" + service + "\", \"" + action + "\"," + JSON.stringify(actionArgs) + ");"
            );
            return;
        } catch (e) {}
    }

    // Register the callbacks and add the callbackId to the positional
    // arguments if given.
    if (successCallback || failCallback) {
        callbackId = service + cordova.callbackId++;
        cordova.callbacks[callbackId] =
            {success:successCallback, fail:failCallback};
    }

    actionArgs = massageArgsJsToNative(actionArgs);

    var command = [callbackId, service, action, actionArgs];

    // Stringify and queue the command. We stringify to command now to
    // effectively clone the command arguments in case they are mutated before
    // the command is executed.
    commandQueue.push(JSON.stringify(command));

    // If we're in the context of a stringByEvaluatingJavaScriptFromString call,
    // then the queue will be flushed when it returns; no need for a poke.
    // Also, if there is already a command in the queue, then we've already
    // poked the native side, so there is no reason to do so again.
    if (!isInContextOfEvalJs && commandQueue.length == 1) {
        switch (bridgeMode) {
        case jsToNativeModes.XHR_NO_PAYLOAD:
        case jsToNativeModes.XHR_WITH_PAYLOAD:
        case jsToNativeModes.XHR_OPTIONAL_PAYLOAD:
            // This prevents sending an XHR when there is already one being sent.
            // This should happen only in rare circumstances (refer to unit tests).
            if (execXhr && execXhr.readyState != 4) {
                execXhr = null;
            }
            // Re-using the XHR improves exec() performance by about 10%.
            execXhr = execXhr || new XMLHttpRequest();
            // Changing this to a GET will make the XHR reach the URIProtocol on 4.2.
            // For some reason it still doesn't work though...
            // Add a timestamp to the query param to prevent caching.
            execXhr.open('HEAD', "/!gap_exec?" + (+new Date()), true);
            if (!vcHeaderValue) {
                vcHeaderValue = /.*\((.*)\)/.exec(navigator.userAgent)[1];
            }
            execXhr.setRequestHeader('vc', vcHeaderValue);
            execXhr.setRequestHeader('rc', ++requestCount);
            if (shouldBundleCommandJson()) {
                execXhr.setRequestHeader('cmds', iOSExec.nativeFetchMessages());
            }
            execXhr.send(null);
            break;
        case jsToNativeModes.IFRAME_HASH_NO_PAYLOAD:
        case jsToNativeModes.IFRAME_HASH_WITH_PAYLOAD:
            execHashIframe = execHashIframe || createHashIframe();
            // Check if they've removed it from the DOM, and put it back if so.
            if (!execHashIframe.contentWindow) {
                execHashIframe = createHashIframe();
            }
            // The delegate method is called only when the hash changes, so toggle it back and forth.
            hashToggle = hashToggle ^ 3;
            var hashValue = '%0' + hashToggle;
            if (bridgeMode === jsToNativeModes.IFRAME_HASH_WITH_PAYLOAD) {
                hashValue += iOSExec.nativeFetchMessages();
            }
            execHashIframe.contentWindow.location.hash = hashValue;
            break;
        default:
            execIframe = execIframe || createExecIframe();
            // Check if they've removed it from the DOM, and put it back if so.
            if (!execIframe.contentWindow) {
                execIframe = createExecIframe();
            }
            execIframe.src = "gap://ready";
        }
    }
}

iOSExec.jsToNativeModes = jsToNativeModes;

iOSExec.setJsToNativeBridgeMode = function(mode) {
    // Remove the iFrame since it may be no longer required, and its existence
    // can trigger browser bugs.
    // https://issues.apache.org/jira/browse/CB-593
    if (execIframe) {
        execIframe.parentNode.removeChild(execIframe);
        execIframe = null;
    }
    bridgeMode = mode;
};

iOSExec.nativeFetchMessages = function() {
    // Each entry in commandQueue is a JSON string already.
    if (!commandQueue.length) {
        return '';
    }
    var json = '[' + commandQueue.join(',') + ']';
    commandQueue.length = 0;
    return json;
};

iOSExec.nativeCallback = function(callbackId, status, message, keepCallback) {
    return iOSExec.nativeEvalAndFetch(function() {
        var success = status === 0 || status === 1;
        var args = convertMessageToArgsNativeToJs(message);
        cordova.callbackFromNative(callbackId, success, status, args, keepCallback);
    });
};

iOSExec.nativeEvalAndFetch = function(func) {
    // This shouldn't be nested, but better to be safe.
    isInContextOfEvalJs++;
    try {
        func();
        return iOSExec.nativeFetchMessages();
    } finally {
        isInContextOfEvalJs--;
    }
};

module.exports = iOSExec;
<commit_msg>Update exec.js<commit_after>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

/**
 * Creates a gap bridge iframe used to notify the native code about queued
 * commands.
 */
var cordova = require('cordova'),
    channel = require('cordova/channel'),
    utils = require('cordova/utils'),
    base64 = require('cordova/base64'),
    // XHR mode does not work on iOS 4.2.
    // XHR mode's main advantage is working around a bug in -webkit-scroll, which
    // doesn't exist only on iOS 5.x devices.
    // IFRAME_NAV is the fastest.
    // IFRAME_HASH could be made to enable synchronous bridge calls if we wanted this feature.
    jsToNativeModes = {
        IFRAME_NAV: 0,
        XHR_NO_PAYLOAD: 1,
        XHR_WITH_PAYLOAD: 2,
        XHR_OPTIONAL_PAYLOAD: 3,
        IFRAME_HASH_NO_PAYLOAD: 4,
        // Bundling the payload turns out to be slower. Probably since it has to be URI encoded / decoded.
        IFRAME_HASH_WITH_PAYLOAD: 5
    },
    bridgeMode,
    execIframe,
    execHashIframe,
    hashToggle = 1,
    execXhr,
    requestCount = 0,
    vcHeaderValue = null,
    commandQueue = [], // Contains pending JS->Native messages.
    isInContextOfEvalJs = 0;

function createExecIframe() {
    var iframe = document.createElement("iframe");
    iframe.style.display = 'none';
    document.body.appendChild(iframe);
    return iframe;
}

function createHashIframe() {
    var ret = createExecIframe();
    // Hash changes don't work on about:blank, so switch it to file:///.
    ret.contentWindow.history.replaceState(null, null, 'file:///#');
    return ret;
}

function shouldBundleCommandJson() {
    if (bridgeMode === jsToNativeModes.XHR_WITH_PAYLOAD) {
        return true;
    }
    if (bridgeMode === jsToNativeModes.XHR_OPTIONAL_PAYLOAD) {
        var payloadLength = 0;
        for (var i = 0; i < commandQueue.length; ++i) {
            payloadLength += commandQueue[i].length;
        }
        // The value here was determined using the benchmark within CordovaLibApp on an iPad 3.
        return payloadLength < 4500;
    }
    return false;
}

function massageArgsJsToNative(args) {
    if (!args || utils.typeName(args) != 'Array') {
        return args;
    }
    var ret = [];
    args.forEach(function(arg, i) {
        if (utils.typeName(arg) == 'ArrayBuffer') {
            ret.push({
                'CDVType': 'ArrayBuffer',
                'data': base64.fromArrayBuffer(arg)
            });
        } else {
            ret.push(arg);
        }
    });
    return ret;
}

function massageMessageNativeToJs(message) {
    if (message.CDVType == 'ArrayBuffer') {
        var stringToArrayBuffer = function(str) {
            var ret = new Uint8Array(str.length);
            for (var i = 0; i < str.length; i++) {
                ret[i] = str.charCodeAt(i);
            }
            return ret.buffer;
        };
        var base64ToArrayBuffer = function(b64) {
            return stringToArrayBuffer(atob(b64));
        };
        message = base64ToArrayBuffer(message.data);
    }
    return message;
}

function convertMessageToArgsNativeToJs(message) {
    var args = [];
    if (!message || !message.hasOwnProperty('CDVType')) {
        args.push(message);
    } else if (message.CDVType == 'MultiPart') {
        message.messages.forEach(function(e) {
            args.push(massageMessageNativeToJs(e));
        });
    } else {
        args.push(massageMessageNativeToJs(message));
    }
    return args;
}

function iOSExec() {
    // Use XHR for iOS 5 to work around a bug in -webkit-scroll.
    // Use IFRAME_NAV elsewhere since it's faster and XHR bridge
    // seems to have bugs in newer OS's (CB-3900, CB-3359, CB-5457, CB-4970, CB-4998, CB-5134)
    if (bridgeMode === undefined) {
        bridgeMode = navigator.userAgent.indexOf(' 5_') == -1 ? jsToNativeModes.IFRAME_NAV: jsToNativeModes.XHR_NO_PAYLOAD;
    }

    var successCallback, failCallback, service, action, actionArgs, splitCommand;
    var callbackId = null;
    if (typeof arguments[0] !== "string") {
        // FORMAT ONE
        successCallback = arguments[0];
        failCallback = arguments[1];
        service = arguments[2];
        action = arguments[3];
        actionArgs = arguments[4];

        // Since we need to maintain backwards compatibility, we have to pass
        // an invalid callbackId even if no callback was provided since plugins
        // will be expecting it. The Cordova.exec() implementation allocates
        // an invalid callbackId and passes it even if no callbacks were given.
        callbackId = 'INVALID';
    } else {
        // FORMAT TWO, REMOVED
        try {
            splitCommand = arguments[0].split(".");
            action = splitCommand.pop();
            service = splitCommand.join(".");
            actionArgs = Array.prototype.splice.call(arguments, 1);

            console.log('The old format of this exec call has been removed (deprecated since 2.1). Change to: ' +
                       "cordova.exec(null, null, \"" + service + "\", \"" + action + "\"," + JSON.stringify(actionArgs) + ");"
            );
            return;
        } catch (e) {}
    }

    // If actionArgs is not provided, default to an empty array
    actionArgs = actionArgs || [];

    // Register the callbacks and add the callbackId to the positional
    // arguments if given.
    if (successCallback || failCallback) {
        callbackId = service + cordova.callbackId++;
        cordova.callbacks[callbackId] =
            {success:successCallback, fail:failCallback};
    }

    actionArgs = massageArgsJsToNative(actionArgs);

    var command = [callbackId, service, action, actionArgs];

    // Stringify and queue the command. We stringify to command now to
    // effectively clone the command arguments in case they are mutated before
    // the command is executed.
    commandQueue.push(JSON.stringify(command));

    // If we're in the context of a stringByEvaluatingJavaScriptFromString call,
    // then the queue will be flushed when it returns; no need for a poke.
    // Also, if there is already a command in the queue, then we've already
    // poked the native side, so there is no reason to do so again.
    if (!isInContextOfEvalJs && commandQueue.length == 1) {
        switch (bridgeMode) {
        case jsToNativeModes.XHR_NO_PAYLOAD:
        case jsToNativeModes.XHR_WITH_PAYLOAD:
        case jsToNativeModes.XHR_OPTIONAL_PAYLOAD:
            // This prevents sending an XHR when there is already one being sent.
            // This should happen only in rare circumstances (refer to unit tests).
            if (execXhr && execXhr.readyState != 4) {
                execXhr = null;
            }
            // Re-using the XHR improves exec() performance by about 10%.
            execXhr = execXhr || new XMLHttpRequest();
            // Changing this to a GET will make the XHR reach the URIProtocol on 4.2.
            // For some reason it still doesn't work though...
            // Add a timestamp to the query param to prevent caching.
            execXhr.open('HEAD', "/!gap_exec?" + (+new Date()), true);
            if (!vcHeaderValue) {
                vcHeaderValue = /.*\((.*)\)/.exec(navigator.userAgent)[1];
            }
            execXhr.setRequestHeader('vc', vcHeaderValue);
            execXhr.setRequestHeader('rc', ++requestCount);
            if (shouldBundleCommandJson()) {
                execXhr.setRequestHeader('cmds', iOSExec.nativeFetchMessages());
            }
            execXhr.send(null);
            break;
        case jsToNativeModes.IFRAME_HASH_NO_PAYLOAD:
        case jsToNativeModes.IFRAME_HASH_WITH_PAYLOAD:
            execHashIframe = execHashIframe || createHashIframe();
            // Check if they've removed it from the DOM, and put it back if so.
            if (!execHashIframe.contentWindow) {
                execHashIframe = createHashIframe();
            }
            // The delegate method is called only when the hash changes, so toggle it back and forth.
            hashToggle = hashToggle ^ 3;
            var hashValue = '%0' + hashToggle;
            if (bridgeMode === jsToNativeModes.IFRAME_HASH_WITH_PAYLOAD) {
                hashValue += iOSExec.nativeFetchMessages();
            }
            execHashIframe.contentWindow.location.hash = hashValue;
            break;
        default:
            execIframe = execIframe || createExecIframe();
            // Check if they've removed it from the DOM, and put it back if so.
            if (!execIframe.contentWindow) {
                execIframe = createExecIframe();
            }
            execIframe.src = "gap://ready";
        }
    }
}

iOSExec.jsToNativeModes = jsToNativeModes;

iOSExec.setJsToNativeBridgeMode = function(mode) {
    // Remove the iFrame since it may be no longer required, and its existence
    // can trigger browser bugs.
    // https://issues.apache.org/jira/browse/CB-593
    if (execIframe) {
        execIframe.parentNode.removeChild(execIframe);
        execIframe = null;
    }
    bridgeMode = mode;
};

iOSExec.nativeFetchMessages = function() {
    // Each entry in commandQueue is a JSON string already.
    if (!commandQueue.length) {
        return '';
    }
    var json = '[' + commandQueue.join(',') + ']';
    commandQueue.length = 0;
    return json;
};

iOSExec.nativeCallback = function(callbackId, status, message, keepCallback) {
    return iOSExec.nativeEvalAndFetch(function() {
        var success = status === 0 || status === 1;
        var args = convertMessageToArgsNativeToJs(message);
        cordova.callbackFromNative(callbackId, success, status, args, keepCallback);
    });
};

iOSExec.nativeEvalAndFetch = function(func) {
    // This shouldn't be nested, but better to be safe.
    isInContextOfEvalJs++;
    try {
        func();
        return iOSExec.nativeFetchMessages();
    } finally {
        isInContextOfEvalJs--;
    }
};

module.exports = iOSExec;
<|endoftext|>