<commit_before>(function () {
  // {{{ addurlp
  /**
   * Append a set of parameters to a given url
   *
   * @param string url
   * @param object params
   *
   * @access private
   * @return string
   **/
  var addurlp = function (url, params) {
    // Prepare the URL
    if (url.indexOf ('?') >= 0)
      url += '&';
    else
      url += '?';

    // Append parameters
    for (var p in params)
      if (params [p] != null)
        url += escape (p) + '=' + escape (params [p]) + '&';

    // Return the result
    return url.substring (0, url.length - 1);
  }
  // }}}

  // {{{ jOAuth2
  /**
   * Create a new jOAuth2 Authorization-Module
   *
   * @param object options for the module
   *
   * @access public
   * @return object
   **/
  self.jOAuth2 = function (options) {
    // Initialize all properties
    this.options = {
      authorization_endpoint : null,
      introspection_endpoint : null,
      introspection_method : 'rfc',
      token_endpoint : null,
      client_id : null,
      redirect_uri : null,
      scope : null,
      storage : window.sessionStorage,
      storage_id : 'default',

      onbeforeauthorize : null,
      onauthorize : null,
      onerror : null
    };

    // Merge external options
    for (var p in this.options)
      if (typeof options [p] != 'undefined')
        this.options [p] = options [p];

    return this;
  };
  // }}}

  // {{{ getToken
  /**
   * Retrive the currently granted token
   *
   * @access public
   * @return object
   **/
  self.jOAuth2.prototype.getToken = function () {
    if (!this.token && (this.token = this.options.storage.getItem ('jOAuth2-Token-' + this.options.storage_id)))
      this.token = JSON.parse (this.token);

    return this.token;
  };
  // }}}

  // {{{ authorize
  /**
   * Make sure that we have an authorization by the user
   *
   * Calling this function may redirect the user to Authorization-Endpoint
   * Please make sure that you keep all state at a safe place!
   *
   * @access public
   * @return bool
   **/
  self.jOAuth2.prototype.authorize = function () {
    // Check if we just received a response
    if ((window.location.hash.length > 1) &&
        ((window.location.hash.indexOf ('access_token=') > 0) ||
         (window.location.hash.indexOf ('error') > 0))) {
      var hash = window.location.hash.substring (1).split ('&');
      var params = { };
      var p;

      // Clear the hash
      history.pushState ('', document.title, window.location.pathname + window.location.search);

      // Parse contents of hash
      for (var i = 0; i < hash.length; i++)
        if ((p = hash [i].indexOf ('=')) > 0)
          params [decodeURIComponent (hash [i].substring (0, p).replace (/\+/, '%20'))] = decodeURIComponent (hash [i].substring (p + 1).replace (/\+/, '%20'));

      // Check for a valid token
      if ((typeof params.access_token != 'undefined') &&
          (typeof params.token_type != 'undefined') &&
          (typeof params.state != 'undefined') &&
          (params.token_type == 'Bearer') &&
          (params.state == this.options.storage_id)) {
        // Append time of issuance
        params.issued_at = Date.now ();

        // Store the token
        this.token = params;
        this.options.storage.setItem ('jOAuth2-Token-' + this.options.storage_id, JSON.stringify (this.token));

        // Run authorize-callback
        if (this.options.onauthorize)
          this.options.onauthorize.apply (this, [ params ]);

        return true;
      }

      // Check for an error
      if ((typeof params.error != 'undefined') &&
          (typeof params.state != 'undefined') &&
          (params.state == this.options.storage_id)) {
        // Run error-callback
        if (this.options.onerror)
          this.options.onerror.apply (this, [ params.error, params ]);

        return false;
      }

      // Bail out some kind of error here
      console.log ('Received invalid access-token?');
      console.log (params);
    }

    // Check if we have a token stored
    if (this.check ())
      return true;

    // Check if all required options are in place
    if (this.options.authorization_endpoint == null)
      throw 'Missing URL for authorization-endpoint';

    if (this.options.client_id == null)
      throw 'Missing client-id for authorization';

    // Generate redirect-uri
    var uri = this.options.redirect_uri;

    if (!uri) {
      uri = window.location.href;

      if (uri.indexOf ('#') >= 0)
        uri = uri.substring (0, uri.indexOf ('#'));
    }

    // Run early callback
    if (this.options.onbeforeauthorize && (this.options.onbeforeauthorize.apply (this, [ ]) === false))
      return false;

    // Redirect to authorization-endpoint
    // TODO: This should be POST?
    window.location.href = addurlp (
      this.options.authorization_endpoint,
      {
        response_type : 'token',
        client_id : this.options.client_id,
        redirect_uri : uri,
        scope : this.options.scope,
        state : this.options.storage_id
      }
    );

    return false;
  };
  // }}}

  // {{{ check
  /**
   * Check the state of the current granted token
   *
   * @access public
   * @return bool
   **/
  self.jOAuth2.prototype.check = function () {
    // Retrive the current token
    var token = this.getToken ();

    if (!token)
      return false;

    // Check the validity by expires-in
    if (typeof token.expires_in != 'undefined') {
      if (Date.now () < (parseInt (token.expires_in) * 1000) + token.issued_at)
        return true;

    // Check the validity by introspection
    } else if (this.options.introspection_endpoint != null) {
      // Request token-introspection (SYNCHRONOUS!!!)
      var xhr = new XMLHttpRequest ();

      if (this.options.introspection_method == 'rfc') {
        xhr.open ('post', this.options.introspection_endpoint, false);

        xhr.setRequestHeader ('Content-Type', 'application/x-www-form-urlencoded');
        this.patchXHR (xhr);

        xhr.send ('token=' + escape (token.access_token));
      } else {
        xhr.open ('get', this.options.introspection_endpoint + '/' + token.access_token, false);
        this.patchXHR (xhr);

        xhr.send ();
      }

      // Process the result
      if ((xhr.getResponseHeader ('Content-Type') == 'application/json') ||
          (xhr.getResponseHeader ('Content-Type') == 'text/json')) {
        var result = JSON.parse (xhr.response);

        if (result.active)
          return true;
      }
    }

    // Remove the token
    this.token = null;
    this.options.storage.removeItem ('jOAuth2-Token-' + this.options.storage_id);

    return false;
  };
  // }}}

  // {{{ getAuthorizationHeaders
  /**
   * Retrive all headers and their contents needed for authentication against
   * a resource-server
   *
   * @access public
   * @return object
   **/
  self.jOAuth2.prototype.getAuthorizationHeaders = function () {
    // Retrive the current token
    var token = this.getToken ();

    if (!token)
      throw 'No token available';

    return {
      'Authorization' : 'Bearer ' + token.access_token
    };
  };
  // }}}

  // {{{ patchXHR
  /**
   * Prepare an XHR-Object to be used with our authorization
   *
   * @param object xhr
   *
   * @access public
   * @return void
   **/
  self.jOAuth2.prototype.patchXHR = function (xhr) {
    var h = this.getAuthorizationHeaders ();

    for (var p in h)
      xhr.setRequestHeader (p, h [p]);
  };
  // }}}
})();
<commit_msg>Allow to re-authorize OAuth2-Sessions<commit_after>(function () {
  // {{{ addurlp
  /**
   * Append a set of parameters to a given url
   *
   * @param string url
   * @param object params
   *
   * @access private
   * @return string
   **/
  var addurlp = function (url, params) {
    // Prepare the URL
    if (url.indexOf ('?') >= 0)
      url += '&';
    else
      url += '?';

    // Append parameters
    for (var p in params)
      if (params [p] != null)
        url += escape (p) + '=' + escape (params [p]) + '&';

    // Return the result
    return url.substring (0, url.length - 1);
  }
  // }}}

  // {{{ jOAuth2
  /**
   * Create a new jOAuth2 Authorization-Module
   *
   * @param object options for the module
   *
   * @access public
   * @return object
   **/
  self.jOAuth2 = function (options) {
    // Initialize all properties
    this.options = {
      authorization_endpoint : null,
      introspection_endpoint : null,
      introspection_method : 'rfc',
      token_endpoint : null,
      client_id : null,
      redirect_uri : null,
      scopes : null,
      storage : window.sessionStorage,
      storage_id : 'default',
      expires_default : 3600000,

      onbeforeauthorize : null,
      onauthorize : null,
      onexpiration_warning : null,
      onexpiration : null,
      onerror : null
    };

    // Merge external options
    for (var p in this.options)
      if (typeof options [p] != 'undefined')
        this.options [p] = options [p];

    return this;
  };
  // }}}

  // {{{ getToken
  /**
   * Retrive the currently granted token
   *
   * @access public
   * @return object
   **/
  self.jOAuth2.prototype.getToken = function () {
    if (!this.token && (this.token = this.options.storage.getItem ('jOAuth2-Token-' + this.options.storage_id)))
      this.token = JSON.parse (this.token);

    return this.token;
  };
  // }}}

  // {{{ authorize
  /**
   * Make sure that we have an authorization by the user
   *
   * Calling this function may redirect the user to Authorization-Endpoint
   * Please make sure that you keep all state at a safe place!
   *
   * @access public
   * @return bool
   **/
  self.jOAuth2.prototype.authorize = function () {
    // Check if we just received a response
    if ((window.location.hash.length > 1) &&
        ((window.location.hash.indexOf ('access_token=') > 0) ||
         (window.location.hash.indexOf ('error') > 0))) {
      var hash = window.location.hash.substring (1).split ('&');
      var params = { };
      var p;

      // Clear the hash
      history.pushState ('', document.title, window.location.pathname + window.location.search);

      // Parse contents of hash
      for (var i = 0; i < hash.length; i++)
        if ((p = hash [i].indexOf ('=')) > 0)
          params [decodeURIComponent (hash [i].substring (0, p).replace (/\+/, '%20'))] = decodeURIComponent (hash [i].substring (p + 1).replace (/\+/, '%20'));

      // Check for a valid token
      if ((typeof params.access_token != 'undefined') &&
          (typeof params.token_type != 'undefined') &&
          (typeof params.state != 'undefined') &&
          (params.token_type == 'Bearer') &&
          (params.state == this.options.storage_id)) {
        // Append time of issuance
        params.issued_at = Date.now ();

        if (params.expires_in)
          params.expires_in = parseInt (params.expires_in) * 1000;
        else
          params.expires_in = this.options.expires_default;

        params.expires_at = params.issued_at + params.expires_in;

        // Post-Process scops
        if (typeof params.scope != 'undefined')
          params.scopes = params.scope.split (' ');

        // Store the token
        this.token = params;
        this.options.storage.setItem ('jOAuth2-Token-' + this.options.storage_id, JSON.stringify (this.token));

        // Run authorize-callback
        if (this.options.onauthorize)
          this.options.onauthorize.apply (this, [ params ]);

      // Check for an error
      } else if ((typeof params.error != 'undefined') &&
          (typeof params.state != 'undefined') &&
          (params.state == this.options.storage_id)) {
        // Run error-callback
        if (this.options.onerror)
          this.options.onerror.apply (this, [ params.error, params ]);

        if (!this.check ())
          return false;

      // Bail out some kind of error here
      } else {
        console.log ('Received invalid access-token?');
        console.log (params);

        if (!this.check ())
          return false;
      }
    }

    // Check if we have a token stored
    if (this.check ()) {
      var jOAuth2 = this;
      var token = this.getToken ();
      var token_lifetime = token.expires_at - Date.now ();

      window.setTimeout (function () {
        if (jOAuth2.options.onexpiration)
          jOAuth2.options.onexpiration.apply (jOAuth2, [ token ]);
      }, token_lifetime);

      window.setTimeout (function () {
        if (jOAuth2.options.onexpiration_warning)
          jOAuth2.options.onexpiration_warning.apply (jOAuth2, [ token, token.expires_at - Date.now () ]);
      }, Math.max (token_lifetime - 900000, parseInt (token_lifetime - (token.expires_in / 4))));

      return true;
    }

    // Try to redirect to authz-endpoint
    this.reauthorize ();

    return false;
  };
  // }}}

  // {{{ reauthorize
  /**
   * Try to renew the last token
   *
   * @access public
   * @return void
   **/
  self.jOAuth2.prototype.reauthorize = function () {
    // Try to access our previous token
    var token = this.getToken ();

    // Check if all required options are in place
    if (this.options.authorization_endpoint == null)
      throw 'Missing URL for authorization-endpoint';

    if (this.options.client_id == null)
      throw 'Missing client-id for authorization';

    // Generate redirect-uri
    var uri = this.options.redirect_uri;

    if (!uri) {
      uri = window.location.href;

      if (uri.indexOf ('#') >= 0)
        uri = uri.substring (0, uri.indexOf ('#'));
    }

    // Run early callback
    if (this.options.onbeforeauthorize && (this.options.onbeforeauthorize.apply (this, [ ]) === false))
      return;

    // Prepare parameters
    var params = {
      response_type : 'token',
      client_id : this.options.client_id,
      redirect_uri : uri,
      scope : (this.options.scopes ? this.options.scopes.join (' ') : ''),
      state : this.options.storage_id
    };

    if (token) {
      params.last_token = token.access_token;

      if (token.scopes && token.scopes.length > 1)
        params.scope = token.scopes.join (' ');
    }

    // Redirect to authorization-endpoint
    // TODO: This should be POST?
    window.location.href = addurlp (this.options.authorization_endpoint, params);
  };
  // }}}

  // {{{ check
  /**
   * Check the state of the current granted token
   *
   * @access public
   * @return bool
   **/
  self.jOAuth2.prototype.check = function () {
    // Retrive the current token
    var token = this.getToken ();

    if (!token)
      return false;

    // Check if the token has expires
    if (Date.now () < token.expires_at)
      return true;

    // Check the validity by introspection
    if (this.options.introspection_endpoint != null) {
      // Request token-introspection (SYNCHRONOUS!!!)
      var xhr = new XMLHttpRequest ();

      if (this.options.introspection_method == 'rfc') {
        xhr.open ('post', this.options.introspection_endpoint, false);

        xhr.setRequestHeader ('Content-Type', 'application/x-www-form-urlencoded');
        this.patchXHR (xhr);

        xhr.send ('token=' + escape (token.access_token));
      } else {
        xhr.open ('get', this.options.introspection_endpoint + '/' + token.access_token, false);
        this.patchXHR (xhr);

        xhr.send ();
      }

      // Process the result
      if ((xhr.getResponseHeader ('Content-Type') == 'application/json') ||
          (xhr.getResponseHeader ('Content-Type') == 'text/json')) {
        var result = JSON.parse (xhr.response);

        if (result.active)
          return true;
      }
    }

    // Remove the token
    this.token = null;
    this.options.storage.removeItem ('jOAuth2-Token-' + this.options.storage_id);

    return false;
  };
  // }}}

  // {{{ getAuthorizationHeaders
  /**
   * Retrive all headers and their contents needed for authentication against
   * a resource-server
   *
   * @access public
   * @return object
   **/
  self.jOAuth2.prototype.getAuthorizationHeaders = function () {
    // Retrive the current token
    var token = this.getToken ();

    if (!token)
      throw 'No token available';

    return {
      'Authorization' : 'Bearer ' + token.access_token
    };
  };
  // }}}

  // {{{ patchXHR
  /**
   * Prepare an XHR-Object to be used with our authorization
   *
   * @param object xhr
   *
   * @access public
   * @return void
   **/
  self.jOAuth2.prototype.patchXHR = function (xhr) {
    var h = this.getAuthorizationHeaders ();

    for (var p in h)
      xhr.setRequestHeader (p, h [p]);
  };
  // }}}
})();
<|endoftext|>


<commit_before>'use strict';

var gulp              = require('gulp');
var gutil             = require('gulp-util');
var basebuildMainFile = '../dist/main.js';

/*
  ==========================
  Basic basebuildOptions
  ==========================
*/
var basebuildOptions = {
  mainAngularModule : 'BaseBuildTests'
};

/*
  ==========================
  Base build
  ==========================
*/

basebuildOptions.modulesData = {

  gulp : {
    uses: '../tests/node_modules/gulp'
  },
  karma: {
    uses: '../tests/node_modules/karma',
    isEnabled: false
  },

  sonar: {
    host         : {
      url : 'http://192.168.99.100:9000'
    },
    jdbc : {
      url      : 'jdbc:h2:tcp://192.168.99.100/sonar'
    },
    projectKey     : 'io.timeoutzero:basebuild-angular-tests',
    projectName    : 'basebuild angular tests',
    projectVersion : '1.0.0',
  },

  unitTests: {
    uses: 'gulp/unitTests.js'
  },

}

if(gutil.env.prod){
  basebuildOptions.modulesData.gulp.uses  = 'gulp';
  basebuildOptions.modulesData.karma.uses = 'karma';
  basebuildMainFile                       = 'basebuild-angular';
}

if(gutil.env.proxies){
  options.modulesData.proxy = {
    uses : 'gulp/multiProxy.js'
  }
}

/*
  ==========================
  Read gulp files
  ==========================
*/

require(basebuildMainFile)(basebuildOptions);
<commit_msg>Disables proxy for tests<commit_after>'use strict';

var gulp              = require('gulp');
var gutil             = require('gulp-util');
var basebuildMainFile = '../dist/main.js';

/*
  ==========================
  Basic basebuildOptions
  ==========================
*/
var basebuildOptions = {
  mainAngularModule : 'BaseBuildTests'
};

/*
  ==========================
  Base build
  ==========================
*/

basebuildOptions.modulesData = {

  gulp : {
    uses: '../tests/node_modules/gulp'
  },
  karma: {
    uses: '../tests/node_modules/karma',
    isEnabled: false
  },

  sonar: {
    host         : {
      url : 'http://192.168.99.100:9000'
    },
    jdbc : {
      url      : 'jdbc:h2:tcp://192.168.99.100/sonar'
    },
    projectKey     : 'io.timeoutzero:basebuild-angular-tests',
    projectName    : 'basebuild angular tests',
    projectVersion : '1.0.0',
  },

  unitTests: {
    uses: 'gulp/unitTests.js'
  },

  proxy: {
    isEnabled: false
  }

}

if(gutil.env.prod){
  basebuildOptions.modulesData.gulp.uses  = 'gulp';
  basebuildOptions.modulesData.karma.uses = 'karma';
  basebuildMainFile                       = 'basebuild-angular';
}

if(gutil.env.proxies){
  options.modulesData.proxy = {
    uses : 'gulp/multiProxy.js'
  }
}

/*
  ==========================
  Read gulp files
  ==========================
*/

require(basebuildMainFile)(basebuildOptions);
<|endoftext|>


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

/**
 * Model element that represents a burner in the simulation.  The burner can heat and also cool other model elements.
 *
 * @author John Blanco
 * @author Jesse Greenberg
 */

define( function( require ) {
  'use strict';

  // modules
  var Air = require( 'ENERGY_FORMS_AND_CHANGES/intro/model/Air' );
  var EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  var EnergyChunk = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunk' );
  var EnergyChunkWanderController = require( 'ENERGY_FORMS_AND_CHANGES/intro/model/EnergyChunkWanderController' );
  var EnergyType = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyType' );
  var HorizontalSurface = require( 'ENERGY_FORMS_AND_CHANGES/intro/model/HorizontalSurface' );
  var inherit = require( 'PHET_CORE/inherit' );
  var ObservableArray = require( 'AXON/ObservableArray' );
  var Property = require( 'AXON/Property' );
  var Range = require( 'DOT/Range' );
  var Rectangle = require( 'DOT/Rectangle' );
  var Vector2 = require( 'DOT/Vector2' );
  var IntroConstants = require( 'ENERGY_FORMS_AND_CHANGES/intro/IntroConstants' );
  var ModelElement = require( 'ENERGY_FORMS_AND_CHANGES/intro/model/ModelElement' );

  // constants
  var WIDTH = 0.075; // In meters.
  var HEIGHT = WIDTH * 1;
  var MAX_ENERGY_GENERATION_RATE = 5000; // joules/sec, empirically chosen.
  var CONTACT_DISTANCE = 0.001; // In meters.
  var ENERGY_CHUNK_CAPTURE_DISTANCE = 0.2; // In meters, empirically chosen.

  // Because of the way that energy chunks are exchanged between thermal modeling elements within this simulation,
  // things can end up looking a bit odd if a burner is turned on with nothing on it.  To account for this, a separate
  // energy generation rate is used when a burner is exchanging energy directly with the air.
  var MAX_ENERGY_GENERATION_RATE_INTO_AIR = MAX_ENERGY_GENERATION_RATE * 0.3; // joules/sec, multiplier empirically chosen.

  /**
   * *
   * @param {Vector2} position - The position in model space where this burner exists. By convention for this simulation, the position is
   * @param {Property.<boolean>} energyChunksVisibleProperty Property that controls whether the energy chunks are visible
   * @constructor
   */
  function Burner( position, energyChunksVisibleProperty ) {

    ModelElement.call( this );

    this.addProperty( 'heatCoolLevel', 0 );

    var thisBurner = this;

    this.position = position;
    this.energyChunkList = new ObservableArray();
    this.energyChunkWanderControllers = [];
    this.energyChunksVisibleProperty = energyChunksVisibleProperty;

    // Track energy transferred to anything sitting on the burner.
    this.energyExchangedWithObjectSinceLastChunkTransfer = 0; // @private

    // Track build up of energy for transferring chunks to/from the air.
    this.energyExchangedWithAirSinceLastChunkTransfer = 0; // @private

    //this.heatCoolLevelProperty = new Property( new Range( -1, 1, 0 ) );

    //  Create and add the top surface.  Some compensation for perspective is necessary in order to avoid problems with edge overlap when dropping
    // objects on top of burner.
    var perspectiveCompensation = thisBurner.getOutlineRect().height * IntroConstants.BURNER_EDGE_TO_HEIGHT_RATIO * Math.cos( IntroConstants.PERSPECTIVE_ANGLE );

    this.addProperty( 'topSurface', new HorizontalSurface( new Range( thisBurner.getOutlineRect().getMinX() - perspectiveCompensation,
      thisBurner.getOutlineRect().maxX + perspectiveCompensation ), thisBurner.getOutlineRect().maxY, this ) );
  }

  return inherit( ModelElement, Burner, {

    /**
     * Get a rectangle that defines the outline of the burner.  In the model the burner is essentially a 2D rectangle.
     *
     * @return {Rectangle} Rectangle that defines the outline in model space.
     */
    getOutlineRect: function() {
      return new Rectangle( this.position.x - WIDTH / 2, this.position.y, WIDTH, HEIGHT );
    },

    /**
     * *
     * @returns {Property}
     */
    getTopSurfaceProperty: function() {
      return this.topSurfaceProperty;
    },
    /**
     * Interact with a thermal energy container, adding or removing energy based on the current heat/cool setting.
     *
     * @param {ThermalEnergyContainer} thermalEnergyContainer - Model object that will get or give energy.
     * @param {number} dt - Amount of time (delta time).
     */
    addOrRemoveEnergyToFromObject: function( thermalEnergyContainer, dt ) {
      // This shouldn't be used for air - there is a specific method for that.
      assert && assert( !( thermalEnergyContainer instanceof Air ),
        'This function should not be used with air - there is a specific method for that.' );
      if ( this.inContactWith( thermalEnergyContainer ) ) {
        var deltaEnergy = 0;
        if ( thermalEnergyContainer.getTemperature() > EFACConstants.FREEZING_POINT_TEMPERATURE ) {
          deltaEnergy = MAX_ENERGY_GENERATION_RATE * this.heatCoolLevel * dt;
        }
        thermalEnergyContainer.changeEnergy( deltaEnergy );
        this.energyExchangedWithObjectSinceLastChunkTransfer += deltaEnergy;
      }
    },

    /**
     * Add or remove energy from the specific energy container air.  This has specific behavior that is different from addOremoveEnergyToFromObject()
     * above.
     *
     * @param {Air} air - air as a thermal energy container
     * @param dt - amount of time (delta time)
     */
    addOrRemoveEnergyToFromAir: function( air, dt ) {
      var deltaEnergy = MAX_ENERGY_GENERATION_RATE_INTO_AIR * this.heatCoolLevel * dt;
      if ( deltaEnergy > 0 ) {
        // TODO: is this a special case?
      }
      air.changeEnergy( deltaEnergy );
      this.energyExchangedWithAirSinceLastChunkTransfer += deltaEnergy;
    },

    /**
     * Determine if the burner is in contact with a thermal energy container.
     *
     * @param {ThermalEnergyContainer} thermalEnergyContainer
     * @returns {boolean}
     */
    inContactWith: function( thermalEnergyContainer ) {
      var containerThermalArea = thermalEnergyContainer.getThermalContactArea();
      return (
        containerThermalArea.centerX > this.getOutlineRect().minX &&
        containerThermalArea.centerX < this.getOutlineRect().maxX &&
        Math.abs( containerThermalArea.minY - this.getOutlineRect().maxY ) < CONTACT_DISTANCE );
    },

    /**
     *
     * @param {EnergyChunk} energyChunk
     */
    addEnergyChunk: function( energyChunk ) {
      energyChunk.zPosition = 0;
      this.energyChunkList.add( energyChunk );
      this.energyChunkWanderControllers.push( new EnergyChunkWanderController( energyChunk, new Property( this.getEnergyChunkStartEndPoint() ), null ) );
      this.energyExchangedWithAirSinceLastChunkTransfer = 0;
      this.energyExchangedWithObjectSinceLastChunkTransfer = 0;
    },

    /**
     * @private
     * @returns {Vector2}
     */
    getEnergyChunkStartEndPoint: function() {
      return this.getCenterPoint();
    },
    /**
     * Request an energy chunk from the burner.
     *
     * @param {Vector2} point - Point from which to search for closest chunk.
     * @return {EnergyChunk} Closest energy chunk, null if none are contained.
     */
    extractClosestEnergyChunk: function( point ) {
      // Extend the scope for callbacks.
      var thisBurner = this;
      var closestEnergyChunk = null;
      if ( this.energyChunkList.length > 0 ) {
        this.energyChunkList.forEach( function( energyChunk ) {
          if ( energyChunk.position.distance( thisBurner.position ) > ENERGY_CHUNK_CAPTURE_DISTANCE &&
            ( closestEnergyChunk === null ||
              energyChunk.position.distance( point ) < closestEnergyChunk.position.distance( point ) ) ) {
            // Found a closer chunk.
            closestEnergyChunk = energyChunk;
          }
        } );

        this.energyChunkList.remove( closestEnergyChunk );
        this.energyChunkWanderControllers.forEach( function( energyChunkWanderController, index ) {
          if ( energyChunkWanderController.energyChunk === closestEnergyChunk ) {
            thisBurner.energyChunkWanderControllers.splice( index, 1 );
          }
        } );
      }

      if ( closestEnergyChunk === null && this.heatCoolLevel > 0 ) {
        // Create an energy chunk.
        closestEnergyChunk = new EnergyChunk( EnergyType.THERMAL, this.getEnergyChunkStartEndPoint(), new Vector2( 0, 0 ), this.energyChunksVisibleProperty );
      }
      if ( closestEnergyChunk !== null ) {
        this.energyExchangedWithAirSinceLastChunkTransfer = 0;
        this.energyExchangedWithObjectSinceLastChunkTransfer = 0;
      } else {
        console.log( 'Warning: Request for energy chunk from burner when not in heat mode and no chunks contained, returning null.' );
      }
      return closestEnergyChunk;
    },
    /**
     * *
     * @returns {Vector2}
     */
    getCenterPoint: function() {
      return new Vector2( this.position.x, this.position.y + HEIGHT / 2 );
    },
    /**
     *
     */
    reset: function() {
      ModelElement.prototype.reset.call( this );
      this.energyChunkList.clear();
      this.energyExchangedWithAirSinceLastChunkTransfer = 0;
      this.energyExchangedWithObjectSinceLastChunkTransfer = 0;
      this.heatCoolLevelProperty.reset();
    },

    /**
     * *
     * @param {Array.<ThermalEnergyContainer>} thermalEnergyContainers
     * @returns {boolean}
     */
    areAnyOnTop: function( thermalEnergyContainers ) {
      var self = this;
      thermalEnergyContainers.forEach( function( thermalEnergyContainer ) {
        if ( self.inContactWith( thermalEnergyContainer ) ) {
          return true;
        }
      } );
      return false;
    },
    /**
     * *
     * @returns {number}
     */
    getEnergyChunkCountForAir: function() {
      var thisModel = this; // extend scope for nested loop function.
      var count = 0;
      // If there are approaching chunks, and the mode has switched to off or to heating, the chunks should go back to the air (if they're not
      // almost to the burner).
      if ( this.energyChunkList.length > 0 && this.heatCoolLevel >= 0 ) {
        this.energyChunkList.forEach( function( energyChunk ) {
          if ( thisModel.position.distance( energyChunk.position ) > ENERGY_CHUNK_CAPTURE_DISTANCE ) {
            count++;
          }
        } );
      }
      if ( count === 0 ) {
        //console.log( EFACConstants.ENERGY_PER_CHUNK );
        //console.log( this.energyExchangedWithAirSinceLastChunkTransfer / EFACConstants.ENERGY_PER_CHUNK )
        // See whether the energy exchanged with the air since the last chunk transfer warrants another chunk.
        count = Math.round( this.energyExchangedWithAirSinceLastChunkTransfer / EFACConstants.ENERGY_PER_CHUNK );
      }
      return count;
    },

    /**
     * Animate the energy chunks.
     *
     * @private
     * @param {number} dt
     */
    step: function( dt ) {
      var thisBurner = this;
      this.energyChunkWanderControllers.forEach( function( energyChunkWanderController ) {
        energyChunkWanderController.updatePosition( dt );
        if ( energyChunkWanderController.destinationReached() ) {
          thisBurner.energyChunkList.remove( energyChunkWanderController.energyChunk );
          var index = thisBurner.energyChunkWanderControllers.indexOf( energyChunkWanderController );
          if ( index > -1 ) {
            thisBurner.energyChunkWanderControllers.splice( index, 1 );
          }
        }
      } );
    },

    /**
     * *
     * @returns {Rectangle}
     */
    getFlameIceRect: function() {
      // be coordinated with the view.
      var outlineRect = this.getOutlineRect();
      return new Rectangle( outlineRect.centerX - outlineRect.width / 4, outlineRect.centerY, outlineRect.width / 2, outlineRect.height / 2 );
    },

    /**
     * *
     * @returns {number}
     */
    getTemperature: function() {
      // low value is limited to the freezing point of water.
      return Math.max( EFACConstants.ROOM_TEMPERATURE + this.heatCoolLevel * 100, EFACConstants.FREEZING_POINT_TEMPERATURE );
    },

    /**
     * Get the (signed) number of energy chunks for interaction with thermal
     * objects (as opposed to air).
     *
     * @return Number of energy chunks that could be supplied or consumed.
     *         Negative value indicates that chunks should come in.
     */
    getEnergyChunkBalanceWithObjects: function() {
      return ( Math.floor( Math.abs( this.energyExchangedWithObjectSinceLastChunkTransfer ) / EFACConstants.ENERGY_PER_CHUNK ) * Math.sign( this.energyExchangedWithObjectSinceLastChunkTransfer ) );
    },

    /**
     * *
     * @returns {boolean}
     */
    canSupplyEnergyChunk: function() {
      return this.heatCoolLevel > 0;
    },

    /**
     * *
     * @returns {boolean}
     */
    canAcceptEnergyChunk: function() {
      return this.heatCoolLevel < 0;
    }
  } );
} );
<commit_msg>Add documentation; clean up formatting; wrap long lines.<commit_after>// Copyright 2014-2015, University of Colorado Boulder

/**
 * Model element that represents a burner in the simulation.  The burner can heat and also cool other model elements.
 *
 * @author John Blanco
 * @author Jesse Greenberg
 */

define( function( require ) {
  'use strict';

  // modules
  var Air = require( 'ENERGY_FORMS_AND_CHANGES/intro/model/Air' );
  var EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  var EnergyChunk = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunk' );
  var EnergyChunkWanderController = require( 'ENERGY_FORMS_AND_CHANGES/intro/model/EnergyChunkWanderController' );
  var EnergyType = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyType' );
  var HorizontalSurface = require( 'ENERGY_FORMS_AND_CHANGES/intro/model/HorizontalSurface' );
  var inherit = require( 'PHET_CORE/inherit' );
  var ObservableArray = require( 'AXON/ObservableArray' );
  var Property = require( 'AXON/Property' );
  var Range = require( 'DOT/Range' );
  var Rectangle = require( 'DOT/Rectangle' );
  var Vector2 = require( 'DOT/Vector2' );
  var IntroConstants = require( 'ENERGY_FORMS_AND_CHANGES/intro/IntroConstants' );
  var ModelElement = require( 'ENERGY_FORMS_AND_CHANGES/intro/model/ModelElement' );

  // constants
  var WIDTH = 0.075; // In meters.
  var HEIGHT = WIDTH * 1;
  var MAX_ENERGY_GENERATION_RATE = 5000; // joules/sec, empirically chosen.
  var CONTACT_DISTANCE = 0.001; // In meters.
  var ENERGY_CHUNK_CAPTURE_DISTANCE = 0.2; // In meters, empirically chosen.

  // Because of the way that energy chunks are exchanged between thermal modeling elements within this simulation,
  // things can end up looking a bit odd if a burner is turned on with nothing on it.  To account for this, a separate
  // energy generation rate is used when a burner is exchanging energy directly with the air.
  // Units: joules/sec, multiplier empirically chosen.
  var MAX_ENERGY_GENERATION_RATE_INTO_AIR = MAX_ENERGY_GENERATION_RATE * 0.3;

  /**
   * Burner class
   *
   * @param {Vector2} position   The position in model space where this burner exists.
   * @param {Property.<boolean>} energyChunksVisibleProperty Controls whether the energy chunks are visible
   * @constructor
   */
  function Burner( position, energyChunksVisibleProperty ) {

    ModelElement.call( this );

    this.addProperty( 'heatCoolLevel', 0 );

    var thisBurner = this;

    this.position = position;
    this.energyChunkList = new ObservableArray();
    this.energyChunkWanderControllers = [];
    this.energyChunksVisibleProperty = energyChunksVisibleProperty;

    // Track energy transferred to anything sitting on the burner.
    this.energyExchangedWithObjectSinceLastChunkTransfer = 0; // @private

    // Track build up of energy for transferring chunks to/from the air.
    this.energyExchangedWithAirSinceLastChunkTransfer = 0; // @private

    //this.heatCoolLevelProperty = new Property( new Range( -1, 1, 0 ) );

    // Create and add the top surface.  Some compensation for perspective is
    // necessary in order to avoid problems with edge overlap when dropping
    // objects on top of burner.
    var perspectiveCompensation =
      thisBurner.getOutlineRect().height * IntroConstants.BURNER_EDGE_TO_HEIGHT_RATIO *
      Math.cos( IntroConstants.PERSPECTIVE_ANGLE );

    this.addProperty( 'topSurface',
      new HorizontalSurface( new Range( thisBurner.getOutlineRect().getMinX() - perspectiveCompensation,
        thisBurner.getOutlineRect().maxX + perspectiveCompensation ), thisBurner.getOutlineRect().maxY, this ) );
  }

  return inherit( ModelElement, Burner, {

    /**
     * Get a rectangle that defines the outline of the burner.  In the model the burner is essentially a 2D rectangle.
     *
     * @return {Rectangle} Rectangle that defines the outline in model space.
     */
    getOutlineRect: function() {
      return new Rectangle( this.position.x - WIDTH / 2, this.position.y, WIDTH, HEIGHT );
    },

    /**
     * *
     * @returns {Property}
     */
    getTopSurfaceProperty: function() {
      return this.topSurfaceProperty;
    },

    /**
     * Interact with a thermal energy container, adding or removing energy based on the current heat/cool setting.
     *
     * @param {ThermalEnergyContainer} thermalEnergyContainer - Model object that will get or give energy.
     * @param {number} dt - Amount of time (delta time).
     */
    addOrRemoveEnergyToFromObject: function( thermalEnergyContainer, dt ) {
      // This shouldn't be used for air - there is a specific method for that.
      assert && assert( !( thermalEnergyContainer instanceof Air ),
        'This function should not be used with air - there is a specific method for that.' );
      if ( this.inContactWith( thermalEnergyContainer ) ) {
        var deltaEnergy = 0;
        if ( thermalEnergyContainer.getTemperature() > EFACConstants.FREEZING_POINT_TEMPERATURE ) {
          deltaEnergy = MAX_ENERGY_GENERATION_RATE * this.heatCoolLevel * dt;
        }
        thermalEnergyContainer.changeEnergy( deltaEnergy );
        this.energyExchangedWithObjectSinceLastChunkTransfer += deltaEnergy;
      }
    },

    /**
     * Add or remove energy from the specific energy container air.  This has specific behavior that is different from
     * addOremoveEnergyToFromObject() above.
     *
     * @param {Air} air - air as a thermal energy container
     * @param dt - amount of time (delta time)
     */
    addOrRemoveEnergyToFromAir: function( air, dt ) {
      var deltaEnergy = MAX_ENERGY_GENERATION_RATE_INTO_AIR * this.heatCoolLevel * dt;
      if ( deltaEnergy > 0 ) {
        // TODO: is this a special case?
      }
      air.changeEnergy( deltaEnergy );
      this.energyExchangedWithAirSinceLastChunkTransfer += deltaEnergy;
    },

    /**
     * Determine if the burner is in contact with a thermal energy container.
     *
     * @param {ThermalEnergyContainer} thermalEnergyContainer
     * @returns {boolean}
     */
    inContactWith: function( thermalEnergyContainer ) {
      var containerThermalArea = thermalEnergyContainer.getThermalContactArea();
      return (
        containerThermalArea.centerX > this.getOutlineRect().minX &&
        containerThermalArea.centerX < this.getOutlineRect().maxX &&
        Math.abs( containerThermalArea.minY - this.getOutlineRect().maxY ) < CONTACT_DISTANCE );
    },

    /**
     *
     * @param {EnergyChunk} energyChunk
     */
    addEnergyChunk: function( energyChunk ) {
      var controller = new EnergyChunkWanderController( energyChunk,
        new Property( this.getEnergyChunkStartEndPoint() ), null );

      energyChunk.zPosition = 0;
      this.energyChunkList.add( energyChunk );
      this.energyChunkWanderControllers.push( controller );
      this.energyExchangedWithAirSinceLastChunkTransfer = 0;
      this.energyExchangedWithObjectSinceLastChunkTransfer = 0;
    },

    /**
     * @private
     * @returns {Vector2}
     */
    getEnergyChunkStartEndPoint: function() {
      return this.getCenterPoint();
    },

    /**
     * Request an energy chunk from the burner.
     *
     * @param {Vector2} point - Point from which to search for closest chunk.
     * @return {EnergyChunk} Closest energy chunk, null if none are contained.
     */
    extractClosestEnergyChunk: function( point ) {
      // Extend the scope for callbacks.
      var thisBurner = this;
      var closestEnergyChunk = null;
      if ( this.energyChunkList.length > 0 ) {
        this.energyChunkList.forEach( function( energyChunk ) {
          if ( energyChunk.position.distance( thisBurner.position ) > ENERGY_CHUNK_CAPTURE_DISTANCE &&
            ( closestEnergyChunk === null ||
              energyChunk.position.distance( point ) < closestEnergyChunk.position.distance( point ) ) ) {
            // Found a closer chunk.
            closestEnergyChunk = energyChunk;
          }
        } );

        this.energyChunkList.remove( closestEnergyChunk );
        this.energyChunkWanderControllers.forEach( function( energyChunkWanderController, index ) {
          if ( energyChunkWanderController.energyChunk === closestEnergyChunk ) {
            thisBurner.energyChunkWanderControllers.splice( index, 1 );
          }
        } );
      }

      if ( closestEnergyChunk === null && this.heatCoolLevel > 0 ) {
        // Create an energy chunk.
        closestEnergyChunk = new EnergyChunk( EnergyType.THERMAL, this.getEnergyChunkStartEndPoint(),
          new Vector2( 0, 0 ), this.energyChunksVisibleProperty );
      }
      if ( closestEnergyChunk !== null ) {
        this.energyExchangedWithAirSinceLastChunkTransfer = 0;
        this.energyExchangedWithObjectSinceLastChunkTransfer = 0;
      } else {
        console.log( 'Warning: Request for energy chunk from burner when not in heat mode and no chunks contained, returning null.' );
      }
      return closestEnergyChunk;
    },

    /**
     * *
     * @returns {Vector2}
     */
    getCenterPoint: function() {
      return new Vector2( this.position.x, this.position.y + HEIGHT / 2 );
    },

    /**
     *
     */
    reset: function() {
      ModelElement.prototype.reset.call( this );
      this.energyChunkList.clear();
      this.energyExchangedWithAirSinceLastChunkTransfer = 0;
      this.energyExchangedWithObjectSinceLastChunkTransfer = 0;
      this.heatCoolLevelProperty.reset();
    },

    /**
     * *
     * @param {Array.<ThermalEnergyContainer>} thermalEnergyContainers
     * @returns {boolean}
     */
    areAnyOnTop: function( thermalEnergyContainers ) {
      var self = this;
      thermalEnergyContainers.forEach( function( thermalEnergyContainer ) {
        if ( self.inContactWith( thermalEnergyContainer ) ) {
          return true;
        }
      } );
      return false;
    },

    /**
     * *
     * @returns {number}
     */
    getEnergyChunkCountForAir: function() {
      var thisModel = this; // extend scope for nested loop function.
      var count = 0;
      // If there are approaching chunks, and the mode has switched to off or to heating,
      // the chunks should go back to the air (if they're not almost to the burner).
      if ( this.energyChunkList.length > 0 && this.heatCoolLevel >= 0 ) {
        this.energyChunkList.forEach( function( energyChunk ) {
          if ( thisModel.position.distance( energyChunk.position ) > ENERGY_CHUNK_CAPTURE_DISTANCE ) {
            count++;
          }
        } );
      }
      if ( count === 0 ) {
        //console.log( EFACConstants.ENERGY_PER_CHUNK );
        //console.log( this.energyExchangedWithAirSinceLastChunkTransfer / EFACConstants.ENERGY_PER_CHUNK )
        // See whether the energy exchanged with the air since the last chunk transfer warrants another chunk.
        count = Math.round( this.energyExchangedWithAirSinceLastChunkTransfer / EFACConstants.ENERGY_PER_CHUNK );
      }
      return count;
    },

    /**
     * Animate the energy chunks.
     *
     * @private
     * @param {number} dt
     */
    step: function( dt ) {
      var thisBurner = this;
      this.energyChunkWanderControllers.forEach( function( energyChunkWanderController ) {
        energyChunkWanderController.updatePosition( dt );
        if ( energyChunkWanderController.destinationReached() ) {
          thisBurner.energyChunkList.remove( energyChunkWanderController.energyChunk );
          var index = thisBurner.energyChunkWanderControllers.indexOf( energyChunkWanderController );
          if ( index > -1 ) {
            thisBurner.energyChunkWanderControllers.splice( index, 1 );
          }
        }
      } );
    },

    /**
     * *
     * @returns {Rectangle}
     */
    getFlameIceRect: function() {
      // be coordinated with the view.
      var outlineRect = this.getOutlineRect();
      var width = outlineRect.width;
      var height = outlineRect.height;
      return new Rectangle( outlineRect.centerX - width / 4, outlineRect.centerY, width / 2, height / 2 );
    },

    /**
     * Get burner temperature, clamped at minimum to the freezing point of water.
     *
     * @returns {number}
     */
    getTemperature: function() {
      var temperature = EFACConstants.ROOM_TEMPERATURE + this.heatCoolLevel * 100;
      return Math.max( temperature, EFACConstants.FREEZING_POINT_TEMPERATURE );
    },

    /**
     * Get the (signed) number of energy chunks for interaction with thermal
     * objects (as opposed to air).
     *
     * @return Number of energy chunks that could be supplied or consumed.
     *         Negative value indicates that chunks should come in.
     */
    getEnergyChunkBalanceWithObjects: function() {
      var deltaE = this.energyExchangedWithObjectSinceLastChunkTransfer;
      var sign = Math.sign( deltaE );
      return ( Math.floor( Math.abs( deltaE ) / EFACConstants.ENERGY_PER_CHUNK ) * sign );
    },

    /**
     * Whether burner can emit an energy chunk to another element
     *
     * @returns {boolean}
     */
    canSupplyEnergyChunk: function() {
      return this.heatCoolLevel > 0;
    },

    /**
     * Whether burner can receive an energy chunk from another element
     *
     * @returns {boolean}
     */
    canAcceptEnergyChunk: function() {
      return this.heatCoolLevel < 0;
    }
  } );
} );
<|endoftext|>


<commit_before>/*jshint indent: 2, evil: false, boss: true, bitwise: true, curly: true, eqeqeq: true, newcap: true, laxbreak: true, undef: true, white: true, browser: true, jquery: true*/
/*globals require module */
/*

The MIT License (MIT)
Copyright (c) 2012 Get Satisfaction

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/

(function () {

  // Support non-browser contexts via jsdom
  var doc;
  if (typeof window === 'undefined') {
    var jsdom = require('jsdom').jsdom;
    var jsDomDoc = jsdom("");
    // These variables are hoisted to the top of the function scope, so we give
    // them nicknames to avoid conflicts when ran in the browser.
    var win = jsDomDoc.createWindow();
    doc = win.document;
  } else {
    doc = document;
  }

  var Janitor = {
    clean: function (html, config) {
      config = config || {};
      config.tags = config.tags || {};
      config.protocols = config.protocols || [];
      config.protocolAttrs = config.protocolAttrs || ['src', 'href'];

      var sandbox = doc.createElement('div');
      sandbox.innerHTML = html;

      var sanitize = function (parentNode) {
        for (var i = 0; i < parentNode.childNodes.length; i += 1) {
          var node = parentNode.childNodes[i],
              nodeName = node.nodeName.toLowerCase(),
              attrs = config.tags[nodeName];

          // Ignore text nodes and nodes that have already been sanitized
          if (node.nodeType === 3 || node._sanitized) {
            continue;
          }

          // Drop tag entirely
          if (!config.tags[nodeName]) {
            while (node.childNodes.length > 0) {
              parentNode.insertBefore(node.childNodes[0], node);
            }
            parentNode.removeChild(node);

            sanitize(parentNode);
            break;
          }

          // Sanitize attributes
          for (var a = 0; a < node.attributes.length; a += 1) {
            var attr = node.attributes[a],
                attrName = attr.name.toLowerCase();

            // Allow attribute?
            if (attrs.indexOf(attrName) === -1) {
              node.removeAttribute(node.attributes[a].name);
            }

            // Allow protocol?
            if (config.protocolAttrs.indexOf(attrName) !== -1) {
              var url = doc.createElement('a');
              url.href = attr.value;

              if (config.protocols.indexOf(url.protocol.replace(/\:$/, '')) === -1) {
                node.setAttribute(node.attributes[a].name, '');
              }
            }
          }

          // Sanitize children
          sanitize(node);

          // Mark node as sanitized so it's ignored in future runs
          node._sanitized = true;
        }
      };

      sanitize(sandbox);

      return sandbox.innerHTML;
    }
  };

  // Make Janitor available in both browser and non-browser contexts
  if (typeof window !== 'undefined') {
    window.Janitor = Janitor;
  } else {
    module.exports = Janitor;
  }
})();
<commit_msg>Apply fix for removing multiple elements<commit_after>/*jshint indent: 2, evil: false, boss: true, bitwise: true, curly: true, eqeqeq: true, newcap: true, laxbreak: true, undef: true, white: true, browser: true, jquery: true*/
/*globals require module */
/*

The MIT License (MIT)
Copyright (c) 2012 Get Satisfaction

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/

(function () {

  // Support non-browser contexts via jsdom
  var doc;
  if (typeof window === 'undefined') {
    var jsdom = require('jsdom').jsdom;
    var jsDomDoc = jsdom("");
    // These variables are hoisted to the top of the function scope, so we give
    // them nicknames to avoid conflicts when ran in the browser.
    var win = jsDomDoc.createWindow();
    doc = win.document;
  } else {
    doc = document;
  }

  var Janitor = {
    clean: function (html, config) {
      config = config || {};
      config.tags = config.tags || {};
      config.protocols = config.protocols || [];
      config.protocolAttrs = config.protocolAttrs || ['src', 'href'];

      var sandbox = doc.createElement('div');
      sandbox.innerHTML = html;

      var sanitize = function (parentNode) {
        for (var i = 0; i < parentNode.childNodes.length; i += 1) {
          var node = parentNode.childNodes[i],
              nodeName = node.nodeName.toLowerCase(),
              attrs = config.tags[nodeName];

          // Ignore text nodes and nodes that have already been sanitized
          if (node.nodeType === 3 || node._sanitized) {
            continue;
          }

          // Drop tag entirely
          if (!config.tags[nodeName]) {
            while (node.childNodes.length > 0) {
              parentNode.insertBefore(node.childNodes[0], node);
            }
            parentNode.removeChild(node);

            sanitize(parentNode);
            break;
          }

          // Sanitize attributes
          for (var a = 0; a < node.attributes.length; a += 1) {
            var attr = node.attributes[a],
                attrName = attr.name.toLowerCase();

            // Allow protocol?
            if (config.protocolAttrs.indexOf(attrName) !== -1) {
              var url = doc.createElement('a');
              url.href = attr.value;

              if (config.protocols.indexOf(url.protocol.replace(/\:$/, '')) === -1) {
                node.setAttribute(node.attributes[a].name, '');
              }
            }

            // Allow attribute?
            if (attrs.indexOf(attrName) === -1) {
              node.removeAttribute(node.attributes[a].name);
              // Shift the array to continue looping.
              a = a - 1;
            }
          }

          // Sanitize children
          sanitize(node);

          // Mark node as sanitized so it's ignored in future runs
          node._sanitized = true;
        }
      };

      sanitize(sandbox);

      return sandbox.innerHTML;
    }
  };

  // Make Janitor available in both browser and non-browser contexts
  if (typeof window !== 'undefined') {
    window.Janitor = Janitor;
  } else {
    module.exports = Janitor;
  }
})();
<|endoftext|>


<commit_before>define([
    "dojo/_base/declare",
    "dojo/_base/lang",
    "dojo/_base/array",
    "dojo/aspect",
    "dijit/_WidgetBase",
    "dijit/_TemplatedMixin",
    "dijit/TooltipDialog",
    "dijit/popup",
    "dgrid/OnDemandGrid",
    "dgrid/Selection",
    "dgrid/Keyboard",
    "dgrid/extensions/DnD",
    "dgrid/extensions/ColumnHider",
    "dgrid/extensions/ColumnResizer",
    "dgrid/extensions/DijitRegistry",
    "dgrid/Editor",
    "dgrid/Selector",
    "dgrid/Tree",
    "dojo/dom",
    "dojo/dom-attr",
    "dojo/dom-construct",
    "dojo/dom-style",
    "dojo/dom-geometry",
    "dojo/query",
    "dojo/NodeList-traverse",
    "dojo/window",
    "dojo/topic",
    "dojo/on",
    "dojo/when",
    "dojo/has",
    "../../../model/meta/Model",
    "../../../locale/Dictionary",
    "../../data/input/Factory",
    "../../data/display/Renderer",
    "dojo/text!./template/GridWidget.html"
], function (
    declare,
    lang,
    array,
    aspect,
    _WidgetBase,
    _TemplatedMixin,
    TooltipDialog,
    popup,
    OnDemandGrid,
    Selection,
    Keyboard,
    DnD,
    ColumnHider,
    ColumnResizer,
    DijitRegistry,
    Editor,
    Selector,
    Tree,
    dom,
    domAttr,
    domConstruct,
    domStyle,
    domGeom,
    query,
    traverse,
    win,
    topic,
    on,
    when,
    has,
    Model,
    Dict,
    ControlFactory,
    Renderer,
    template
) {
    return declare([_WidgetBase, _TemplatedMixin], {

        type: null,
        store: null,
        columns: [], // array of attribute names or columns objects
        actions: [],
        enabledFeatures: [], // array of strings matching items in optionalFeatures
        canEdit: true,
        initialFilter: null,
        rowEnhancer: null,

        actionsByName: {},
        templateString: lang.replace(template, Dict.tplTranslate),
        grid: null,
        gridFilter: {},

        defaultFeatures: {
            'Selection': Selection,
            'Keyboard': Keyboard,
            'ColumnHider': ColumnHider,
            'ColumnResizer': ColumnResizer,
            'DijitRegistry': DijitRegistry
        },
        optionalFeatures: {
            'Selector': Selector,
            'DnD': DnD,
            'Tree': Tree
        },

        dndInProgress: false,

        // too many filter calls make the rows disapear sometimes
        // so we define a minimal time span that has to be passed until the next filter is set
        lastFiltered: null,
        filterLifetime: 1000,

        constructor: function (params) {
            if (params && params.actions) {
                params.actionsByName = {};
                for (var i=0,count=params.actions.length; i<count; i++) {
                    var action = params.actions[i];
                    params.actionsByName[action.name] = action;
                }
            }
            declare.safeMixin(this, params);
        },

        postCreate: function () {
            this.inherited(arguments);

            ControlFactory.loadControlClasses(this.type).then(lang.hitch(this, function(controls) {

                this.grid = this.buildGrid(controls);
                this.grid.startup();
                this.own(
                    on(window, "resize", lang.hitch(this, this.onResize)),
                    on(this.grid, "click", lang.hitch(this, function(e) {
                        // close summary tooltip
                        if (this.summaryDialog) {
                            popup.close(this.summaryDialog);
                        }
                        // process grid clicks
                        var links = query(e.target).closest("a");
                        if (links.length > 0) {
                          var actionName = domAttr.get(links[0], "data-action");
                          var action = this.actionsByName[actionName];
                          if (action) {
                              // cell action
                              e.preventDefault();
                              // execute the action
                              var columnNode = e.target.parentNode;
                              var row = this.grid.row(columnNode);
                              action.entity = row.data;
                              action.execute();
                              // refresh the action cell, if the grid is still present
                              if (this.grid.collection) {
                                var cell = this.grid.cell(columnNode);
                                this.grid.refreshCell(cell);
                              }
                          }
                        }
                    })),
                    on(this.grid, "mouseover", lang.hitch(this, function(e) {
                        var row = this.grid.row(e.target.parentNode);
                        if (row) {
                            var column = this.grid.column(e.target);
                            var typeClass = Model.getType(this.type);
                            if (column && column.field === typeClass.displayValues[0]) {
                                if (typeof typeClass.getSummary === 'function') {
                                    when(typeClass.getSummary(row.data), lang.hitch(this, function(text) {
                                        if (text) {
                                            this.summaryDialog = new TooltipDialog({
                                                content: text,
                                                onMouseLeave: lang.hitch(this, function() {
                                                    popup.close(this.summaryDialog);
                                                })
                                            });
                                            popup.open({
                                                popup: this.summaryDialog,
                                                around: e.target.parentNode
                                            });
                                        }
                                    }));
                                }
                            }
                        }
                    })),
                    on(this.grid, "mouseout", lang.hitch(this, function(e) {
                        if (this.summaryDialog) {
                            popup.close(this.summaryDialog);
                        }
                    })),
                    topic.subscribe("store-datachange", lang.hitch(this, function(data) {
                        if (this.dndInProgress) {
                            topic.publish('ui/_include/widget/GridWidget/dnd-end', null);
                            this.dndInProgress = false;
                        }
                    })),
                    topic.subscribe("/dnd/drop", lang.hitch(this, function(source, nodes, copy, target) {
                        this.dndInProgress = true;
                        topic.publish('ui/_include/widget/GridWidget/dnd-start', null);
                    }))
                );
                this.onResize();
            }));
        },

        buildGrid: function (controls) {
            var _this = this;

            // select features
            var features = [];
            var featureNames = [];
            for (var idx in this.defaultFeatures) {
                featureNames.push(idx);
                features.push(this.defaultFeatures[idx]);
            }
            for (var idx in this.enabledFeatures) {
                var featureName = this.enabledFeatures[idx];
                if (this.optionalFeatures[featureName]) {
                    featureNames.push(featureName);
                    features.push(this.optionalFeatures[featureName]);
                }
            }

            // create columns
            var columns = [];
            if (array.indexOf(featureNames, 'Selector') !== -1) {
                columns.push({
                    label: " ",
                    selector: "checkbox",
                    field: "selector",
                    unhidable: true,
                    sortable: true,
                    resizable: false
                });
            }
            var typeClass = Model.getType(this.type);
            var renderOptions = {};
            for (var i=0, count=this.columns.length; i<count; i++) {
                var columnDef = this.columns[i];
                if (typeof(columnDef) === "string") {
                    // attribute column
                    var curValue = columnDef;
                    var curAttributeDef = typeClass.getAttribute(curValue);
                    if (curAttributeDef !== null) {
                        var controlClass = controls[curAttributeDef.inputType];
                        var column = {
                            label: Dict.translate(curValue),
                            field: curValue,
                            editor: controlClass,
                            editorArgs: {
                                name: curAttributeDef.name,
                                helpText: Dict.translate(curAttributeDef.description),
                                inputType: curAttributeDef.inputType,
                                entity: null, // will be set in dgrid-editor-show event
                                style: 'height:20px; padding:0;',
                                isInlineEditor: true
                            },
                            editOn: "dblclick",
                            canEdit: this.canEdit ? lang.hitch(curAttributeDef, function(obj, value) {
                                // only allow to edit editable objects of grid's own type
                                var sameType = _this.isSameType(obj);
                                return sameType && typeClass.isEditable(this, obj);
                            }) : function(obj, value) {
                                return false;
                            },
                            autoSave: true,
                            sortable: true,
                            renderCell: lang.hitch(curAttributeDef, function(object, data, td, options) {
                                when(Renderer.render(data, this, renderOptions), function(value) {
                                    td.innerHTML = value;
                                });
                            })
                        };
                        if (array.indexOf(featureNames, 'Tree') !== -1) {
                            column.renderExpando = true;
                        }
                        columns.push(column);
                    }
                }
                else {
                    // custom column
                    columns.push(columnDef);
                }
            }

            // add actions column
            if (this.actions.length > 0) {
                columns.push({
                    label: " ",
                    field: "actions-"+this.actions.length,
                    unhidable: true,
                    sortable: false,
                    resizable: false,
                    formatter: lang.hitch(this, function(data, entity) {
                        var html = '
';
                        for (var name in this.actionsByName) {
                            var action = this.actionsByName[name];
                            action.entity = entity;
                            var url = action.getUrl() || '#';
                            html += '';
                        }
                        html += '
';
                        return html;
                    })
                });
            }

            // create widget
            var grid = new (declare([OnDemandGrid, Editor].concat(features)))({
                getBeforePut: true,
                columns: columns,
                collection: this.store.filter(this.initialFilter),
                selectionMode: "extended",
                dndParams: {
                    checkAcceptance: lang.hitch(this, function(source, nodes) {
                        var row = this.grid.row(nodes[0]);
                        if (!row) {
                            return false;
                        }
                        return this.isSameType(row.data);
                    })
                },
                loadingMessage: Dict.translate("Loading"),
                noDataMessage: Dict.translate("No data"),
                minRowsPerPage: 25,
                maxRowsPerPage: 25,
                bufferRows: 0,
                pagingDelay: 0,
                farOffRemoval: Infinity,
                pagingMethod: 'throttleDelayed',
            }, this.gridNode);

            if (typeof this.rowEnhancer === 'function') {
                aspect.after(grid, 'renderRow', lang.hitch(this, function(row, args) {
                    return this.rowEnhancer(row, args[0]);
                }));
            }

            grid.on("dgrid-editor-show", function(evt) {
                // set the entity property on the input control
                evt.editor.entity = evt.cell.row.data;
            })

            grid.on("dgrid-error", function(evt) {
                topic.publish('ui/_include/widget/GridWidget/error', evt.error);
            });

            grid.on("dgrid-refresh-complete", lang.hitch(this, function(evt) {
                topic.publish('ui/_include/widget/GridWidget/refresh-complete', evt.grid);
                grid.resize();
            }));

            return grid;
        },

        isSameType: function(entity) {
            var oid = entity.get('oid');
            var typeName = Model.getFullyQualifiedTypeName(Model.getTypeNameFromOid(oid));
            return this.store.typeName === typeName;
        },

        getSelectedOids: function() {
            var oids = [];
            for (var id in this.grid.selection) {
                if (this.grid.selection[id]) {
                    var row = this.grid.row(id);
                    oids.push(row.data.get('oid'));
                }
            }
            return oids;
        },

        refresh: function() {
            this.grid.refresh({
                keepScrollPosition: true
            });
        },

        filter: function(filter) {
            // prevent too many filter calls
            var now = (new Date()).getTime();
            if (this.lastFiltered !== null && now-this.lastFiltered < this.filterLifetime) {
                return;
            }
            this.lastFiltered = now;

            this.gridFilter = filter;
            if (this.grid) {
                this.grid.set('collection', this.store.filter(this.gridFilter));
            }
        },

        onResize: function() {
            if (window.innerWidth > 640) {
                // TODO: remove magic number
                var vs = win.getBox();

                // calculate height of dynamic elements
                var navbarHeight = 0;
                var toolbarHeight = 0;
                var footerHeight = 0;

                var navbar = query(".navbar");
                if (navbar.length > 0) {
                  navbarHeight = domGeom.getMarginBox(navbar[0]).h;
                }
                var toolbar = query('[data-dojo-attach-point$=\"toolbarNode\"]');
                if (toolbar.length > 0) {
                  toolbarHeight = domGeom.getMarginBox(toolbar[0]).h;
                }
                var footer = dom.byId("footer");
                if (footer) {
                  footerHeight = domGeom.getMarginBox(footer).h;
                }
                var h = this.height ? this.height : vs.h-navbarHeight-toolbarHeight-footerHeight-210;
                if (h >= 0) {
                    domAttr.set(this.grid.domNode, "style", {height: h+"px"});
                }
            }
        }
    });
});<commit_msg>improved grid loading<commit_after>define([
    "dojo/_base/declare",
    "dojo/_base/lang",
    "dojo/_base/array",
    "dojo/aspect",
    "dijit/_WidgetBase",
    "dijit/_TemplatedMixin",
    "dijit/TooltipDialog",
    "dijit/popup",
    "dgrid/OnDemandGrid",
    "dgrid/Selection",
    "dgrid/Keyboard",
    "dgrid/extensions/DnD",
    "dgrid/extensions/ColumnHider",
    "dgrid/extensions/ColumnResizer",
    "dgrid/extensions/DijitRegistry",
    "dgrid/Editor",
    "dgrid/Selector",
    "dgrid/Tree",
    "dojo/dom",
    "dojo/dom-attr",
    "dojo/dom-construct",
    "dojo/dom-style",
    "dojo/dom-geometry",
    "dojo/query",
    "dojo/NodeList-traverse",
    "dojo/window",
    "dojo/topic",
    "dojo/on",
    "dojo/when",
    "dojo/has",
    "../../../model/meta/Model",
    "../../../locale/Dictionary",
    "../../data/input/Factory",
    "../../data/display/Renderer",
    "dojo/text!./template/GridWidget.html"
], function (
    declare,
    lang,
    array,
    aspect,
    _WidgetBase,
    _TemplatedMixin,
    TooltipDialog,
    popup,
    OnDemandGrid,
    Selection,
    Keyboard,
    DnD,
    ColumnHider,
    ColumnResizer,
    DijitRegistry,
    Editor,
    Selector,
    Tree,
    dom,
    domAttr,
    domConstruct,
    domStyle,
    domGeom,
    query,
    traverse,
    win,
    topic,
    on,
    when,
    has,
    Model,
    Dict,
    ControlFactory,
    Renderer,
    template
) {
    return declare([_WidgetBase, _TemplatedMixin], {

        type: null,
        store: null,
        columns: [], // array of attribute names or columns objects
        actions: [],
        enabledFeatures: [], // array of strings matching items in optionalFeatures
        canEdit: true,
        initialFilter: null,
        rowEnhancer: null,

        actionsByName: {},
        templateString: lang.replace(template, Dict.tplTranslate),
        grid: null,
        gridFilter: {},

        defaultFeatures: {
            'Selection': Selection,
            'Keyboard': Keyboard,
            'ColumnHider': ColumnHider,
            'ColumnResizer': ColumnResizer,
            'DijitRegistry': DijitRegistry
        },
        optionalFeatures: {
            'Selector': Selector,
            'DnD': DnD,
            'Tree': Tree
        },

        dndInProgress: false,

        // too many filter calls make the rows disapear sometimes
        // so we define a minimal time span that has to be passed until the next filter is set
        lastFiltered: null,
        filterLifetime: 1000,

        constructor: function (params) {
            if (params && params.actions) {
                params.actionsByName = {};
                for (var i=0,count=params.actions.length; i<count; i++) {
                    var action = params.actions[i];
                    params.actionsByName[action.name] = action;
                }
            }
            declare.safeMixin(this, params);
        },

        postCreate: function () {
            this.inherited(arguments);

            ControlFactory.loadControlClasses(this.type).then(lang.hitch(this, function(controls) {

                this.grid = this.buildGrid(controls);
                this.grid.startup();
                this.own(
                    on(window, "resize", lang.hitch(this, this.onResize)),
                    on(this.grid, "click", lang.hitch(this, function(e) {
                        // close summary tooltip
                        if (this.summaryDialog) {
                            popup.close(this.summaryDialog);
                        }
                        // process grid clicks
                        var links = query(e.target).closest("a");
                        if (links.length > 0) {
                          var actionName = domAttr.get(links[0], "data-action");
                          var action = this.actionsByName[actionName];
                          if (action) {
                              // cell action
                              e.preventDefault();
                              // execute the action
                              var columnNode = e.target.parentNode;
                              var row = this.grid.row(columnNode);
                              action.entity = row.data;
                              action.execute();
                              // refresh the action cell, if the grid is still present
                              if (this.grid.collection) {
                                var cell = this.grid.cell(columnNode);
                                this.grid.refreshCell(cell);
                              }
                          }
                        }
                    })),
                    on(this.grid, "mouseover", lang.hitch(this, function(e) {
                        var row = this.grid.row(e.target.parentNode);
                        if (row) {
                            var column = this.grid.column(e.target);
                            var typeClass = Model.getType(this.type);
                            if (column && column.field === typeClass.displayValues[0]) {
                                if (typeof typeClass.getSummary === 'function') {
                                    when(typeClass.getSummary(row.data), lang.hitch(this, function(text) {
                                        if (text) {
                                            this.summaryDialog = new TooltipDialog({
                                                content: text,
                                                onMouseLeave: lang.hitch(this, function() {
                                                    popup.close(this.summaryDialog);
                                                })
                                            });
                                            popup.open({
                                                popup: this.summaryDialog,
                                                around: e.target.parentNode
                                            });
                                        }
                                    }));
                                }
                            }
                        }
                    })),
                    on(this.grid, "mouseout", lang.hitch(this, function(e) {
                        if (this.summaryDialog) {
                            popup.close(this.summaryDialog);
                        }
                    })),
                    topic.subscribe("store-datachange", lang.hitch(this, function(data) {
                        if (this.dndInProgress) {
                            topic.publish('ui/_include/widget/GridWidget/dnd-end', null);
                            this.dndInProgress = false;
                        }
                    })),
                    topic.subscribe("/dnd/drop", lang.hitch(this, function(source, nodes, copy, target) {
                        this.dndInProgress = true;
                        topic.publish('ui/_include/widget/GridWidget/dnd-start', null);
                    }))
                );
                this.onResize();
            }));
        },

        buildGrid: function (controls) {
            var _this = this;

            // select features
            var features = [];
            var featureNames = [];
            for (var idx in this.defaultFeatures) {
                featureNames.push(idx);
                features.push(this.defaultFeatures[idx]);
            }
            for (var idx in this.enabledFeatures) {
                var featureName = this.enabledFeatures[idx];
                if (this.optionalFeatures[featureName]) {
                    featureNames.push(featureName);
                    features.push(this.optionalFeatures[featureName]);
                }
            }

            // create columns
            var columns = [];
            if (array.indexOf(featureNames, 'Selector') !== -1) {
                columns.push({
                    label: " ",
                    selector: "checkbox",
                    field: "selector",
                    unhidable: true,
                    sortable: true,
                    resizable: false
                });
            }
            var typeClass = Model.getType(this.type);
            var renderOptions = {};
            for (var i=0, count=this.columns.length; i<count; i++) {
                var columnDef = this.columns[i];
                if (typeof(columnDef) === "string") {
                    // attribute column
                    var curValue = columnDef;
                    var curAttributeDef = typeClass.getAttribute(curValue);
                    if (curAttributeDef !== null) {
                        var controlClass = controls[curAttributeDef.inputType];
                        var column = {
                            label: Dict.translate(curValue),
                            field: curValue,
                            editor: controlClass,
                            editorArgs: {
                                name: curAttributeDef.name,
                                helpText: Dict.translate(curAttributeDef.description),
                                inputType: curAttributeDef.inputType,
                                entity: null, // will be set in dgrid-editor-show event
                                style: 'height:20px; padding:0;',
                                isInlineEditor: true
                            },
                            editOn: "dblclick",
                            canEdit: this.canEdit ? lang.hitch(curAttributeDef, function(obj, value) {
                                // only allow to edit editable objects of grid's own type
                                var sameType = _this.isSameType(obj);
                                return sameType && typeClass.isEditable(this, obj);
                            }) : function(obj, value) {
                                return false;
                            },
                            autoSave: true,
                            sortable: true,
                            renderCell: lang.hitch(curAttributeDef, function(object, data, td, options) {
                                when(Renderer.render(data, this, renderOptions), function(value) {
                                    td.innerHTML = value;
                                });
                            })
                        };
                        if (array.indexOf(featureNames, 'Tree') !== -1) {
                            column.renderExpando = true;
                        }
                        columns.push(column);
                    }
                }
                else {
                    // custom column
                    columns.push(columnDef);
                }
            }

            // add actions column
            if (this.actions.length > 0) {
                columns.push({
                    label: " ",
                    field: "actions-"+this.actions.length,
                    unhidable: true,
                    sortable: false,
                    resizable: false,
                    formatter: lang.hitch(this, function(data, entity) {
                        var html = '
';
                        for (var name in this.actionsByName) {
                            var action = this.actionsByName[name];
                            action.entity = entity;
                            var url = action.getUrl() || '#';
                            html += '';
                        }
                        html += '
';
                        return html;
                    })
                });
            }

            // create widget
            var grid = new (declare([OnDemandGrid, Editor].concat(features)))({
                getBeforePut: true,
                columns: columns,
                collection: this.store.filter(this.initialFilter),
                selectionMode: "extended",
                dndParams: {
                    checkAcceptance: lang.hitch(this, function(source, nodes) {
                        var row = this.grid.row(nodes[0]);
                        if (!row) {
                            return false;
                        }
                        return this.isSameType(row.data);
                    })
                },
                loadingMessage: Dict.translate("Loading"),
                noDataMessage: Dict.translate("No data"),
                minRowsPerPage: 30,
                maxRowsPerPage: 30,
                bufferRows: 0,
                pagingDelay: 0,
                farOffRemoval: Infinity,
                pagingMethod: 'throttleDelayed'
            }, this.gridNode);

            if (typeof this.rowEnhancer === 'function') {
                aspect.after(grid, 'renderRow', lang.hitch(this, function(row, args) {
                    return this.rowEnhancer(row, args[0]);
                }));
            }

            grid.on("dgrid-editor-show", function(evt) {
                // set the entity property on the input control
                evt.editor.entity = evt.cell.row.data;
            })

            grid.on("dgrid-error", function(evt) {
                topic.publish('ui/_include/widget/GridWidget/error', evt.error);
            });

            grid.on("dgrid-refresh-complete", lang.hitch(this, function(evt) {
                topic.publish('ui/_include/widget/GridWidget/refresh-complete', evt.grid);
                grid.resize();
            }));

            return grid;
        },

        isSameType: function(entity) {
            var oid = entity.get('oid');
            var typeName = Model.getFullyQualifiedTypeName(Model.getTypeNameFromOid(oid));
            return this.store.typeName === typeName;
        },

        getSelectedOids: function() {
            var oids = [];
            for (var id in this.grid.selection) {
                if (this.grid.selection[id]) {
                    var row = this.grid.row(id);
                    oids.push(row.data.get('oid'));
                }
            }
            return oids;
        },

        refresh: function() {
            this.grid.refresh({
                keepScrollPosition: true
            });
        },

        filter: function(filter) {
            // prevent too many filter calls
            var now = (new Date()).getTime();
            if (this.lastFiltered !== null && now-this.lastFiltered < this.filterLifetime) {
                return;
            }
            this.lastFiltered = now;

            this.gridFilter = filter;
            if (this.grid) {
                this.grid.set('collection', this.store.filter(this.gridFilter));
            }
        },

        onResize: function() {
            if (window.innerWidth > 640) {
                // TODO: remove magic number
                var vs = win.getBox();

                // calculate height of dynamic elements
                var navbarHeight = 0;
                var toolbarHeight = 0;
                var footerHeight = 0;

                var navbar = query(".navbar");
                if (navbar.length > 0) {
                  navbarHeight = domGeom.getMarginBox(navbar[0]).h;
                }
                var toolbar = query('[data-dojo-attach-point$=\"toolbarNode\"]');
                if (toolbar.length > 0) {
                  toolbarHeight = domGeom.getMarginBox(toolbar[0]).h;
                }
                var footer = dom.byId("footer");
                if (footer) {
                  footerHeight = domGeom.getMarginBox(footer).h;
                }
                var h = this.height ? this.height : vs.h-navbarHeight-toolbarHeight-footerHeight-210;
                if (h >= 0) {
                    domAttr.set(this.grid.domNode, "style", {height: h+"px"});
                }
            }
        }
    });
});<|endoftext|>