<commit_before>/**
 * extends BaseModel with ACL functionality.
 */
var _ = require('lodash');
var when = require('when');
var BaseModel = require('./base_model');
var _debug = require('debug');
var logId = 'serverbone:models:acl';
var acl = require('serverbone-acl');
var errors = require('../errors');
var util = require('util');
var debug = {
  trace: _debug(logId + ':trace'),
  log: _debug(logId + ':log'),
  warn: _debug(logId + ':warn'),
  error: _debug(logId + ':error')
};

var ACLModel = BaseModel.extend({
  initialize: function(args, options) {
    ACLModel.__super__.initialize.apply(this, arguments);
    options = options ||  {};
    this._processPermissions();
    this.addRoles(options.roles);
    if (options.actor) {
      this.actor = options.actor;
    }
  },

  /**
   * Check access before proceeding with save
   */
  save: function(key, val, options) {
    var opts = options ||  {};
    if (key == null || typeof key === 'object') {
      opts = val ||  {};
    }
    opts.action = opts.action || (this.isNew() ? 'create' : 'update');
    opts.actor = opts.actor ||  this.actor;
    debug.trace('ACLModel(%s).save %s actor present: %d', this.type || '', opts.action, opts.actor !== undefined);
    return ACLModel.__super__.save.call(this, key, opts);
  },

  /**
   * Check access before proceeding with fetchAll
   */
  fetchAll: function(options) {
    options = options ||  {};
    options.action = 'read';
    options.actor = options.actor ||  this.actor;
    debug.trace('ACLModel(%s).fetchAll %s actor present: %d', this.type || '',
      options.action, options.actor !== undefined);
    return ACLModel.__super__.fetchAll.call(this, options);
  },

  /**
   * Check access before proceeding with fetch
   */
  fetch: function(options) {
    options = options ||  {};
    options.action = 'read';
    options.actor = options.actor ||  this.actor;
    debug.trace('ACLModel(%s).fetch %s actor present: %d',
      this.type || '', options.action, options.actor !== undefined);
    return ACLModel.__super__.fetch.call(this, options);
  },

  /**
   * fetchACLDependencies should be implemented if role resolving needs data e.g. from other model
   * @param  {Object} options options for fetch the dependencies
   * @return {Promise}
   */
  fetchACLDependencies: function(options) {
    return when.resolve();
  },

  /**
   * Process the permissions because the template-derived permissions might be
   * dependent on the data.
   *
   * @return {Promise}
   */
  afterFetch: function(options) {
    this._processPermissions();
    return ACLModel.__super__.afterFetch.call(this, options);
  },

  /**
   * Check access before proceeding with destroy
   */
  destroy: function(options) {
    var self = this;
    options = options ||  {};
    options.action = 'destroy';
    options.actor = options.actor || this.actor;
    debug.trace('ACLModel(%s).destroy %s actor present: %d',
      this.type || '', options.action, options.actor !== undefined);

    return this
      .fetchACLDependencies(options)
      .then(function() {
        var canAccess = self.canAccess(options.action, options.actor);
        if (!canAccess) {
          var errorMsg = util.format(
            'No access to %s: %s (%s)',
            options.action,
            self.type || self.indexKey,
            self.id
          );
          return when.reject(new errors.ForbiddenError(errorMsg));
        }
        return ACLModel.__super__.destroy.call(self, options);
      });
  },

  /**
   * Add role/roles to this instance of the Model
   *
   * @param  {Object|Array} roles
   */
  addRoles: function(roles) {
  //  debug.trace('ACLModel(%s).addRoles %s', this.type || '', JSON.stringify(roles || []));
    this.roles = this.roles ||  [];
    if (!roles) return;
    if (!_.isArray(roles)) roles = [roles];
 //   debug.log('Adding roles %s to model %s', _.uniq(this.roles.concat(roles)), this.url());
    this.roles = _.uniq(this.roles.concat(roles));
  },

  /**
   * Return roles for actor accessing this Model.
   *
   * @param  {Object} actor
   * @return {Array}
   */
  getRoles: function(actor) {
    //debug.trace('ACLModel(%s).getRoles for model type %s, self.id: %s, actor.id: %s',
    //  this.type || '', (actor || {}).type || '', this.id, (actor || {}).id);
    //debug.log('Model %s asking roles from %s',actor ? actor.url() : '', this.url());
    var roles = [];
    if (!actor) return roles;

    _.each(this.relationDefinitions, function(value, key) {
      if (!value.references || !value.Class) return;
      var refs = _.pairs(value.references)[0];
      var relation = this.get(key);
      if (relation
        && actor.type === relation.type
        && actor.has(refs[0]) && this.has(refs[1])
        && actor.get(refs[0]) === this.get(refs[1])) {
          roles = roles.concat(value.roles || key);
      }
    }, this);

    roles = _.uniq((actor.roles || []).concat(roles));
    return roles;
  },

  /**
   * init ACL
   */
  _processPermissions: function() {
    var permissions = {};
    if (this.schema && this.schema.permissions) {
      // permissons keys may be templated -> replace based on model attributes
      _.each(_.keys(this.schema.permissions), function(templatedRole) {
        var role = BaseModel.formatTemplatedProperties.call(this, templatedRole).call(this);
        permissions[role] = this.schema.permissions[templatedRole];
      }, this);
    }
    this.acl = new acl.ACL(permissions);
  },

  /**
   * Filters data in JSON to include only properties that actor has access to
   * @param  {Object} options options for reading the JSON, should contain at least actor object
   * @return {Object}
   */
  toJSON: function(options) {
    options = options || {};
    var json = ACLModel.__super__.toJSON.apply(this, arguments);
    if (!json) return json;
    if (!options.actor && (options.user || this.actor)) {
      options.actor = options.user || this.actor;
    }
    if (options.actor && this.acl) {
      var action = options.action || 'read';
      var readableFields = this.propertiesWithAccess(action, options.actor);
      _.each(Object.keys(json), function(key) {
        if (readableFields.indexOf(key) === -1) {
          delete json[key];
        }
      });
    }
    return json;
  },

  // check access to this model
  canAccess: function(action, model) {
   // debug.trace('canAccess %s(%s): %s', model ? model.type : '', model ? model.id : '', action);
    if (!this.acl) return true;
    if (action === 'validate') return true;
    if (!model) {
      debug.log('using default access model');
      model = new ACLModel();
    }
//    debug.log('canAccess %s %s', action, model.url());
    return this.acl.assert(this.getRoles(model), action);
  },

  // Field level access: get properties which user has permissions
  // field can override object level access mask
  propertiesWithAccess: function(action, model) {
    if (!model) {
      debug.log('using default access model');
      model = new ACLModel();
    }
    //debug.trace('propertiesWithAccess to action %s with model %s', action, model.toJSON());
    var objectAccess = this.canAccess(action, model);
    var keys = [];
    var self = this;
    if (!this.schema || !this.schema.properties) return keys;

    function grantAccess(properyList) {
      _.each(properyList, function(prop, key) {
        if (!prop.permissions && objectAccess) {
          keys.push(key);
        } else if (prop.permissions) {
          var mask = new acl.ACL(prop.permissions);
          var roles = self.getRoles(model);
          if (mask.assert(roles, action)) {
            //debug.log('adding key %s because of %s for mask %s', key, roles.join(','), JSON.stringify(mask));
            keys.push(key);
          }
        }
      });
    }
    grantAccess(this.schema.properties);

    debug.log('propertiesWithAccess giving out keys: %s to %s(%s) to %s(%s)',
      keys.join(','), model.type, model.id, self.type, self.id);
    return keys;
  },

  /**
   * Override default validation by adding ACL check. Checks if actor has access to proceed with given action.
   */
  validate: function(attrs, options) {
    var changedAttrs = this.changedSinceSync ? this.changedSinceSync() : this.changedAttributes();
    options = options ? _.clone(options) : {};
    var action = options.action || (this.isNew() ? 'create' : 'update');
    var actor = options.actor || options.user || options.model || this.actor;
    var oldAttrs = action === 'update' ? this.previousAttributes() : attrs;

    if (this.acl) {
      var canAccess = this.canAccess(action, actor);
      var noAccessTo;
      var changedProps;
      if (canAccess && changedAttrs) {
        var schemaProps = Object.keys(this.schema.properties);
        changedProps = Object.keys(changedAttrs);
        changedProps = _.filter(changedProps, function(prop) {
          var oldValue = _.isDate(oldAttrs[prop]) ? oldAttrs[prop].getTime() : oldAttrs[prop];
          var newValue = _.isDate(this.get(prop)) ? this.get(prop).getTime() : this.get(prop);
          return schemaProps.indexOf(prop) > -1 && oldValue !== newValue;
        }, this);
        var canActProps = this.propertiesWithAccess(action, actor);
        noAccessTo = _.difference(changedProps, canActProps);
      }
      if ((!canAccess && changedAttrs) || (noAccessTo && noAccessTo.length > 0 && action !== 'validate')) {
        debug.log('Failing validation, no access with actor: %s roles %s - action %s to fields %s',
          actor && actor.id,
          this.getRoles(actor),
          action,
          JSON.stringify(noAccessTo)
        );
        var errorMsg = util.format('No access to %s %s (%s)', action, this.type, this.id);
        if (noAccessTo) errorMsg += ' fields:' + noAccessTo;
        var error = new errors.ForbiddenError(errorMsg);
        if (options && options.error) {
          if (options.error.length === 3) {
            options.error(this, error);
          } else {
            options.error(error);
          }
        }
        return error;
      }
    }
    return ACLModel.__super__.validate.apply(this, arguments);
  }
});

module.exports = ACLModel;
<commit_msg>Get actor from parent if not given in the options<commit_after>/**
 * extends BaseModel with ACL functionality.
 */
var _ = require('lodash');
var when = require('when');
var BaseModel = require('./base_model');
var _debug = require('debug');
var logId = 'serverbone:models:acl';
var acl = require('serverbone-acl');
var errors = require('../errors');
var util = require('util');
var debug = {
  trace: _debug(logId + ':trace'),
  log: _debug(logId + ':log'),
  warn: _debug(logId + ':warn'),
  error: _debug(logId + ':error')
};

var ACLModel = BaseModel.extend({
  initialize: function(args, options) {
    ACLModel.__super__.initialize.apply(this, arguments);
    options = options ||  {};
    this._processPermissions();
    this.addRoles(options.roles);
    if (options.actor) {
      this.actor = options.actor;
    }
    if (!this.actor && options.parent) {
      this.actor = options.parent.actor;
    }
  },

  /**
   * Check access before proceeding with save
   */
  save: function(key, val, options) {
    var opts = options ||  {};
    if (key == null || typeof key === 'object') {
      opts = val ||  {};
    }
    opts.action = opts.action || (this.isNew() ? 'create' : 'update');
    opts.actor = opts.actor ||  this.actor;
    debug.trace('ACLModel(%s).save %s actor present: %d', this.type || '', opts.action, opts.actor !== undefined);
    return ACLModel.__super__.save.call(this, key, opts);
  },

  /**
   * Check access before proceeding with fetchAll
   */
  fetchAll: function(options) {
    options = options ||  {};
    options.action = 'read';
    options.actor = options.actor ||  this.actor;
    debug.trace('ACLModel(%s).fetchAll %s actor present: %d', this.type || '',
      options.action, options.actor !== undefined);
    return ACLModel.__super__.fetchAll.call(this, options);
  },

  /**
   * Check access before proceeding with fetch
   */
  fetch: function(options) {
    options = options ||  {};
    options.action = 'read';
    options.actor = options.actor ||  this.actor;
    debug.trace('ACLModel(%s).fetch %s actor present: %d',
      this.type || '', options.action, options.actor !== undefined);
    return ACLModel.__super__.fetch.call(this, options);
  },

  /**
   * fetchACLDependencies should be implemented if role resolving needs data e.g. from other model
   * @param  {Object} options options for fetch the dependencies
   * @return {Promise}
   */
  fetchACLDependencies: function(options) {
    return when.resolve();
  },

  /**
   * Process the permissions because the template-derived permissions might be
   * dependent on the data.
   *
   * @return {Promise}
   */
  afterFetch: function(options) {
    this._processPermissions();
    return ACLModel.__super__.afterFetch.call(this, options);
  },

  /**
   * Check access before proceeding with destroy
   */
  destroy: function(options) {
    var self = this;
    options = options ||  {};
    options.action = 'destroy';
    options.actor = options.actor || this.actor;
    debug.trace('ACLModel(%s).destroy %s actor present: %d',
      this.type || '', options.action, options.actor !== undefined);

    return this
      .fetchACLDependencies(options)
      .then(function() {
        var canAccess = self.canAccess(options.action, options.actor);
        if (!canAccess) {
          var errorMsg = util.format(
            'No access to %s: %s (%s)',
            options.action,
            self.type || self.indexKey,
            self.id
          );
          return when.reject(new errors.ForbiddenError(errorMsg));
        }
        return ACLModel.__super__.destroy.call(self, options);
      });
  },

  /**
   * Add role/roles to this instance of the Model
   *
   * @param  {Object|Array} roles
   */
  addRoles: function(roles) {
  //  debug.trace('ACLModel(%s).addRoles %s', this.type || '', JSON.stringify(roles || []));
    this.roles = this.roles ||  [];
    if (!roles) return;
    if (!_.isArray(roles)) roles = [roles];
 //   debug.log('Adding roles %s to model %s', _.uniq(this.roles.concat(roles)), this.url());
    this.roles = _.uniq(this.roles.concat(roles));
  },

  /**
   * Return roles for actor accessing this Model.
   *
   * @param  {Object} actor
   * @return {Array}
   */
  getRoles: function(actor) {
    //debug.trace('ACLModel(%s).getRoles for model type %s, self.id: %s, actor.id: %s',
    //  this.type || '', (actor || {}).type || '', this.id, (actor || {}).id);
    //debug.log('Model %s asking roles from %s',actor ? actor.url() : '', this.url());
    var roles = [];
    if (!actor) return roles;

    _.each(this.relationDefinitions, function(value, key) {
      if (!value.references || !value.Class) return;
      var refs = _.pairs(value.references)[0];
      var relation = this.get(key);
      if (relation
        && actor.type === relation.type
        && actor.has(refs[0]) && this.has(refs[1])
        && actor.get(refs[0]) === this.get(refs[1])) {
          roles = roles.concat(value.roles || key);
      }
    }, this);

    roles = _.uniq((actor.roles || []).concat(roles));
    return roles;
  },

  /**
   * init ACL
   */
  _processPermissions: function() {
    var permissions = {};
    if (this.schema && this.schema.permissions) {
      // permissons keys may be templated -> replace based on model attributes
      _.each(_.keys(this.schema.permissions), function(templatedRole) {
        var role = BaseModel.formatTemplatedProperties.call(this, templatedRole).call(this);
        permissions[role] = this.schema.permissions[templatedRole];
      }, this);
    }
    this.acl = new acl.ACL(permissions);
  },

  /**
   * Filters data in JSON to include only properties that actor has access to
   * @param  {Object} options options for reading the JSON, should contain at least actor object
   * @return {Object}
   */
  toJSON: function(options) {
    options = options || {};
    var json = ACLModel.__super__.toJSON.apply(this, arguments);
    if (!json) return json;
    if (!options.actor && (options.user || this.actor)) {
      options.actor = options.user || this.actor;
    }
    if (options.actor && this.acl) {
      var action = options.action || 'read';
      var readableFields = this.propertiesWithAccess(action, options.actor);
      _.each(Object.keys(json), function(key) {
        if (readableFields.indexOf(key) === -1) {
          delete json[key];
        }
      });
    }
    return json;
  },

  // check access to this model
  canAccess: function(action, model) {
   // debug.trace('canAccess %s(%s): %s', model ? model.type : '', model ? model.id : '', action);
    if (!this.acl) return true;
    if (action === 'validate') return true;
    if (!model) {
      debug.log('using default access model');
      model = new ACLModel();
    }
//    debug.log('canAccess %s %s', action, model.url());
    return this.acl.assert(this.getRoles(model), action);
  },

  // Field level access: get properties which user has permissions
  // field can override object level access mask
  propertiesWithAccess: function(action, model) {
    if (!model) {
      debug.log('using default access model');
      model = new ACLModel();
    }
    //debug.trace('propertiesWithAccess to action %s with model %s', action, model.toJSON());
    var objectAccess = this.canAccess(action, model);
    var keys = [];
    var self = this;
    if (!this.schema || !this.schema.properties) return keys;

    function grantAccess(properyList) {
      _.each(properyList, function(prop, key) {
        if (!prop.permissions && objectAccess) {
          keys.push(key);
        } else if (prop.permissions) {
          var mask = new acl.ACL(prop.permissions);
          var roles = self.getRoles(model);
          if (mask.assert(roles, action)) {
            //debug.log('adding key %s because of %s for mask %s', key, roles.join(','), JSON.stringify(mask));
            keys.push(key);
          }
        }
      });
    }
    grantAccess(this.schema.properties);

    debug.log('propertiesWithAccess giving out keys: %s to %s(%s) to %s(%s)',
      keys.join(','), model.type, model.id, self.type, self.id);
    return keys;
  },

  /**
   * Override default validation by adding ACL check. Checks if actor has access to proceed with given action.
   */
  validate: function(attrs, options) {
    var changedAttrs = this.changedSinceSync ? this.changedSinceSync() : this.changedAttributes();
    options = options ? _.clone(options) : {};
    var action = options.action || (this.isNew() ? 'create' : 'update');
    var actor = options.actor || options.user || options.model || this.actor;
    var oldAttrs = action === 'update' ? this.previousAttributes() : attrs;

    if (this.acl) {
      var canAccess = this.canAccess(action, actor);
      var noAccessTo;
      var changedProps;
      if (canAccess && changedAttrs) {
        var schemaProps = Object.keys(this.schema.properties);
        changedProps = Object.keys(changedAttrs);
        changedProps = _.filter(changedProps, function(prop) {
          var oldValue = _.isDate(oldAttrs[prop]) ? oldAttrs[prop].getTime() : oldAttrs[prop];
          var newValue = _.isDate(this.get(prop)) ? this.get(prop).getTime() : this.get(prop);
          return schemaProps.indexOf(prop) > -1 && oldValue !== newValue;
        }, this);
        var canActProps = this.propertiesWithAccess(action, actor);
        noAccessTo = _.difference(changedProps, canActProps);
      }
      if ((!canAccess && changedAttrs) || (noAccessTo && noAccessTo.length > 0 && action !== 'validate')) {
        debug.log('Failing validation, no access with actor: %s roles %s - action %s to fields %s',
          actor && actor.id,
          this.getRoles(actor),
          action,
          JSON.stringify(noAccessTo)
        );
        var errorMsg = util.format('No access to %s %s (%s)', action, this.type, this.id);
        if (noAccessTo) errorMsg += ' fields:' + noAccessTo;
        var error = new errors.ForbiddenError(errorMsg);
        if (options && options.error) {
          if (options.error.length === 3) {
            options.error(this, error);
          } else {
            options.error(error);
          }
        }
        return error;
      }
    }
    return ACLModel.__super__.validate.apply(this, arguments);
  }
});

module.exports = ACLModel;
<|endoftext|>


<commit_before>/**
 * # NodeGameClient Events Handling
 * Copyright(c) 2014 Stefano Balietti
 * MIT Licensed
 *
 * ---
 */
(function(exports, parent) {

    "use strict";

    var NGC = parent.NodeGameClient;

    /**
     * ### NodeGameClient.redirect
     *
     * Redirects a player to the specified url
     *
     * Works only if it is a monitor client to send
     * the message, i.e. players cannot redirect each
     * other.
     *
     * Examples
     *
     *  // Redirect to http://mydomain/mygame/missing_auth
     *  node.redirect('missing_auth', 'xxx');
     *
     *  // Redirect to external urls
     *  node.redirect('http://www.google.com');
     *
     * @param {string} url the url of the redirection
     * @param {string} who A player id or 'ALL'
     */
    NGC.prototype.redirect = function(url, who) {
        var msg;
        if ('string' !== typeof url) {
            throw new TypeError('node.redirect: url must be string.');
        }
        if ('undefined' === typeof who) {
            throw new TypeError('node.redirect: who must be string.');
        }
        msg = this.msg.create({
            target: this.constants.target.REDIRECT,
            data: url,
            to: who
        });
        this.socket.send(msg);
    };

    /**
     * ### NodeGameClient.remoteCommand
     *
     * Executes a game command on a client
     *
     * By default, only admins can send use this method, as messages
     * sent by players will be filtered out by the server.
     *
     * @param {string} command The command to execute
     * @param {string} to The id of the player to command
     */
    NGC.prototype.remoteCommand = function(command, to, options) {
        var msg;
        if ('string' !== typeof command) {
            throw new TypeError('node.remoteCommand: command must be string.');
        }
        if (!parent.constants.gamecommands[command]) {
            throw new Error('node.remoteCommand: unknown command: ' +
                            command + '.');
        }
        if ('string' !== typeof to) {
            throw new TypeError('node.remoteCommand: to must be string.');
        }

        msg = this.msg.create({
            target: this.constants.target.GAMECOMMAND,
            text: command,
            data: options,
            to: to
        });
        this.socket.send(msg);
    };

    /**
     * ### NodeGameClient.remoteAlert
     *
     * Displays an alert message in the screen of the client
     *
     * Message is effective only if the client has a _window_ object
     * with a global _alert_ method.
     *
     * @param {string} text The text of of the messagex
     * @param {string} to The id of the player to alert
     */
    NGC.prototype.remoteAlert = function(text, to) {
        var msg;
        if ('string' !== typeof text) {
            throw new TypeError('node.remoteAlert: text must be string.');
        }
        if ('undefined' === typeof to) {
            throw new TypeError('node.remoteAlert: to must be string.');
        }
        msg = this.msg.create({
            target: this.constants.target.ALERT,
            text: text,
            to: to
        });
        this.socket.send(msg);
    };

})(
    'undefined' != typeof node ? node : module.exports,
    'undefined' != typeof node ? node : module.parent.exports
);
<commit_msg>node.redirect strict type checking for second input param<commit_after>/**
 * # NodeGameClient Events Handling
 * Copyright(c) 2014 Stefano Balietti
 * MIT Licensed
 *
 * ---
 */
(function(exports, parent) {

    "use strict";

    var NGC = parent.NodeGameClient;

    /**
     * ### NodeGameClient.redirect
     *
     * Redirects a player to the specified url
     *
     * Works only if it is a monitor client to send
     * the message, i.e. players cannot redirect each
     * other.
     *
     * Examples
     *
     *  // Redirect to http://mydomain/mygame/missing_auth
     *  node.redirect('missing_auth', 'xxx');
     *
     *  // Redirect to external urls
     *  node.redirect('http://www.google.com');
     *
     * @param {string} url the url of the redirection
     * @param {string} who A player id or 'ALL'
     */
    NGC.prototype.redirect = function(url, who) {
        var msg;
        if ('string' !== typeof url) {
            throw new TypeError('node.redirect: url must be string.');
        }
        if ('string' !== typeof who) {
            throw new TypeError('node.redirect: who must be string.');
        }
        msg = this.msg.create({
            target: this.constants.target.REDIRECT,
            data: url,
            to: who
        });
        this.socket.send(msg);
    };

    /**
     * ### NodeGameClient.remoteCommand
     *
     * Executes a game command on a client
     *
     * By default, only admins can send use this method, as messages
     * sent by players will be filtered out by the server.
     *
     * @param {string} command The command to execute
     * @param {string} to The id of the player to command
     */
    NGC.prototype.remoteCommand = function(command, to, options) {
        var msg;
        if ('string' !== typeof command) {
            throw new TypeError('node.remoteCommand: command must be string.');
        }
        if (!parent.constants.gamecommands[command]) {
            throw new Error('node.remoteCommand: unknown command: ' +
                            command + '.');
        }
        if ('string' !== typeof to) {
            throw new TypeError('node.remoteCommand: to must be string.');
        }

        msg = this.msg.create({
            target: this.constants.target.GAMECOMMAND,
            text: command,
            data: options,
            to: to
        });
        this.socket.send(msg);
    };

    /**
     * ### NodeGameClient.remoteAlert
     *
     * Displays an alert message in the screen of the client
     *
     * Message is effective only if the client has a _window_ object
     * with a global _alert_ method.
     *
     * @param {string} text The text of of the messagex
     * @param {string} to The id of the player to alert
     */
    NGC.prototype.remoteAlert = function(text, to) {
        var msg;
        if ('string' !== typeof text) {
            throw new TypeError('node.remoteAlert: text must be string.');
        }
        if ('undefined' === typeof to) {
            throw new TypeError('node.remoteAlert: to must be string.');
        }
        msg = this.msg.create({
            target: this.constants.target.ALERT,
            text: text,
            to: to
        });
        this.socket.send(msg);
    };

})(
    'undefined' != typeof node ? node : module.exports,
    'undefined' != typeof node ? node : module.parent.exports
);
<|endoftext|>


<commit_before>//
// Copyright (c) 2016 Satoshi Nakajima (https://github.com/snakajima)
// License: The MIT License
//
import Page from './Page';

function generate(store) {

    const state = store.getState();
    var prev = state.elements;
    //const idsAll = state.elements.map((element) => element.id);
    return {
      type:"net.swipe.swipe",
      dimension:[state.dimension.width, state.dimension.height],
      paging:"leftToRight",
      orientation:"landscape",
      templates:{
        pages:{
          s0:{
            play: "scroll",
            elements: state.elements
          }
        }
      },
      pages: state.pages.map((page) => {
        var obj = {template:"s0"}
        const elements = Page.applyTransform(state.elements, page);
        console.log("elements=", JSON.stringify(elements));
        obj.elements = elements.reduce((s, element, index) => {
               var d0 = Page.extractDelta(state.elements[index], prev[index], false);
               const d1 = Page.extractDelta(state.elements[index], element, false);
               if (d0) {
                  d0.id = element.id;
                  if (d1) {
                    s.push(Object.assign(d0, {to:d1}));
                  } else {
                    var delta = {};
                    const sceneElement = state.elements[index];
                    if (d0.translate) {
                      delta.translate=[0,0]
                    }
                    if (d0.rotate) {
                      delta.rotate = sceneElement.rotate || 0;
                    }
                    s.push(Object.assign(d0, {to:delta}));
                  }
               } else if (d1) {
                 s.push({id:element.id, to:d1});
               }
              return s;
              }, []);
        prev = elements;
        return obj;
      })
    };
}

module.exports = { generate };
<commit_msg>fix issue #1<commit_after>//
// Copyright (c) 2016 Satoshi Nakajima (https://github.com/snakajima)
// License: The MIT License
//
import Page from './Page';

function generate(store) {

    const state = store.getState();
    var prev = state.elements;
    //const idsAll = state.elements.map((element) => element.id);
    return {
      type:"net.swipe.swipe",
      dimension:[state.dimension.width, state.dimension.height],
      paging:"leftToRight",
      orientation:"landscape",
      templates:{
        pages:{
          s0:{
            play: "scroll",
            elements: state.elements
          }
        }
      },
      pages: state.pages.map((page) => {
        var obj = {template:"s0"}
        const elements = Page.applyTransform(state.elements, page);
        console.log("elements=", JSON.stringify(elements));
        obj.elements = elements.reduce((s, element, index) => {
               const sceneElement = state.elements[index];
               var d0 = Page.extractDelta(sceneElement, prev[index], false);
               var d1 = Page.extractDelta(sceneElement, element, false);
               if (d0) {
                  d0.id = element.id;
                  if (d1) {
                    if (d0.translate && !d1.translate) {
                      d1.translate = [0,0];
                    }
                    if (d0.rotate && !d1.rotate) {
                      d1.rotate = sceneElement.rotate;
                    }
                    if (d0.opacity && !d1.opacity) {
                      d1.opacity = sceneElement.opacity;
                    }
                    s.push(Object.assign(d0, {to:d1}));
                  } else {
                    var delta = {};
                    if (d0.translate) {
                      delta.translate=[0,0]
                    }
                    if (d0.rotate) {
                      delta.rotate = sceneElement.rotate || 0;
                    }
                    s.push(Object.assign(d0, {to:delta}));
                  }
               } else if (d1) {
                 s.push({id:element.id, to:d1});
               }
              return s;
              }, []);
        prev = elements;
        return obj;
      })
    };
}

module.exports = { generate };
<|endoftext|>


<commit_before>var vm = require('vm');
var async = require('async');
var _ = require('lodash');
var winston = require('winston');

/* CONSTANTS */
var LOGLEVEL = 'error';

var log = new ( winston.Logger )({ transports: [ new (winston.transports.Console)({ 'level': LOGLEVEL, 'timestamp': 1, 'colorize': 1 }) ] });

exports.find = function (records, criteria, callback)
{
    var boundFunc = _.bind(_tryMatch, { criteria : criteria } );

    async.filter( records, boundFunc, function( results ) {
        log.verbose( "End result", JSON.stringify(results, null, 4) );
        return callback(results);
    });

}

exports.match = function (record, criteria, callback )
{
    var boundFunc = _.bind(_tryMatch, { criteria : criteria } );

    boundFunc( record,  function( result ) {
        log.verbose( "End result", JSON.stringify(result, null, 4) );
        return callback(result);
    });
}

/* internal functions */

function _tryMatch( record, recordCallback )
{

    _doEvery( record, this.criteria, recordCallback);

}

function _doEvery( record, criteria, cb )
{
    log.verbose( "doEvery", JSON.stringify({ criteria : criteria, record : record }, null, 4) );

    var boundFunc = _.bind(_checkTrue, { record : record, criteria : criteria } );

    async.every( _.keys(criteria), boundFunc, function( result ) {
        return cb(result);
    });
}

function _callNor( record, criteria, cb )
{
    log.verbose( "callNor", JSON.stringify({ criteria : criteria, record : record }, null, 4) );

    // reverse result of OR
    _callOr( record, criteria, function( result ) {
	return cb( ! result );
    } );
}

function _callOr( record, criteria, cb )
{
    log.verbose( "callOr", JSON.stringify({ criteria : criteria, record : record }, null, 4) );

    var boundFunc = _.bind(_checkTrue, { record : record, criteria : criteria } );

    async.detect( _.keys(criteria), boundFunc, function( innerResult ) {
        log.verbose( "or", JSON.stringify(innerResult, null, 4) );
        return cb(innerResult);
    });
}

function _checkTrue( key, keyCallback )
{
    log.verbose("checkTrue", JSON.stringify(this.criteria, null, 4) );

    var currCriteria =  this.criteria[key];

    log.verbose( "checkTrue params", JSON.stringify({ key : key, currentCriteria : currCriteria, currentCriteriaType : typeof currCriteria }, null, 4) );

    if (
        ( key === '$and' )
            &&
            ( currCriteria instanceof Array )
        )
    {
        return _doEvery( this.record, currCriteria, keyCallback );
    }
    else if (
        ( key === '$or' )
            &&
            ( currCriteria instanceof Array )
        )
    {
        return _callOr( this.record, currCriteria, keyCallback );
    }
    else if (
        ( key === '$nor' )
            &&
            ( currCriteria instanceof Array )
        )
    {
        return _callNor( this.record, currCriteria, keyCallback );
    }
    else if ( key === '$where' )
    {
        var contextCode = '';
        var recordKeys = _.keys( this.record );

        for ( var k = 0; k < recordKeys.length; k++ )
        {
            contextCode += 'var ' + recordKeys[k] + "=" + JSON.stringify(this.record[recordKeys[k]]) + ';';
        }

        var retValue = false;
        try {
            retValue = vm.runInThisContext( contextCode + this.criteria[key] )
        }
        catch (e) {
            log.warn( "Error in $where clause: " + e);
        };

        keyCallback( retValue );

    }
    else if ( typeof currCriteria === 'object' )
    {
        if (
            ( _.keys( currCriteria ).length == 1 )
             &&
            ( _.keys( currCriteria )[0].match(/^\$(exists|lt|lte|gt|gte|ne|in|nin)$/))
         )
        {
            var retValue = false;

            switch (_.keys(currCriteria)[0])
            {
                case '$exists' :

                    var isDef = ( typeof _objAccessor(this.record, key) != 'undefined' );

                    retValue = currCriteria['$exists'] ? isDef : (!isDef);
                    break;

                case '$lt' :

                    retValue = ( _objAccessor(this.record, key) < currCriteria['$lt'] );
                    break;

                case '$lte' :

                    retValue = ( _objAccessor(this.record, key) <= currCriteria['$lte'] );
                    break;

                case '$gt' :

                    retValue = ( _objAccessor(this.record, key) > currCriteria['$gt'] );
                    break;

                case '$gte' :

                    retValue = ( _objAccessor(this.record, key) >= currCriteria['$gte'] );
                    break;

                case '$ne' :

                    retValue = ( _objAccessor(this.record, key) != currCriteria['$ne'] );
                    break;

                case '$in' :

                    var toMatch = _objAccessor(this.record, key);
                    for ( var i = 0; i < currCriteria['$in'].length; i++ )
                    {
                        if ( currCriteria['$in'][i] == toMatch )
                        {
                            retValue = true;
                            break;
                        }
                    }
                    break;

                case '$nin' :

                    var toMatch = _objAccessor(this.record, key);
                    for ( var i = 0; i < currCriteria['$in'].length; i++ )
                    {
                        if ( currCriteria['$in'][i] == toMatch )
                        {
                            retValue = true;
                            break;
                        }
                    }

                    // reverse value since "not in"
                    retValue = ! retValue;

                    break;

            }

            log.verbose( "check result " +  retValue );

             keyCallback( retValue );
        }
        else
        {
            return _doEvery( this.record, currCriteria, keyCallback );
        }
    }
    else
    {
        var retValue = ( _objAccessor( this.record, key ) == currCriteria );
        log.verbose( "check result " +  retValue );

         keyCallback( retValue );
    }

}

function _objAccessor( obj, objPath )
{
    log.verbose( "_objAccessor", JSON.stringify({ obj : JSON.stringify(obj, null, 4), path : objPath }, null, 4) )
    objPath = objPath.split(".");

    for(var i = 0; i < objPath.length; i++)
    {
        if ( typeof obj[ objPath[i] ] != 'undefined' )
        {
            obj = obj[ objPath[i] ];
        }
        else
        {
            return undefined;
        }
    }
    return obj;
}
<commit_msg>Remove winston<commit_after>var vm = require('vm');
var async = require('async');
var _ = require('lodash');

exports.find = function (records, criteria, callback)
{
    var boundFunc = _.bind(_tryMatch, { criteria : criteria } );

    async.filter( records, boundFunc, function( results ) {
        return callback(results);
    });

}

exports.match = function (record, criteria, callback )
{
    var boundFunc = _.bind(_tryMatch, { criteria : criteria } );

    boundFunc( record,  function( result ) {
        return callback(result);
    });
}

/* internal functions */

function _tryMatch( record, recordCallback )
{

    _doEvery( record, this.criteria, recordCallback);

}

function _doEvery( record, criteria, cb )
{
    var boundFunc = _.bind(_checkTrue, { record : record, criteria : criteria } );

    async.every( _.keys(criteria), boundFunc, function( result ) {
        return cb(result);
    });
}

function _callNor( record, criteria, cb )
{
    // reverse result of OR
    _callOr( record, criteria, function( result ) {
	return cb( ! result );
    } );
}

function _callOr( record, criteria, cb )
{

    var boundFunc = _.bind(_checkTrue, { record : record, criteria : criteria } );

    async.detect( _.keys(criteria), boundFunc, function( innerResult ) {
        return cb(innerResult);
    });
}

function _checkTrue( key, keyCallback )
{

    var currCriteria =  this.criteria[key];

    if (
        ( key === '$and' )
            &&
            ( currCriteria instanceof Array )
        )
    {
        return _doEvery( this.record, currCriteria, keyCallback );
    }
    else if (
        ( key === '$or' )
            &&
            ( currCriteria instanceof Array )
        )
    {
        return _callOr( this.record, currCriteria, keyCallback );
    }
    else if (
        ( key === '$nor' )
            &&
            ( currCriteria instanceof Array )
        )
    {
        return _callNor( this.record, currCriteria, keyCallback );
    }
    else if ( key === '$where' )
    {
        var contextCode = '';
        var recordKeys = _.keys( this.record );

        for ( var k = 0; k < recordKeys.length; k++ )
        {
            contextCode += 'var ' + recordKeys[k] + "=" + JSON.stringify(this.record[recordKeys[k]]) + ';';
        }

        var retValue = false;
        try {
            retValue = vm.runInThisContext( contextCode + this.criteria[key] )
        }
        catch (e) {
            console.log( "Error in $where clause: " + e);
        };

        keyCallback( retValue );

    }
    else if ( typeof currCriteria === 'object' )
    {
        if (
            ( _.keys( currCriteria ).length == 1 )
             &&
            ( _.keys( currCriteria )[0].match(/^\$(exists|lt|lte|gt|gte|ne|in|nin)$/))
         )
        {
            var retValue = false;

            switch (_.keys(currCriteria)[0])
            {
                case '$exists' :

                    var isDef = ( typeof _objAccessor(this.record, key) != 'undefined' );

                    retValue = currCriteria['$exists'] ? isDef : (!isDef);
                    break;

                case '$lt' :

                    retValue = ( _objAccessor(this.record, key) < currCriteria['$lt'] );
                    break;

                case '$lte' :

                    retValue = ( _objAccessor(this.record, key) <= currCriteria['$lte'] );
                    break;

                case '$gt' :

                    retValue = ( _objAccessor(this.record, key) > currCriteria['$gt'] );
                    break;

                case '$gte' :

                    retValue = ( _objAccessor(this.record, key) >= currCriteria['$gte'] );
                    break;

                case '$ne' :

                    retValue = ( _objAccessor(this.record, key) != currCriteria['$ne'] );
                    break;

                case '$in' :

                    var toMatch = _objAccessor(this.record, key);
                    for ( var i = 0; i < currCriteria['$in'].length; i++ )
                    {
                        if ( currCriteria['$in'][i] == toMatch )
                        {
                            retValue = true;
                            break;
                        }
                    }
                    break;

                case '$nin' :

                    var toMatch = _objAccessor(this.record, key);
                    for ( var i = 0; i < currCriteria['$in'].length; i++ )
                    {
                        if ( currCriteria['$in'][i] == toMatch )
                        {
                            retValue = true;
                            break;
                        }
                    }

                    // reverse value since "not in"
                    retValue = ! retValue;

                    break;

            }

             keyCallback( retValue );
        }
        else
        {
            return _doEvery( this.record, currCriteria, keyCallback );
        }
    }
    else
    {
        var retValue = ( _objAccessor( this.record, key ) == currCriteria );

         keyCallback( retValue );
    }

}

function _objAccessor( obj, objPath )
{
    objPath = objPath.split(".");

    for(var i = 0; i < objPath.length; i++)
    {
        if ( typeof obj[ objPath[i] ] != 'undefined' )
        {
            obj = obj[ objPath[i] ];
        }
        else
        {
            return undefined;
        }
    }
    return obj;
}
<|endoftext|>


<commit_before>export function getValType(data, variable){
	let var_vals = d3.set(data.map(m => m[variable])).values();
	let vals_numbers = var_vals.filter(f => +f || f === 0 );

	if(var_vals.length === vals_numbers.length && var_vals.length > 4){
		return 'continuous';
	}
	else{
		return 'categorical';
	}
}
<commit_msg>Consider "0" numeric<commit_after>export function getValType(data, variable){
	let var_vals = d3.set(data.map(m => m[variable])).values();
	let vals_numbers = var_vals.filter(f => +f || f == 0 );

	if(var_vals.length === vals_numbers.length && var_vals.length > 4){
		return 'continuous';
	}
	else{
		return 'categorical';
	}
}
<|endoftext|>


<commit_before>'use strict';

var isObject = angular.isObject,
  isFunction = angular.isFunction,
  isArray = angular.isArray,
  isString = angular.isString,
  forEach = angular.forEach,
  ngInjector = angular.injector(['ng']),
  $q = ngInjector.get('$q'),
  bodyElement,
  loadingClass = 'deferred-bootstrap-loading',
  errorClass = 'deferred-bootstrap-error';

function addLoadingClass() {
  bodyElement.addClass(loadingClass);
}

function removeLoadingClass() {
  bodyElement.removeClass(loadingClass);
}

function addErrorClass() {
  removeLoadingClass();
  bodyElement.addClass(errorClass);
}

function isPromise(value) {
  return isObject(value) && isFunction(value.then);
}

function checkConfig(config) {
  if (!isObject(config)) {
    throw new Error('Bootstrap configuration must be an object.');
  }
  if (!isString(config.module)) {
    throw new Error('\'config.module\' must be a string.');
  }
  if (!isObject(config.resolve)) {
    throw new Error('\'config.resolve\' must be an object.');
  }
  if (angular.isDefined(config.onError) && !isFunction(config.onError)) {
    throw new Error('\'config.onError\' must be a function.');
  }
}

function createInjector(injectorModules) {
  if (!isArray(injectorModules)) {
    return (injectorModules === 'ng') ? ngInjector : angular.injector([injectorModules]);
  } else {
    return (injectorModules.length === 1 && injectorModules[0] === 'ng') ? ngInjector : angular.injector(injectorModules);
  }
}

function doBootstrap(element, module) {
  var deferred = $q.defer();

  angular.element(document).ready(function () {
    angular.bootstrap(element, [module]);
    removeLoadingClass();

    deferred.resolve(true);
  });

  return deferred.promise;
}

function bootstrap(configParam) {
  var config = configParam || {},
    element = config.element,
    module = config.module,
    injectorModules = config.injectorModules || ['ng'],
    injector,
    promises = [],
    constantNames = [];

  bodyElement = angular.element(document.body);

  addLoadingClass();
  checkConfig(config);

  function callResolveFn(resolveFunction, constantName) {
    var result;

    constantNames.push(constantName);

    if (!isFunction(resolveFunction) && !isArray(resolveFunction)) {
      throw new Error('Resolve for \'' + constantName + '\' is not a valid dependency injection format.');
    }

    injector = createInjector(injectorModules);
    result = injector.instantiate(resolveFunction);

    if (isPromise(result)) {
      promises.push(result);
    } else {
      throw new Error('Resolve function for \'' + constantName + '\' must return a promise.');
    }
  }

  function handleResults(results) {
    forEach(results, function (value, index) {
      var result = value && value.data ? value.data : value;
      angular.module(module).constant(constantNames[index], result);
    });

    return doBootstrap(element, module);
  }

  function handleError(error) {
    addErrorClass();
    if (isFunction(config.onError)) {
      config.onError(error);
    }
  }

  forEach(config.resolve, callResolveFn);

  return $q.all(promises).then(handleResults, handleError);

}

window.deferredBootstrapper = {
  bootstrap: bootstrap
};<commit_msg>Minor code formatting changes and inverted an if statement.<commit_after>'use strict';

var isObject = angular.isObject,
  isFunction = angular.isFunction,
  isArray = angular.isArray,
  isString = angular.isString,
  forEach = angular.forEach,
  ngInjector = angular.injector(['ng']),
  $q = ngInjector.get('$q'),
  bodyElement,
  loadingClass = 'deferred-bootstrap-loading',
  errorClass = 'deferred-bootstrap-error';

function addLoadingClass () {
  bodyElement.addClass(loadingClass);
}

function removeLoadingClass () {
  bodyElement.removeClass(loadingClass);
}

function addErrorClass () {
  removeLoadingClass();
  bodyElement.addClass(errorClass);
}

function isPromise (value) {
  return isObject(value) && isFunction(value.then);
}

function checkConfig (config) {
  if (!isObject(config)) {
    throw new Error('Bootstrap configuration must be an object.');
  }
  if (!isString(config.module)) {
    throw new Error('\'config.module\' must be a string.');
  }
  if (!isObject(config.resolve)) {
    throw new Error('\'config.resolve\' must be an object.');
  }
  if (angular.isDefined(config.onError) && !isFunction(config.onError)) {
    throw new Error('\'config.onError\' must be a function.');
  }
}

function createInjector (injectorModules) {
  if (isArray(injectorModules)) {
    return (injectorModules.length === 1 && injectorModules[0] === 'ng') ? ngInjector : angular.injector(injectorModules);
  } else {
    return (injectorModules === 'ng') ? ngInjector : angular.injector([injectorModules]);
  }
}

function doBootstrap (element, module) {
  var deferred = $q.defer();

  angular.element(document).ready(function () {
    angular.bootstrap(element, [module]);
    removeLoadingClass();

    deferred.resolve(true);
  });

  return deferred.promise;
}

function bootstrap (configParam) {
  var config = configParam || {},
    element = config.element,
    module = config.module,
    injectorModules = config.injectorModules || ['ng'],
    injector,
    promises = [],
    constantNames = [];

  bodyElement = angular.element(document.body);

  addLoadingClass();
  checkConfig(config);

  function callResolveFn (resolveFunction, constantName) {
    var result;

    constantNames.push(constantName);

    if (!isFunction(resolveFunction) && !isArray(resolveFunction)) {
      throw new Error('Resolve for \'' + constantName + '\' is not a valid dependency injection format.');
    }

    injector = createInjector(injectorModules);
    result = injector.instantiate(resolveFunction);

    if (isPromise(result)) {
      promises.push(result);
    } else {
      throw new Error('Resolve function for \'' + constantName + '\' must return a promise.');
    }
  }

  function handleResults (results) {
    forEach(results, function (value, index) {
      var result = value && value.data ? value.data : value;
      angular.module(module).constant(constantNames[index], result);
    });

    return doBootstrap(element, module);
  }

  function handleError (error) {
    addErrorClass();
    if (isFunction(config.onError)) {
      config.onError(error);
    }
  }

  forEach(config.resolve, callResolveFn);

  return $q.all(promises).then(handleResults, handleError);
}

window.deferredBootstrapper = {
  bootstrap: bootstrap
};<|endoftext|>


<commit_before>define(["jquery"], function($){
  function load(){
    console.log("nbbrowserpdf loaded!");
  }

  return {
    load_ipython_extension: load,
    load_jupyter_extension: load,
  };
});
<commit_msg>adding entry to file->download as menu<commit_after>define([
  "jquery",
  "base/js/namespace"
], function($, Jupyter){
  function load(){
    $("<li/>", {id: "downlaod_browserpdf"}).append(
        $("<a/>")
          .text("PDF via Headless Browser (.pdf)")
          .on("click", function(){
            Jupyter.menubar._nbconvert("browserpdf", true);
          })
      )
      .appendTo($("#download_html").parent());
  }

  return {
    load_ipython_extension: load,
    load_jupyter_extension: load,
  };
});
<|endoftext|>


<commit_before>
// See: http://www.crockford.com/javascript/jslint.html
/*global alert, window, document, navigator, DOMParser, XMLHttpRequest */

/**
 * Declare a constructor function to which we can add real functions.
 * @constructor
 */
function DWREngine()
{
}

/**
 * Constants to pick the XMLHttpRequest remoting method.
 * For example:
 * DWREngine.setMethod(DWREngine.XMLHttpRequest);
 * @see DWREngine.setMethod()
 * @see DWREngine.IFrame
 */
DWREngine.XMLHttpRequest = 1;

/**
 * Constants to pick the iframe remoting method.
 * For example:
 * DWREngine.setMethod(DWREngine.IFrame);
 * @see DWREngine.setMethod()
 * @see DWREngine.XMLHttpRequest
 */
DWREngine.IFrame = 2;

/**
 * The default error handler displays an alert box, but that is not correct
 * for all applications, so this method allows you to set an alternative
 * error handler.
 * By default there is no error handler set.
 * @param handler A function to call with single an error parameter on failure
 * @see DWREngine.defaultMessageHandler()
 */
DWREngine.setErrorHandler = function(handler)
{
    DWREngine._errorHandler = handler;
};

/**
 * The default warning handler displays an alert box, but that is not correct
 * for all applications, so this method allows you to set an alternative
 * warning handler.
 * By default there is no error handler set.
 * @param handler A function to call with single an warning parameter on failure
 * @see DWREngine.defaultMessageHandler()
 */
DWREngine.setWarningHandler = function(handler)
{
    DWREngine._warningHandler = handler;
};

/**
 * The Pre-Hook is called before any DWR remoting is done.
 * Pre hooks can be useful for displaying "please wait" messages.
 * @param handler A function to call with no params before remoting
 * @see DWREngine.setPostHook()
 */
DWREngine.setPreHook = function(handler)
{
    DWREngine._preHook = handler;
};

/**
 * The Post-Hook is called after any DWR remoting is done.
 * Pre hooks can be useful for removing "please wait" messages.
 * @param handler A function to call with no params after remoting
 * @see DWREngine.setPreHook()
 */
DWREngine.setPostHook = function(handler)
{
    DWREngine._postHook = handler;
};

/**
 * Set the preferred remoting method.
 * setMethod does not guarantee that the selected method will be used, just that
 * we will try that method first.
 * @param newmethod One of DWREngine.XMLHttpRequest or DWREngine.IFrame
 */
DWREngine.setMethod = function(newmethod)
{
    if (newmethod != DWREngine.XMLHttpRequest && newmethod != DWREngine.IFrame)
    {
        if (DWREngine._errorHandler)
        {
            DWREngine._errorHandler("Remoting method must be one of DWREngine.XMLHttpRequest or DWREngine.IFrame");
        }

        return;
    }

    DWREngine._method = newmethod;
};

/**
 * Which HTTP verb do we use so send results?
 * Must be one of "GET" or "POST".
 * @param verb the new HTTP verb.
 */
DWREngine.setVerb = function(verb)
{
    if (verb != "GET" && verb != "POST")
    {
        if (DWREngine._errorHandler)
        {
            DWREngine._errorHandler("Remoting verb must be one of GET or POST");
        }

        return;
    }

    DWREngine._verb = verb;
};

/**
 * Do we attempt to ensure that remote calls happen in the order in which they
 * were sent? (Default: false)
 * Warning: Setting this to true will slow down your application, and could
 * leave users with an unresponsive browser if a message gets lost.
 * Sometimes there are better solutions where you make your application use the
 * asynchronous model properly. Please think before you use this method.
 * @param ordered true or false
 */
DWREngine.setOrdered = function(ordered)
{
    DWREngine._ordered = ordered;
};

/**
 * The default message handler.
 * Useful in calls to setErrorHandler() or setWarningHandler() to allow you to
 * get the default back.
 * @param message The message to display to the user somehow
 */
DWREngine.defaultMessageHandler = function(message)
{
    if (typeof message == "object" && message.name == "Error" && message.description)
    {
        alert("Error: " + message.description);
    }
    else
    {
        alert(message);
    }
};

/**
 * You can group several remote calls together using a batch.
 * This saves on round trips to the server so there is much less latency involved.
 * @see DWREngine.endBatch()
 */
DWREngine.beginBatch = function()
{
    if (DWREngine._batch)
    {
        if (DWREngine._errorHandler)
        {
            DWREngine._errorHandler("Batch already started.");
        }

        return;
    }

    // Setup a batch
    DWREngine._batch = {};
    DWREngine._batch.map = {};
    DWREngine._batch.paramCount = 0;
    DWREngine._batch.map.callCount = 0;
    DWREngine._batch.metadata = {};
};

/**
 * We are finished grouping a set of remote calls together, now go and execute
 * them all.
 */
DWREngine.endBatch = function()
{
    if (DWREngine._batch == null)
    {
        if (DWREngine._errorHandler)
        {
            DWREngine._errorHandler("No batch in progress.");
        }

        return;
    }

    // If we are in ordered mode, then we don't send unless the list of sent
    // items is empty
    if (!DWREngine._ordered)
    {
        DWREngine._sendData(DWREngine._batch);
        DWREngine._batches[DWREngine._batches.length] = DWREngine._batch;
    }
    else
    {
        if (DWREngine._batches.length == 0)
        {
            // We aren't waiting for anything, go now.
            DWREngine._sendData(DWREngine._batch);
            DWREngine._batches[DWREngine._batches.length] = DWREngine._batch;
        }
        else
        {
            // Push the batch onto the waiting queue
            DWREngine._batchQueue[DWREngine._batchQueue.length] = DWREngine._batch;
        }
    }

    DWREngine._batch = null;
};

//==============================================================================
// Only private stuff below here
//==============================================================================

/**
 * A function to call if something fails.
 * @private
 */
DWREngine._errorHandler = DWREngine.defaultMessageHandler;

/**
 * A function to call to alert the user to some breakage.
 * @private
 */
DWREngine._warningHandler = DWREngine.defaultMessageHandler;

/**
 * A function to be called before requests are marshalled. Can be null.
 * @private
 */
DWREngine._preHook = null;

/**
 * A function to be called after replies are received. Can be null.
 * @private
 */
DWREngine._postHook = null;

/**
 * An array of the batches that we have sent and are awaiting a reply on.
 * @private
 */
DWREngine._batches = [];

/**
 * An array of batches that we'd like to send, but because we are in ordered
 * mode we won't until the current batch has been returned.
 * @private
 */
DWREngine._batchQueue = [];

/**
 * A map of all the known current batches
 * @private
 */
DWREngine._callbacks = {};

/**
 * What is the default remoting method
 * @private
 */
DWREngine._method = DWREngine.XMLHttpRequest;

/**
 * What is the default remoting verb (ie GET or POST)
 * @private
 */
DWREngine._verb = "POST";

/**
 * Do we attempt to ensure that remote calls happen in the order in which they
 * were sent?
 * @private
 */
DWREngine._ordered = false;

/**
 * The current batch (if we are in batch mode)
 * @private
 */
DWREngine._batch = null;

/**
 * Called when the replies are received.
 * This method is called by Javascript that is emitted by server
 * @param id The identifier of the call that we are handling a response for
 * @param reply The data to pass to the callback function
 * @private
 */
DWREngine._handleResponse = function(id, reply, httpRequestAllowed, authenticated, sessionValidated)
{
    var func = DWREngine._callbacks[id];

    // Clear this callback out of the list - we don't need it any more
    DWREngine._callbacks[id] = null;

    if (func)
    {
        // Error handlers inside here indicate an error that is nothing to do
        // with DWR so we handle them differently.
        try
        {
			if (!httpRequestAllowed)
			{
				DWREngine._httpRequestMethodDenied("Request Denied");
			}
			else if (!authenticated)
			{
				DWREngine._authenticationFailureHandler("Client Authentication failed");
			}
			else if (!sessionValidated)
			{
				DWREngine._sessionAuthenticationFailureHandler("Session Authentication failed");
			}
			else
			{
	            func(reply);
			}
        }
        catch (ex)
        {
            if (DWREngine._errorHandler)
            {
                DWREngine._errorHandler(ex);
            }
        }
    }
    else
    {
        // If there is no reply then we don;t worry about warning for a missing
        // callback function.
        if (reply)
        {
            if (DWREngine._warningHandler)
            {
                DWREngine._warningHandler("Internal Error: Missing callback for id='" + id + "'");
            }
        }
    }
};

/**
 * Called when errors are received.
 * This method is called by Javascript that is emitted by server
 * @private
 */
DWREngine._handleError = function(id, reason)
{
    if (DWREngine._errorHandler)
    {
        DWREngine._errorHandler(reason);
    }
};

/**
 * Call right at the end of a batch being executed to clear up
 * @param batch The batch to tidy up after
 * @private
 */
DWREngine._finalize = function(batch)
{
    DWREngine._removeNode(batch.div);
    DWREngine._removeNode(batch.iframe);
    DWREngine._removeNode(batch.form);

    if (DWREngine._postHook)
    {
        DWREngine._postHook();
    }

    // TODO: There must be a better way???
    for (var i = 0; i < DWREngine._batches.length; i++)
    {
        if (DWREngine._batches[i] == batch)
        {
            DWREngine._batches.splice(i, 1);
            break;
        }
    }

    // If there is anything on the queue waiting to go out, then send it.
    // We don't need to check for ordered mode, here because when ordered mode
    // gets turned off, we still process *waiting* batches in an ordered way.
    if (DWREngine._batchQueue.length != 0)
    {
        var batch = DWREngine._batchQueue.shift();
        DWREngine._sendData(batch);
        DWREngine._batches[DWREngine._batches.length] = batch;
    }
};

/**
 * Remove a node from a document.
 * @param node the node to remove from the document that it's part of.
 * @private
 */
DWREngine._removeNode = function(node)
{
    if (node)
    {
        node.parentNode.removeChild(node);
    }
};

/**
 * Send a request to the server
 * This method is called by Javascript that is emitted by server
 * @param path The part of the URL after the host and before the exec bit
 *             without leading or trailing /s
 * @param scriptName The class to execute
 * @param methodName The method on said class to execute
 * @param func The callback function to which any returned data should be passed
 *             if this is null, any returned data will be ignored
 * @param vararg_params The parameters to pass to the above class
 * @private
 */
DWREngine._execute = function(path, scriptName, methodName, vararg_params)
{
    var singleShot = false;
    if (DWREngine._batch == null)
    {
        DWREngine.beginBatch();
        singleShot = true;
    }

    // To make them easy to manipulate we copy the arguments into an args array
    var args = [];
    for (var i = 0; i < arguments.length - 3; i++)
    {
        args[i] = arguments[i + 3];
    }

    // All the paths MUST be to the same servlet
    if (DWREngine._batch.path == null)
    {
        DWREngine._batch.path = path;
    }
    else
    {
        if (DWREngine._batch.path != path)
        {
            if (DWREngine._errorHandler)
            {
                DWREngine._errorHandler("Can't batch requests to multiple DWR Servlets.");
            }

            return;
        }
    }

    // From the other params, work out which is the function (or object with
    // call meta-data) and which is the call parameters
    var func;
    var params;
    var metadata;

    var firstArg = args[0];
    var lastArg = args[args.length - 1];

    if (typeof firstArg == "function")
    {
        func = args.shift();
        params = args;
        metadata = {};
    }
    else if (typeof lastArg == "function")
    {
        func = args.pop();
        params = args;
        metadata = {};
    }
    else if (typeof lastArg == "object" && lastArg.callback != null && typeof lastArg.callback == "function")
    {
        metadata = args.pop();
        params = args;
        func = metadata.callback;
    }
    else if (firstArg == null)
    {
        // This could be a null callback function, but if the last arg is also
        // null then we can't tell which is the function unless there are only
        // 2 args, in which case we don't care!
        if (lastArg == null && args.length > 2)
        {
            if (DWREngine._warningHandler)
            {
                DWREngine._warningHandler("Ambiguous nulls at start and end of parameter list. Which is the callback function?");
            }
        }

        func = args.shift();
        params = args;
        metadata = {};
    }
    else if (lastArg == null)
    {
        func = args.pop();
        params = args;
        metadata = {};
    }
    else
    {
        if (DWREngine._warningHandler)
        {
            DWREngine._warningHandler("Missing callback function or metadata object.");
        }

        return;
    }

    // Get a unique ID for this call
    var random = Math.floor(Math.random() * 10001);
    var id = (random + "_" + new Date().getTime()).toString();
    // var id = DWREngine._batches.length;
    // var id = idbase++;

    DWREngine._callbacks[id] = func;

    var prefix = "c" + DWREngine._batch.map.callCount + "-";

    // merge the metadata from this call into the batch
    if (metadata != null)
    {
        for (var prop in metadata)
        {
            DWREngine._batch.metadata[prop] = metadata[prop];
        }
    }

    DWREngine._batch.map[prefix + "scriptName"] = scriptName;
    DWREngine._batch.map[prefix + "methodName"] = methodName;
    DWREngine._batch.map[prefix + "id"] = id;

    // Serialize the parameters into batch.map
    DWREngine._addSerializeFunctions();
    for (i = 0; i < params.length; i++)
    {
        DWREngine._serializeAll(DWREngine._batch, [], params[i], prefix + "param" + i);
    }
    DWREngine._removeSerializeFunctions();

    // Now we have finished remembering the call, we incr the call count
    DWREngine._batch.map.callCount++;

    if (singleShot)
    {
        DWREngine.endBatch();
    }
};

/**
 * Called as a result of a request timeout or an http reply status != 200
 * @param batch Block of data about the calls we are making on the server
 * @private
 */
DWREngine._abortRequest = function(batch)
{
    if (batch && batch.metadata && batch.completed != true)
    {
        batch.completed = true;
        if (batch.req != null)
        {
            batch.req.abort();

            if (batch.metadata.errorHandler)
            {
                if (typeof batch.metadata.errorHandler == "string")
                {
                    eval(batch.metadata.errorHandler);
                }
                else if (typeof batch.metadata.errorHandler == "function")
                {
                    batch.metadata.errorHandler();
                }
                else
                {
                    if (DWREngine._warningHandler)
                    {
                        DWREngine._warningHandler("errorHandler is neither a string (for eval()) or a function.");
                    }
                }
            }
        }
    }
};

/**
 * Actually send the block of data in the batch object.
 * @param batch Block of data about the calls we are making on the server
 * @private
 */
DWREngine._sendData = function(batch)
{
    // Actually make the call
    if (DWREngine._preHook)
    {
        DWREngine._preHook();
    }

    // Set a timeout
    if (batch.metadata && batch.metadata.timeout)
    {
        var funcReq = function() { DWREngine._abortRequest(batch); };
        setTimeout(funcReq, batch.metadata.timeout);
	}

    // Get setup for XMLHttpRequest if possible
    if (DWREngine._method == DWREngine.XMLHttpRequest)
    {
        if (window.XMLHttpRequest)
        {
            batch.req = new XMLHttpRequest();
        }
        // IE5 for the mac claims to support window.ActiveXObject, but throws an error when it's used
        else if (window.ActiveXObject && !(navigator.userAgent.indexOf('Mac') >= 0 && navigator.userAgent.indexOf("MSIE") >= 0))
        {
            batch.req = new window.ActiveXObject("Microsoft.XMLHTTP");
        }
    }

    // A quick string to help people that use web log analysers
    var statsInfo;
    if (batch.map.callCount == 1)
    {
        statsInfo = batch.map["c0-scriptName"] + "." + batch.map["c0-methodName"];
    }
    else
    {
        statsInfo = "Multiple." + batch.map.callCount;
    }

	var query = "ajax=true&";
    var prop;

    if (batch.req)
    {
        batch.map.xml = true;

        // Proceed using XMLHttpRequest
        batch.req.onreadystatechange = function() { DWREngine._stateChange(batch); };

        // Force Mac people to use GET because Safari is broken.
        if (DWREngine._verb == "GET" || navigator.userAgent.indexOf('Safari') >= 0)
        {
			//add authentication Key
			query += encodeURIComponent("clientAuthenticationKey") + "=" + encodeURIComponent(DWREngine.clientAuthenticationKey) + "&";

			for (prop in batch.map)
            {
                query += encodeURIComponent(prop) + "=" + encodeURIComponent(batch.map[prop]) + "&";
            }
            query = query.substring(0, query.length - 1);

            try
            {
                batch.req.open("GET", batch.path + "?" + query);
                batch.req.send(null);
            }
            catch (ex)
            {
                if (DWREngine._errorHandler)
                {
                    DWREngine._errorHandler(ex);
                }
            }
        }
        else
        {
			//add authentication Key
			query += "clientAuthenticationKey" + "=" + encodeURIComponent(DWREngine.clientAuthenticationKey) + "&";
            for (prop in batch.map)
            {
                query += prop + "=" + batch.map[prop] + "&";
            }

            try
            {
				batch.req.open("POST", batch.path + "?" + statsInfo, true);
				batch.req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                batch.req.send(query);
            }
            catch (ex)
            {
                if (DWREngine._errorHandler)
                {
                    DWREngine._errorHandler(ex);
                }
            }
        }
    }
    else
    {
        batch.map.xml = false;

        var idname = "dwr-if-" + batch.map["c0-id"];

        // Proceed using iframe
        batch.div = document.createElement('div');
        batch.div.innerHTML = "";
        document.body.appendChild(batch.div);
        batch.iframe = document.getElementById(idname);
        batch.iframe.setAttribute('style', 'width:0px; height:0px; border:0px;');

        if (DWREngine._verb == "GET")
        {
            for (prop in batch.map)
            {
                query += encodeURIComponent(prop) + "=" + encodeURIComponent(batch.map[prop]) + "&";
            }
            query = query.substring(0, query.length - 1);

            batch.iframe.setAttribute('src', batch.path + "?" + query);
            document.body.appendChild(batch.iframe);
        }
        else
        {
            batch.form = document.createElement('form');
            batch.form.setAttribute('id', 'dwr-form');
            batch.form.setAttribute('action', batch.path);
            batch.form.setAttribute('target', idname);
            batch.form.target = idname;
            batch.form.setAttribute('method', 'post');
            for (prop in batch.map)
            {
                var formInput = document.createElement('input');
                formInput.setAttribute('type', 'hidden');
                formInput.setAttribute('name', prop);
                formInput.setAttribute('value', batch.map[prop]);
                batch.form.appendChild(formInput);
            }

            document.body.appendChild(batch.form);
            batch.form.submit();
        }
    }
};

/**
 * Called by XMLHttpRequest to indicate that something has happened
 * @private
 */
DWREngine._stateChange = function(batch)
{
    if (batch.req.readyState == 4)
    {
        try
        {
            if (batch.req.status && batch.req.status == 200)
            {
                batch.completed = true;

				var response = batch.req.responseText;
				if (response.indexOf("****/") > 0)  response = "/****" + response;
				var footerPos = response.indexOf("/* EOF CFAJAX */");
				if (footerPos > 0) response = response.substring(0, footerPos);
                eval(response);
            }
            else
            {
				///////////////////////////document.frm.error.value = batch.req.responseText;
                if (batch.metadata != null)
                {
                    DWREngine._abortRequest(batch);
                }
                else if (DWREngine._errorHandler)
                {
					var response = batch.req.responseText;
					if (response.indexOf("****/") > 0)  response = "/****" + response;
                    DWREngine._errorHandler(response);
                }
            }
        }
        catch (ex)
        {
            if (batch.metadata != null)
            {
                DWREngine._abortRequest(batch);
            }
            else if (DWREngine._errorHandler)
            {
                DWREngine._errorHandler(ex);
            }
        }

        DWREngine._finalize(batch);
    }
};

/**
 * Hack a polymorphic dwrSerialize() function on all basic types. Yeulch
 * @see DWREngine._addSerializeFunctions
 * @private
 */
DWREngine._addSerializeFunctions = function()
{
    Object.prototype.dwrSerialize = DWREngine._serializeObject;
    Array.prototype.dwrSerialize = DWREngine._serializeArray;
    Boolean.prototype.dwrSerialize = DWREngine._serializeBoolean;
    Number.prototype.dwrSerialize = DWREngine._serializeNumber;
    String.prototype.dwrSerialize = DWREngine._serializeString;
    Date.prototype.dwrSerialize = DWREngine._serializeDate;
};

/**
 * Remove the hacked polymorphic dwrSerialize() function on all basic types.
 * @see DWREngine._removeSerializeFunctions
 * @private
 */
DWREngine._removeSerializeFunctions = function()
{
    delete Object.prototype.dwrSerialize;
    delete Array.prototype.dwrSerialize;
    delete Boolean.prototype.dwrSerialize;
    delete Number.prototype.dwrSerialize;
    delete String.prototype.dwrSerialize;
    delete Date.prototype.dwrSerialize;
};

/**
 * Marshall a data item
 * @param batch A map of variables to how they have been marshalled
 * @param referto An array of already marshalled variables to prevent recurrsion
 * @param data The data to be marshalled
 * @param name The name of the data being marshalled
 * @private
 */
DWREngine._serializeAll = function(batch, referto, data, name)
{
    if (data == null)
    {
        batch.map[name] = "null:null";
        return;
    }

    switch (typeof data)
    {
    case "boolean":
        batch.map[name] = "boolean:" + data;
        break;

    case "number":
        batch.map[name] = "number:" + data;
        break;

    case "string":
        batch.map[name] = "string:" + encodeURIComponent(data);
        break;

    case "object":
        if (data.dwrSerialize)
        {
            batch.map[name] = data.dwrSerialize(batch, referto, data, name);
        }
        else
        {
            if (DWREngine._warningHandler)
            {
                DWREngine._warningHandler("Object without dwrSerialize: " + typeof data + ", attempting default converter.");
            }
            batch.map[name] = "default:" + data;
        }
        break;

    case "function":
        // We just ignore functions.
        break;

    default:
        if (DWREngine._warningHandler)
        {
            DWREngine._warningHandler("Unexpected type: " + typeof data + ", attempting default converter.");
        }
        batch.map[name] = "default:" + data;
        break;
    }
};

/**
 * This is for the types that can recurse so we need to check that we've not
 * marshalled this object before.
 * We'd like to do:
 *   var lookup = referto[data];
 * However hashmaps in Javascript appear to use the hash values of the *string*
 * versions of the objects used as keys so all objects count as the same thing.
 * So we need to have referto as an array and go through it sequentially
 * checking for equality with data
 * @private
 */
DWREngine._lookup = function(referto, data, name)
{
    var lookup;
    for (var i = 0; i < referto.length; i++)
    {
        if (referto[i].data == data)
        {
            lookup = referto[i];
            break;
        }
    }

    if (lookup)
    {
        return "reference:" + lookup.name;
    }

    referto.push({ data:data, name:name });
    return null;
};

/**
 * Marshall an object
 * @private
 * @see DWREngine._serializeAll()
 */
DWREngine._serializeObject = function(batch, referto, data, name)
{
    var ref = DWREngine._lookup(referto, this, name);
    if (ref)
    {
        return ref;
    }

    // treat objects as an associative arrays
    var reply = "Object:{";
    var element;
    for (element in this)
    {
        if (element != "dwrSerialize")
        {
            batch.paramCount++;
            var childName = "c" + DWREngine._batch.map.callCount + "-e" + batch.paramCount;
            DWREngine._serializeAll(batch, referto, this[element], childName);

            reply += encodeURIComponent(element);
            reply += ":reference:";
            reply += childName;
            reply += ", ";
        }
    }
    reply = reply.substring(0, reply.length - 2);
    reply += "}";

    return reply;
};

/**
 * Marshall an array
 * @private
 * @see DWREngine._serializeAll()
 */
DWREngine._serializeArray = function(batch, referto, data, name)
{
    var ref = DWREngine._lookup(referto, this, name);
    if (ref)
    {
        return ref;
    }

    var reply = "Array:[";
    for (var i = 0; i < this.length; i++)
    {
        if (i != 0)
        {
            reply += ",";
        }

        batch.paramCount++;
        var childName = "c" + DWREngine._batch.map.callCount + "-e" + batch.paramCount;
        DWREngine._serializeAll(batch, referto, this[i], childName);
        reply += "reference:";
        reply += childName;
    }
    reply += "]";

    return reply;
};

/**
 * Marshall a Boolean
 * @private
 * @see DWREngine._serializeAll()
 */
DWREngine._serializeBoolean = function(batch, referto, data, name)
{
    return "Boolean:" + this;
};

/**
 * Marshall a Number
 * @private
 * @see DWREngine._serializeAll()
 */
DWREngine._serializeNumber = function(batch, referto, data, name)
{
    return "Number:" + this;
};

/**
 * Marshall a String
 * @private
 * @see DWREngine._serializeAll()
 */
DWREngine._serializeString = function(batch, referto, data, name)
{
    return "String:" + encodeURIComponent(this);
};

/**
 * Marshall a Date
 * @private
 * @see DWREngine._serializeAll()
 */
DWREngine._serializeDate = function(batch, referto, data, name)
{
    return "Date:[ " +
        this.getUTCFullYear() + ", " +
        this.getUTCMonth() + ", " +
        this.getUTCDate() + ", " +
        this.getUTCHours() + ", " +
        this.getUTCMinutes() + ", " +
        this.getUTCSeconds() + ", " +
        this.getUTCMilliseconds() + "]";
};

/**
 * Convert an XML string into a DOC object.
 * @param xml The xml string
 * @return a DOM version of the xml string
 * @private
 */
DWREngine._unserializeDocument = function(xml)
{
    var parser = new DOMParser();
    var dom = parser.parseFromString(xml, "text/xml");

    if (!dom.documentElement || dom.documentElement.tagName == "parsererror")
    {
        var message = dom.documentElement.firstChild.data;
        message += "\n" + dom.documentElement.firstChild.nextSibling.firstChild.data;
        throw message;
    }

    return dom;
};

/**
 * Inform the users that the function they just called is deprecated.
 * @deprecated
 * @private
 */
DWREngine._deprecated = function()
{
    if (DWREngine._warningHandler)
    {
        DWREngine._warningHandler("dwrXxx() functions are deprecated. Please convert to DWREngine.xxx()");
    }
};

/**
 * Stores the encrypted Key created by createClientAuthenticationKey() function
*/
DWREngine.clientAuthenticationKey = "";

DWREngine._authenticationFailureHandler = DWREngine.defaultMessageHandler;
DWREngine._sessionAuthenticationFailureHandler = DWREngine.defaultMessageHandler;
DWREngine._httpRequestMethodDenied = DWREngine.defaultMessageHandler;

DWREngine.setClientAuthenticationKey = function(value)
{
	DWREngine.clientAuthenticationKey = value;
};

DWREngine.setAuthenticationFailureHandler = function(handler)
{
    DWREngine._authenticationFailureHandler = handler;
};

DWREngine.setSessionAuthenticationFailureHandler = function(handler)
{
    DWREngine._sessionAuthenticationFailureHandler = handler;
};

DWREngine.setHttpRequestMethodDeniedHandler = function(handler)
{
    DWREngine._httpRequestMethodDenied = handler;
};
<commit_msg>Delete engine.js<commit_after><|endoftext|>


<commit_before>(function(angular) {
    'use strict';

    angular.module('esri.map').directive('esriMap', function($q, $timeout, esriLoader) {

        // don't apply if already in digest cycle
        // TODO: is there a better way to do this, since it's an anti-pattern:
        // https://github.com/angular/angular.js/wiki/Anti-Patterns
        function safeApply ($scope, fn) {
            var phase = $scope.$root.$$phase;
            if (phase === '$apply' || phase === '$digest') {
                console.log('trying to apply in ' + phase);
                $scope.$eval(fn);
            } else {
                $scope.$apply(fn);
            }
        }

        function getLatLngSignificantDigits(zoom) {
            return Math.max(Math.floor(zoom / 2) - 2, 0);
        }

        return {
            // element only
            restrict: 'E',

            // isoloate scope
            scope: {
                // two-way binding for center/zoom
                // because map pan/zoom can chnage these
                center: '=',
                zoom: '=',
                // one-way binding for other properties
                basemap: '@',
                // function binding for event handlers
                load: '&',
                extentChange: '&'
            },

            // replace tag with div with same id
            compile: function($element, $attrs) {
                // remove the id attribute from the main element
                $element.removeAttr('id');

                // append a new div inside this element, this is where we will create our map
                $element.append('

');

                // since we are using compile we need to return our linker function
                // the 'link' function handles how our directive responds to changes in $scope
                /*jshint unused: false*/
                return function(scope, element, attrs, controller) {
                };
            },

            // directive api
            controller: function($scope, $element, $attrs) {
                // only do this once per directive
                // this deferred will be resolved with the map
                var mapDeferred = $q.defer();

                // setup our map options based on the attributes and scope
                var mapOptions = {};

                // center/zoom/extent
                // check for convenience extent attribute
                // otherwise get from scope center/zoom
                if ($attrs.extent) {
                    mapOptions.extent = $scope[$attrs.extent];
                } else {
                    if ($scope.center.lng && $scope.center.lat) {
                        mapOptions.center = [$scope.center.lng, $scope.center.lat];
                    } else if ($scope.center) {
                        mapOptions.center = $scope.center;
                    }
                    if ($scope.zoom) {
                        mapOptions.zoom = $scope.zoom;
                    }
                }

                // basemap
                if ($scope.basemap) {
                    mapOptions.basemap = $scope.basemap;
                }

                esriLoader('esri/map').then(function(Map){

                    // initialize map and resolve the deferred
                    var map = new Map($attrs.id, mapOptions);

                    mapDeferred.resolve(map);

                    // make a reference to the map object available
                    // to the controller once it is loaded.
                    map.on('load', function() {
                        if (!$attrs.load) {
                            return;
                        }
                        $scope.$apply(function() {
                            $scope.load()(map);
                        });
                    });

                    // listen for changes to scope and update map
                    $scope.$watch('basemap', function(newBasemap, oldBasemap) {
                        if (map.loaded && newBasemap !== oldBasemap) {
                            map.setBasemap(newBasemap);
                        }
                    });

                    $scope.$watch(function(scope){ return [scope.center.lng,scope.center.lat, scope.zoom].join(',');}, function(newCenterZoom,oldCenterZoom)
                    // $scope.$watchGroup(['center.lng','center.lat', 'zoom'], function(newCenterZoom,oldCenterZoom)
                    {
                        console.log("center/zoom changed", newCenterZoom, oldCenterZoom);
                        newCenterZoom = newCenterZoom.split(',');
                        map.centerAndZoom([newCenterZoom[0], newCenterZoom[1]], newCenterZoom[2]);
                    });

                    map.on('extent-change', function(e)
                    {
                        console.log('extent-change', e.extent.toJson());
                        console.log('extent-change geo', map.geographicExtent);

                        var geoCenter = map.geographicExtent.getCenter();

                        $scope.$apply(function()
                        {
                            $scope.center.lng = geoCenter.x;
                            $scope.center.lat = geoCenter.y;
                            $scope.zoom = map.getZoom();

                            if( $attrs.extentChange )
                                $scope.extentChange()(e);
                        });

                    });

                    // clean up
                    $scope.$on('$destroy', function () {
                        map.destroy();
                        // TODO: anything else?
                    });
                });

                // method returns the promise that will be resolved with the map
                this.getMap = function() {
                    return mapDeferred.promise;
                };

                // adds the layer, returns the promise that will be resolved with the result of map.addLayer
                this.addLayer = function(layer) {
                    return this.getMap().then(function(map) {
                        return map.addLayer(layer);
                    });
                };

                // array to store layer info, needed for legend
                // TODO: is this the right place for this?
                // can it be done on the legend directive itself?
                this.addLayerInfo = function(lyrInfo) {
                    if (!this.layerInfos) {
                        this.layerInfos = [lyrInfo];
                    } else {
                        this.layerInfos.push(lyrInfo);
                    }
                };
                this.getLayerInfos = function() {
                    return this.layerInfos;
                };
            }
        };
    });

})(angular);
<commit_msg>it works now<commit_after>(function(angular) {
    'use strict';

    angular.module('esri.map').directive('esriMap', function($q, $timeout, esriLoader) {

        return {
            // element only
            restrict: 'E',

            // isoloate scope
            scope: {
                // two-way binding for center/zoom
                // because map pan/zoom can chnage these
                center: '=',
                zoom: '=',
                // one-way binding for other properties
                basemap: '@',
                // function binding for event handlers
                load: '&',
                extentChange: '&'
            },

            // replace tag with div with same id
            compile: function($element, $attrs) {
                // remove the id attribute from the main element
                $element.removeAttr('id');

                // append a new div inside this element, this is where we will create our map
                $element.append('

');

                // since we are using compile we need to return our linker function
                // the 'link' function handles how our directive responds to changes in $scope
                /*jshint unused: false*/
                return function(scope, element, attrs, controller) {
                };
            },

            // directive api
            controller: function($scope, $element, $attrs) {
                // only do this once per directive
                // this deferred will be resolved with the map
                var mapDeferred = $q.defer();

                // setup our map options based on the attributes and scope
                var mapOptions = {};

                // center/zoom/extent
                // check for convenience extent attribute
                // otherwise get from scope center/zoom
                if ($attrs.extent) {
                    mapOptions.extent = $scope[$attrs.extent];
                } else {
                    if ($scope.center.lng && $scope.center.lat) {
                        mapOptions.center = [$scope.center.lng, $scope.center.lat];
                    } else if ($scope.center) {
                        mapOptions.center = $scope.center;
                    }
                    if ($scope.zoom) {
                        mapOptions.zoom = $scope.zoom;
                    }
                }

                // basemap
                if ($scope.basemap) {
                    mapOptions.basemap = $scope.basemap;
                }

                esriLoader('esri/map').then(function(Map){

                    // initialize map and resolve the deferred
                    var map = new Map($attrs.id, mapOptions);

                    mapDeferred.resolve(map);

                    // make a reference to the map object available
                    // to the controller once it is loaded.
                    map.on('load', function() {
                        if (!$attrs.load) {
                            return;
                        }
                        $scope.$apply(function() {
                            $scope.load()(map);
                        });
                    });

                    // listen for changes to scope and update map
                    $scope.$watch('basemap', function(newBasemap, oldBasemap) {
                        if (map.loaded && newBasemap !== oldBasemap) {
                            map.setBasemap(newBasemap);
                        }
                    });

                    $scope.inEvent = false;

                    $scope.$watch(function(scope){ return [scope.center.lng,scope.center.lat, scope.zoom].join(',');}, function(newCenterZoom,oldCenterZoom)
                    // $scope.$watchGroup(['center.lng','center.lat', 'zoom'], function(newCenterZoom,oldCenterZoom) // supported starting at Angular 1.3
                    {
                        if( $scope.inEvent ) {
                            return;
                        }

                        $scope.inEvent = true;  // prevent circular updates between $watch and $apply

                        console.log('center/zoom changed', newCenterZoom, oldCenterZoom);
                        newCenterZoom = newCenterZoom.split(',');
                        map.centerAndZoom([newCenterZoom[0], newCenterZoom[1]], newCenterZoom[2]).then(function()
                        {
                            console.log('after centerAndZoom()');
                            $scope.inEvent = false;
                        });
                    });

                    map.on('extent-change', function(e)
                    {
                        if( $scope.inEvent ) {
                            return;
                        }

                        $scope.inEvent = true;  // prevent circular updates between $watch and $apply

                        console.log('extent-change geo', map.geographicExtent);

                        $scope.$apply(function()
                        {
                            var geoCenter = map.geographicExtent.getCenter();

                            $scope.center.lng = geoCenter.x;
                            $scope.center.lat = geoCenter.y;
                            $scope.zoom = map.getZoom();

                            // we might want to execute event handler even if $scope.inEvent is true
                            if( $attrs.extentChange ) {
                                $scope.extentChange()(e);
                            }

                            $timeout(function(){
                                // this will be executed after the $digest cycle
                                console.log('after apply()');
                                $scope.inEvent = false;
                            },0);
                        });
                    });

                    // clean up
                    $scope.$on('$destroy', function () {
                        map.destroy();
                        // TODO: anything else?
                    });
                });

                // method returns the promise that will be resolved with the map
                this.getMap = function() {
                    return mapDeferred.promise;
                };

                // adds the layer, returns the promise that will be resolved with the result of map.addLayer
                this.addLayer = function(layer) {
                    return this.getMap().then(function(map) {
                        return map.addLayer(layer);
                    });
                };

                // array to store layer info, needed for legend
                // TODO: is this the right place for this?
                // can it be done on the legend directive itself?
                this.addLayerInfo = function(lyrInfo) {
                    if (!this.layerInfos) {
                        this.layerInfos = [lyrInfo];
                    } else {
                        this.layerInfos.push(lyrInfo);
                    }
                };
                this.getLayerInfos = function() {
                    return this.layerInfos;
                };
            }
        };
    });

})(angular);
<|endoftext|>