<commit_before>'use strict';

const assert = require('assert');
const debug = require('debug')('koa-joi-router');
const isGenFn = require('is-gen-fn');
const flatten = require('flatten');
const methods = require('methods');
const KoaRouter = require('koa-router');
const busboy = require('await-busboy');
const parse = require('co-body');
const Joi = require('joi');
const slice = require('sliced');
const delegate = require('delegates');
const clone = require('clone');
const OutputValidator = require('./output-validator');

module.exports = Router;

// expose Joi for use in applications
Router.Joi = Joi;

function Router() {
  if (!(this instanceof Router)) {
    return new Router();
  }

  this.routes = [];
  this.router = new KoaRouter();
}

/**
 * Array of routes
 *
 * Router.prototype.routes;
 * @api public
 */

/**
 * Delegate methods to internal router object
 */

delegate(Router.prototype, 'router')
  .method('prefix')
  .method('use')
  .method('param');

/**
 * Return koa middleware
 * @return {Function}
 * @api public
 */

Router.prototype.middleware = function middleware() {
  return this.router.routes();
};

/**
 * Adds a route or array of routes to this router, storing the route
 * in `this.routes`.
 *
 * Example:
 *
 *   var admin = router();
 *
 *   admin.route({
 *     method: 'get',
 *     path: '/do/stuff/:id',
 *     handler: function *(next){},
 *     validate: {
 *       header: Joi object
 *       params: Joi object (:id)
 *       query: Joi object (validate key/val pairs in the querystring)
 *       body: Joi object (the request payload body) (json or form)
 *       maxBody: '64kb' // (json, x-www-form-urlencoded only - not stream size)
 *                       // optional
 *       type: 'json|form|multipart' (required when body is specified)
 *       failure: 400 // http error code to use
 *     },
 *     meta: { // this is ignored but useful for doc generators etc
 *       desc: 'We can use this for docs generation.'
 *       produces: ['application/json']
 *       model: {} // response object definition
 *     }
 *   })
 *
 * @param {Object} spec
 * @return {Router} self
 * @api public
 */

Router.prototype.route = function route(spec) {
  if (Array.isArray(spec)) {
    for (let i = 0; i < spec.length; i++) {
      this._addRoute(spec[i]);
    }
  } else {
    this._addRoute(spec);
  }

  return this;
};

/**
 * Adds a route to this router, storing the route
 * in `this.routes`.
 *
 * @param {Object} spec
 * @api private
 */

Router.prototype._addRoute = function addRoute(spec) {
  this._validateRouteSpec(spec);
  this.routes.push(spec);

  debug('add %s "%s"', spec.method, spec.path);

  const bodyParser = makeBodyParser(spec);
  const specExposer = makeSpecExposer(spec);
  const validator = makeValidator(spec);
  const preHandlers = spec.pre ? flatten(spec.pre) : [];
  const handlers = flatten(spec.handler);

  const args = [
    spec.path
  ].concat(preHandlers, [
    prepareRequest,
    specExposer,
    bodyParser,
    validator
  ], handlers);

  const router = this.router;

  spec.method.forEach((method) => {
    router[method].apply(router, args);
  });
};

/**
 * Validate the spec passed to route()
 *
 * @param {Object} spec
 * @api private
 */

Router.prototype._validateRouteSpec = function validateRouteSpec(spec) {
  assert(spec, 'missing spec');

  const ok = typeof spec.path === 'string' || spec.path instanceof RegExp;
  assert(ok, 'invalid route path');

  checkHandler(spec);
  checkPreHandler(spec);
  checkMethods(spec);
  checkValidators(spec);
};

/**
 * @api private
 */

function checkHandler(spec) {
  if (!Array.isArray(spec.handler)) {
    spec.handler = [spec.handler];
  }

  return flatten(spec.handler).forEach(isSupportedFunction);
}

/**
 * @api private
 */

function checkPreHandler(spec) {
  if (!spec.pre) {
    return
  }

  if (!Array.isArray(spec.pre)) {
    spec.pre = [spec.pre];
  }

  return flatten(spec.pre).forEach(isSupportedFunction);
}

/**
 * @api private
 */

function isSupportedFunction(handler) {
  assert.equal('function', typeof handler, 'route handler must be a function');

  if (isGenFn(handler)) {
    throw new Error(`route handlers must not be GeneratorFunctions
       Please use "async function" or "function".`);
  }
}

/**
 * Validate the spec.method
 *
 * @param {Object} spec
 * @api private
 */

function checkMethods(spec) {
  assert(spec.method, 'missing route methods');

  if (typeof spec.method === 'string') {
    spec.method = spec.method.split(' ');
  }

  if (!Array.isArray(spec.method)) {
    throw new TypeError('route methods must be an array or string');
  }

  if (spec.method.length === 0) {
    throw new Error('missing route method');
  }

  spec.method.forEach((method, i) => {
    assert(typeof method === 'string', 'route method must be a string');
    spec.method[i] = method.toLowerCase();
  });
}

/**
 * Validate the spec.validators
 *
 * @param {Object} spec
 * @api private
 */

function checkValidators(spec) {
  if (!spec.validate) return;

  let text;
  if (spec.validate.body) {
    text = 'validate.type must be declared when using validate.body';
    assert(/json|form/.test(spec.validate.type), text);
  }

  if (spec.validate.type) {
    text = 'validate.type must be either json, form, multipart or stream';
    assert(/json|form|multipart|stream/i.test(spec.validate.type), text);
  }

  if (spec.validate.output) {
    spec.validate._outputValidator = new OutputValidator(spec.validate.output);
  }

  // default HTTP status code for failures
  if (!spec.validate.failure) {
    spec.validate.failure = 400;
  }
}

/**
 * Creates body parser middleware.
 *
 * @param {Object} spec
 * @return {async function}
 * @api private
 */

function makeBodyParser(spec) {
  return async function parsePayload(ctx, next) {
    if (!(spec.validate && spec.validate.type)) return await next();

    let opts;

    try {
      switch (spec.validate.type) {
        case 'json':
          if (!ctx.request.is('json')) {
            return ctx.throw(400, 'expected json');
          }

          opts = spec.validate.jsonOptions || {};
          if (typeof opts.limit === 'undefined') {
            opts.limit = spec.validate.maxBody;
          }

          ctx.request.body = ctx.request.body || await parse.json(ctx, opts);
          break;

        case 'form':
          if (!ctx.request.is('urlencoded')) {
            return ctx.throw(400, 'expected x-www-form-urlencoded');
          }

          opts = spec.validate.formOptions || {};
          if (typeof opts.limit === 'undefined') {
            opts.limit = spec.validate.maxBody;
          }

          ctx.request.body = ctx.request.body || await parse.form(ctx, opts);
          break;

        case 'stream':
        case 'multipart':
          if (!ctx.request.is('multipart/*')) {
            return ctx.throw(400, 'expected multipart');
          }

          opts = spec.validate.multipartOptions || {};
          if (typeof opts.autoFields === 'undefined') {
            opts.autoFields = true;
          }

          ctx.request.parts = busboy(ctx, opts);
          break;
      }
    } catch (err) {
      captureError(ctx, 'type', err);
      if (!spec.validate.continueOnError) return ctx.throw(err);
    }

    await next();
  };
}

/**
 * @api private
 */

function captureError(ctx, type, err) {
  // expose Error message to JSON.stringify()
  err.msg = err.message;
  if (!ctx.invalid) ctx.invalid = {};
  ctx.invalid[type] = err;
}

/**
 * Creates validator middleware.
 *
 * @param {Object} spec
 * @return {async function}
 * @api private
 */

function makeValidator(spec) {
  const props = 'header query params body'.split(' ');

  return async function validator(ctx, next) {
    if (!spec.validate) return await next();

    let err;

    for (let i = 0; i < props.length; ++i) {
      const prop = props[i];

      if (spec.validate[prop]) {
        err = validateInput(prop, ctx, spec.validate);

        if (err) {
          captureError(ctx, prop, err);
          if (!spec.validate.continueOnError) return ctx.throw(err);
        }
      }
    }

    await next();

    if (spec.validate._outputValidator) {
      debug('validating output');

      err = spec.validate._outputValidator.validate(ctx);
      if (err) {
        err.status = 500;
        return ctx.throw(err);
      }
    }
  };
}

/**
 * Exposes route spec.
 *
 * @param {Object} spec
 * @return {async function}
 * @api private
 */
function makeSpecExposer(spec) {
  const defn = clone(spec);
  return async function specExposer(ctx, next) {
    ctx.state.route = defn;
    await next();
  };
}

/**
 * Middleware which creates `request.params`.
 *
 * @api private
 */

async function prepareRequest(ctx, next) {
  ctx.request.params = ctx.params;
  await next();
}

/**
 * Validates request[prop] data with the defined validation schema.
 *
 * @param {String} prop
 * @param {koa.Request} request
 * @param {Object} validate
 * @returns {Error|undefined}
 * @api private
 */

function validateInput(prop, ctx, validate) {
  debug('validating %s', prop);

  const request = ctx.request;
  const res = Joi.validate(request[prop], validate[prop]);

  if (res.error) {
    res.error.status = validate.failure;
    return res.error;
  }

  // update our request w/ the casted values
  switch (prop) {
    case 'header': // request.header is getter only, cannot set it
    case 'query': // setting request.query directly causes casting back to strings
      Object.keys(res.value).forEach((key) => {
        request[prop][key] = res.value[key];
      });
      break;
    case 'params':
      request.params = ctx.params = res.value;
      break;
    default:
      request[prop] = res.value;
  }
}

/**
 * Routing shortcuts for all HTTP methods
 *
 * Example:
 *
 *    var admin = router();
 *
 *    admin.get('/user', async function(ctx) {
 *      ctx.body = ctx.session.user;
 *    })
 *
 *    var validator = Joi().object().keys({ name: Joi.string() });
 *    var config = { validate: { body: validator }};
 *
 *    admin.post('/user', config, async function(ctx){
 *      console.log(ctx.body);
 *    })
 *
 *    async function commonHandler(ctx){
 *      // ...
 *    }
 *    admin.post('/account', [commonHandler, async function(ctx){
 *      // ...
 *    }]);
 *
 * @param {String} path
 * @param {Object} [config] optional
 * @param {async function|async function[]} handler(s)
 * @return {App} self
 */

methods.forEach((method) => {
  method = method.toLowerCase();

  Router.prototype[method] = function(path) {
    // path, handler1, handler2, ...
    // path, config, handler1
    // path, config, handler1, handler2, ...
    // path, config, [handler1, handler2], handler3, ...

    let fns;
    let config;

    if (typeof arguments[1] === 'function' || Array.isArray(arguments[1])) {
      config = {};
      fns = slice(arguments, 1);
    } else if (typeof arguments[1] === 'object') {
      config = arguments[1];
      fns = slice(arguments, 2);
    }

    const spec = {
      path: path,
      method: method,
      handler: fns
    };

    Object.assign(spec, config);

    this.route(spec);
    return this;
  };
});
<commit_msg>refactoring makeBodyParser<commit_after>'use strict';

const assert = require('assert');
const debug = require('debug')('koa-joi-router');
const isGenFn = require('is-gen-fn');
const flatten = require('flatten');
const methods = require('methods');
const KoaRouter = require('koa-router');
const busboy = require('await-busboy');
const parse = require('co-body');
const Joi = require('joi');
const slice = require('sliced');
const delegate = require('delegates');
const clone = require('clone');
const OutputValidator = require('./output-validator');

module.exports = Router;

// expose Joi for use in applications
Router.Joi = Joi;

function Router() {
  if (!(this instanceof Router)) {
    return new Router();
  }

  this.routes = [];
  this.router = new KoaRouter();
}

/**
 * Array of routes
 *
 * Router.prototype.routes;
 * @api public
 */

/**
 * Delegate methods to internal router object
 */

delegate(Router.prototype, 'router')
  .method('prefix')
  .method('use')
  .method('param');

/**
 * Return koa middleware
 * @return {Function}
 * @api public
 */

Router.prototype.middleware = function middleware() {
  return this.router.routes();
};

/**
 * Adds a route or array of routes to this router, storing the route
 * in `this.routes`.
 *
 * Example:
 *
 *   var admin = router();
 *
 *   admin.route({
 *     method: 'get',
 *     path: '/do/stuff/:id',
 *     handler: function *(next){},
 *     validate: {
 *       header: Joi object
 *       params: Joi object (:id)
 *       query: Joi object (validate key/val pairs in the querystring)
 *       body: Joi object (the request payload body) (json or form)
 *       maxBody: '64kb' // (json, x-www-form-urlencoded only - not stream size)
 *                       // optional
 *       type: 'json|form|multipart' (required when body is specified)
 *       failure: 400 // http error code to use
 *     },
 *     meta: { // this is ignored but useful for doc generators etc
 *       desc: 'We can use this for docs generation.'
 *       produces: ['application/json']
 *       model: {} // response object definition
 *     }
 *   })
 *
 * @param {Object} spec
 * @return {Router} self
 * @api public
 */

Router.prototype.route = function route(spec) {
  if (Array.isArray(spec)) {
    for (let i = 0; i < spec.length; i++) {
      this._addRoute(spec[i]);
    }
  } else {
    this._addRoute(spec);
  }

  return this;
};

/**
 * Adds a route to this router, storing the route
 * in `this.routes`.
 *
 * @param {Object} spec
 * @api private
 */

Router.prototype._addRoute = function addRoute(spec) {
  this._validateRouteSpec(spec);
  this.routes.push(spec);

  debug('add %s "%s"', spec.method, spec.path);

  const bodyParser = makeBodyParser(spec);
  const specExposer = makeSpecExposer(spec);
  const validator = makeValidator(spec);
  const preHandlers = spec.pre ? flatten(spec.pre) : [];
  const handlers = flatten(spec.handler);

  const args = [
    spec.path
  ].concat(preHandlers, [
    prepareRequest,
    specExposer,
    bodyParser,
    validator
  ], handlers);

  const router = this.router;

  spec.method.forEach((method) => {
    router[method].apply(router, args);
  });
};

/**
 * Validate the spec passed to route()
 *
 * @param {Object} spec
 * @api private
 */

Router.prototype._validateRouteSpec = function validateRouteSpec(spec) {
  assert(spec, 'missing spec');

  const ok = typeof spec.path === 'string' || spec.path instanceof RegExp;
  assert(ok, 'invalid route path');

  checkHandler(spec);
  checkPreHandler(spec);
  checkMethods(spec);
  checkValidators(spec);
};

/**
 * @api private
 */

function checkHandler(spec) {
  if (!Array.isArray(spec.handler)) {
    spec.handler = [spec.handler];
  }

  return flatten(spec.handler).forEach(isSupportedFunction);
}

/**
 * @api private
 */

function checkPreHandler(spec) {
  if (!spec.pre) {
    return;
  }

  if (!Array.isArray(spec.pre)) {
    spec.pre = [spec.pre];
  }

  return flatten(spec.pre).forEach(isSupportedFunction);
}

/**
 * @api private
 */

function isSupportedFunction(handler) {
  assert.equal('function', typeof handler, 'route handler must be a function');

  if (isGenFn(handler)) {
    throw new Error(`route handlers must not be GeneratorFunctions
       Please use "async function" or "function".`);
  }
}

/**
 * Validate the spec.method
 *
 * @param {Object} spec
 * @api private
 */

function checkMethods(spec) {
  assert(spec.method, 'missing route methods');

  if (typeof spec.method === 'string') {
    spec.method = spec.method.split(' ');
  }

  if (!Array.isArray(spec.method)) {
    throw new TypeError('route methods must be an array or string');
  }

  if (spec.method.length === 0) {
    throw new Error('missing route method');
  }

  spec.method.forEach((method, i) => {
    assert(typeof method === 'string', 'route method must be a string');
    spec.method[i] = method.toLowerCase();
  });
}

/**
 * Validate the spec.validators
 *
 * @param {Object} spec
 * @api private
 */

function checkValidators(spec) {
  if (!spec.validate) return;

  let text;
  if (spec.validate.body) {
    text = 'validate.type must be declared when using validate.body';
    assert(/json|form/.test(spec.validate.type), text);
  }

  if (spec.validate.type) {
    text = 'validate.type must be either json, form, multipart or stream';
    assert(/json|form|multipart|stream/i.test(spec.validate.type), text);
  }

  if (spec.validate.output) {
    spec.validate._outputValidator = new OutputValidator(spec.validate.output);
  }

  // default HTTP status code for failures
  if (!spec.validate.failure) {
    spec.validate.failure = 400;
  }
}

/**
 * Does nothing
 * @param  {[type]}   ctx  [description]
 * @param  {Function} next [description]
 * @return {async function}        [description]
 * @api private
 */

async function noopMiddleware(ctx, next) {
  return await next();
}

/**
 * Handles parser internal errors
 * @param  {Object} spec         [description]
 * @param  {function} parsePayload [description]
 * @return {async function}              [description]
 * @api private
 */

function wrapError(spec, parsePayload) {
  return async function errorHandler(ctx, next) {
    try {
      await parsePayload(ctx, next);
    } catch (err) {
      captureError(ctx, 'type', err);
      if (spec.validate.continueOnError) {
        return await next();
      } else {
        return ctx.throw(err);
      }
    }
  };
}

/**
 * Creates JSON body parser middleware.
 *
 * @param {Object} spec
 * @return {async function}
 * @api private
 */

function makeJSONBodyParser(spec) {
  const opts = spec.validate.jsonOptions || {};
  if (typeof opts.limit === 'undefined') {
    opts.limit = spec.validate.maxBody;
  }

  return async function parseJSONPayload(ctx, next) {
    if (!ctx.request.is('json')) {
      return ctx.throw(400, 'expected json');
    }
    ctx.request.body = ctx.request.body || await parse.json(ctx, opts);
    await next();
  };
}

/**
 * Creates form body parser middleware.
 *
 * @param {Object} spec
 * @return {async function}
 * @api private
 */

function makeFormBodyParser(spec) {
  const opts = spec.validate.formOptions || {};
  if (typeof opts.limit === 'undefined') {
    opts.limit = spec.validate.maxBody;
  }
  return async function parseFormBody(ctx, next) {
    if (!ctx.request.is('urlencoded')) {
      return ctx.throw(400, 'expected x-www-form-urlencoded');
    }
    ctx.request.body = ctx.request.body || await parse.form(ctx, opts);
    await next();
  };
}

/**
 * Creates stream/multipart-form body parser middleware.
 *
 * @param {Object} spec
 * @return {async function}
 * @api private
 */

function makeMultipartParser(spec) {
  const opts = spec.validate.multipartOptions || {};
  if (typeof opts.autoFields === 'undefined') {
    opts.autoFields = true;
  }
  return async function parseMultipart(ctx, next) {
    if (!ctx.request.is('multipart/*')) {
      return ctx.throw(400, 'expected multipart');
    }
    ctx.request.parts = busboy(ctx, opts);
    await next();
  };
}

/**
 * Creates body parser middleware.
 *
 * @param {Object} spec
 * @return {async function}
 * @api private
 */

function makeBodyParser(spec) {
  if (!(spec.validate && spec.validate.type)) return noopMiddleware;

  switch (spec.validate.type) {
    case 'json':
      return wrapError(spec, makeJSONBodyParser(spec));
    case 'form':
      return wrapError(spec, makeFormBodyParser(spec));
    case 'stream':
    case 'multipart':
      return wrapError(spec, makeMultipartParser(spec));
    default:
      throw new Error(`unsupported body type: ${spec.validate.type}`);
  }
}

/**
 * @api private
 */

function captureError(ctx, type, err) {
  // expose Error message to JSON.stringify()
  err.msg = err.message;
  if (!ctx.invalid) ctx.invalid = {};
  ctx.invalid[type] = err;
}

/**
 * Creates validator middleware.
 *
 * @param {Object} spec
 * @return {async function}
 * @api private
 */

function makeValidator(spec) {
  const props = 'header query params body'.split(' ');

  return async function validator(ctx, next) {
    if (!spec.validate) return await next();

    let err;

    for (let i = 0; i < props.length; ++i) {
      const prop = props[i];

      if (spec.validate[prop]) {
        err = validateInput(prop, ctx, spec.validate);

        if (err) {
          captureError(ctx, prop, err);
          if (!spec.validate.continueOnError) return ctx.throw(err);
        }
      }
    }

    await next();

    if (spec.validate._outputValidator) {
      debug('validating output');

      err = spec.validate._outputValidator.validate(ctx);
      if (err) {
        err.status = 500;
        return ctx.throw(err);
      }
    }
  };
}

/**
 * Exposes route spec.
 *
 * @param {Object} spec
 * @return {async function}
 * @api private
 */
function makeSpecExposer(spec) {
  const defn = clone(spec);
  return async function specExposer(ctx, next) {
    ctx.state.route = defn;
    await next();
  };
}

/**
 * Middleware which creates `request.params`.
 *
 * @api private
 */

async function prepareRequest(ctx, next) {
  ctx.request.params = ctx.params;
  await next();
}

/**
 * Validates request[prop] data with the defined validation schema.
 *
 * @param {String} prop
 * @param {koa.Request} request
 * @param {Object} validate
 * @returns {Error|undefined}
 * @api private
 */

function validateInput(prop, ctx, validate) {
  debug('validating %s', prop);

  const request = ctx.request;
  const res = Joi.validate(request[prop], validate[prop]);

  if (res.error) {
    res.error.status = validate.failure;
    return res.error;
  }

  // update our request w/ the casted values
  switch (prop) {
    case 'header': // request.header is getter only, cannot set it
    case 'query': // setting request.query directly causes casting back to strings
      Object.keys(res.value).forEach((key) => {
        request[prop][key] = res.value[key];
      });
      break;
    case 'params':
      request.params = ctx.params = res.value;
      break;
    default:
      request[prop] = res.value;
  }
}

/**
 * Routing shortcuts for all HTTP methods
 *
 * Example:
 *
 *    var admin = router();
 *
 *    admin.get('/user', async function(ctx) {
 *      ctx.body = ctx.session.user;
 *    })
 *
 *    var validator = Joi().object().keys({ name: Joi.string() });
 *    var config = { validate: { body: validator }};
 *
 *    admin.post('/user', config, async function(ctx){
 *      console.log(ctx.body);
 *    })
 *
 *    async function commonHandler(ctx){
 *      // ...
 *    }
 *    admin.post('/account', [commonHandler, async function(ctx){
 *      // ...
 *    }]);
 *
 * @param {String} path
 * @param {Object} [config] optional
 * @param {async function|async function[]} handler(s)
 * @return {App} self
 */

methods.forEach((method) => {
  method = method.toLowerCase();

  Router.prototype[method] = function(path) {
    // path, handler1, handler2, ...
    // path, config, handler1
    // path, config, handler1, handler2, ...
    // path, config, [handler1, handler2], handler3, ...

    let fns;
    let config;

    if (typeof arguments[1] === 'function' || Array.isArray(arguments[1])) {
      config = {};
      fns = slice(arguments, 1);
    } else if (typeof arguments[1] === 'object') {
      config = arguments[1];
      fns = slice(arguments, 2);
    }

    const spec = {
      path: path,
      method: method,
      handler: fns
    };

    Object.assign(spec, config);

    this.route(spec);
    return this;
  };
});
<|endoftext|>


<commit_before>"use strict";

var EventEmitter = require("events").EventEmitter;
var doValidation = require("../misc/Validation").SharedObjectValidation;
var differ = require("deep-diff");
var clone = require("../misc/clone");

class SharedObjectClient extends EventEmitter{
    constructor(endpoint, transports){
        super();
        if (!transports.rpc || !transports.source)
            throw new Error("Shared objects need both Source and RPC transports to be configured");

        this.endpoint = endpoint;
        this.initTransport = transports.rpc;
        this.updateTransport = transports.source;

        this._init();
    }

    subscribe(){
        this.updateTransport.subscribe("_SO_" + this.endpoint.name);
    }

    _processMessage(data){
        if (data.endpoint == "_SO_" + this.endpoint.name){
            var idx = data.message.v - (this._v + 1);
            if (idx < 0){
                console.error("Bad version! Reinit!");
                return this._init();
            }
            this.procBuffer[idx] = data.message.diffs;
            this.timeBuffer[idx] = data.message.now;

            this.outstandingDiffs++;
            process.nextTick(this._tryApply.bind(this));
        }
    }

    _tryApply(){
        var totalDiffs = [];

        while(!!this.procBuffer[0]){
            var diffs = this.procBuffer.shift().reverse();
            this.outstandingDiffs--;
            totalDiffs = diffs.concat(totalDiffs);

            for (let diff of diffs){
                differ.applyChange(this.data, true, diff);
            }

            this.timeSum += new Date() - this.timeBuffer.shift();
            this.timeCount++;
            if (this.timeCount == 1000){
                console.log("Average time: " + (this.timeSum/(10000)) + " ms");
                this.timeSum = 0;
                this.timeCount = 0;
            }

            this._v++;
        }

        if (totalDiffs.length > 0) {
            this.emit('update', totalDiffs);
        }else if (this.ready && this.outstandingDiffs > 10){
            console.error("Too many outstanding diffs, missed a version. Reinit.");
            this._init();
        }
    }

    _init(){

        this.data = {};
        this._v = 0;
        this.procBuffer = [];
        this.timeBuffer = [];

        this.timeSum = 0;
        this.timeCount = 0;

        this.outstandingDiffs = 0;

        this.ready = false;

        this.initTransport.send({
            endpoint: "_SO_" + this.endpoint.name,
            input: "init"
        }, (answer) => {
            this.data = answer.res.data;
            this._v = answer.res.v;
            console.log("Init installed version",this._v);
            this.procBuffer.splice(0,this._v);
            this.timeBuffer.splice(0,this._v);
            this.outstandingDiffs = 0;
            for (let i of this.procBuffer){
                if (!!i)
                    this.outstandingDiffs++;
            }
            this.ready = true;
            this._tryApply();
            this.emit('init');
        });
    }
}

module.exports = SharedObjectClient;<commit_msg>Report every 10 pops<commit_after>"use strict";

var EventEmitter = require("events").EventEmitter;
var doValidation = require("../misc/Validation").SharedObjectValidation;
var differ = require("deep-diff");
var clone = require("../misc/clone");

class SharedObjectClient extends EventEmitter{
    constructor(endpoint, transports){
        super();
        if (!transports.rpc || !transports.source)
            throw new Error("Shared objects need both Source and RPC transports to be configured");

        this.endpoint = endpoint;
        this.initTransport = transports.rpc;
        this.updateTransport = transports.source;

        this._init();
    }

    subscribe(){
        this.updateTransport.subscribe("_SO_" + this.endpoint.name);
    }

    _processMessage(data){
        if (data.endpoint == "_SO_" + this.endpoint.name){
            var idx = data.message.v - (this._v + 1);
            if (idx < 0){
                console.error("Bad version! Reinit!");
                return this._init();
            }
            this.procBuffer[idx] = data.message.diffs;
            this.timeBuffer[idx] = data.message.now;

            this.outstandingDiffs++;
            process.nextTick(this._tryApply.bind(this));
        }
    }

    _tryApply(){
        var totalDiffs = [];

        while(!!this.procBuffer[0]){
            var diffs = this.procBuffer.shift().reverse();
            this.outstandingDiffs--;
            totalDiffs = diffs.concat(totalDiffs);

            for (let diff of diffs){
                differ.applyChange(this.data, true, diff);
            }

            this.timeSum += new Date() - this.timeBuffer.shift();
            this.timeCount++;
            if (this.timeCount == 10){
                console.log("Average time: " + (this.timeSum/(10000)) + " ms");
                this.timeSum = 0;
                this.timeCount = 0;
            }

            this._v++;
        }

        if (totalDiffs.length > 0) {
            this.emit('update', totalDiffs);
        }else if (this.ready && this.outstandingDiffs > 10){
            console.error("Too many outstanding diffs, missed a version. Reinit.");
            this._init();
        }
    }

    _init(){

        this.data = {};
        this._v = 0;
        this.procBuffer = [];
        this.timeBuffer = [];

        this.timeSum = 0;
        this.timeCount = 0;

        this.outstandingDiffs = 0;

        this.ready = false;

        this.initTransport.send({
            endpoint: "_SO_" + this.endpoint.name,
            input: "init"
        }, (answer) => {
            this.data = answer.res.data;
            this._v = answer.res.v;
            console.log("Init installed version",this._v);
            this.procBuffer.splice(0,this._v);
            this.timeBuffer.splice(0,this._v);
            this.outstandingDiffs = 0;
            for (let i of this.procBuffer){
                if (!!i)
                    this.outstandingDiffs++;
            }
            this.ready = true;
            this._tryApply();
            this.emit('init');
        });
    }
}

module.exports = SharedObjectClient;<|endoftext|>


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

/**
 * Lightweight event & listener abstraction for a single event type.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const axon = require( 'AXON/axon' );
  const EmitterIO = require( 'AXON/EmitterIO' );
  const PhetioObject = require( 'TANDEM/PhetioObject' );
  const Tandem = require( 'TANDEM/Tandem' );
  const Validator = require( 'AXON/Validator' );

  // constants
  const EmitterIOWithNoArgs = EmitterIO( [] );

  // Simulations have thousands of Emitters, so we re-use objects where possible.
  const EMPTY_ARRAY = [];
  assert && Object.freeze( EMPTY_ARRAY );

  /**
   * @param {Object} [options]
   */
  class Emitter extends PhetioObject {
    constructor( options ) {

      options = _.extend( {

        // {Array.<Object>|null} - array of "Validator Options" Objects that hold options for how to validate each
        // argument, see Validator.js for details.
        argumentTypes: EMPTY_ARRAY,

        tandem: Tandem.optional,
        phetioState: false,
        phetioType: EmitterIOWithNoArgs, // subtypes can override with EmitterIO([...]), see EmitterIO.js
        listener: null // {function} [listener] optional listener to be added during construction.
      }, options );

      // phetioPlayback events need to know the order the arguments occur in order to call EmitterIO.emit()
      // Indicate whether the event is for playback, but leave this "sparse"--only indicate when this happens to be true
      if ( options.phetioPlayback ) {
        options.phetioEventMetadata = options.phetioEventMetadata || {};
        assert && assert( !options.phetioEventMetadata.hasOwnProperty( 'dataKeys' ), 'dataKeys should be supplied by Emitter, not elsewhere' );
        options.phetioEventMetadata.dataKeys = options.phetioType.elements.map( element => element.name );
      }

      super( options );

      Validator.validate( options.argumentTypes, { valueType: Array } );

      if ( assert ) {

        // Iterate through all argumentType validator options and make sure that they won't validate options on validating value
        options.argumentTypes.forEach( validatorOptions => {
          assert && assert(
            validatorOptions.validateOptionsOnValidateValue === undefined,
            'emitter sets its own validateOptionsOnValidateValue for each argument type'
          );
          validatorOptions.validateOptionsOnValidateValue = false;

          // Changing the validator options after construction indicates a logic error
          assert && Object.freeze( validatorOptions );

          // validate the options passed in to validate each emitter argument
          Validator.validateOptions( validatorOptions );
        } );

        // Changing after construction indicates a logic error
        assert && Object.freeze( options.argumentTypes );
      }

      // @private {function|false}
      this.validate = assert && function() {
        assert(
          arguments.length === options.argumentTypes.length,
          `Emitted unexpected number of args. Expected: ${options.argumentTypes.length} and received ${arguments.length}`
        );
        for ( let i = 0; i < options.argumentTypes.length; i++ ) {
          Validator.validate( arguments[ i ], options.argumentTypes[ i ] );
        }
      };

      // @private {function[]} - the listeners that will be called on emit
      this.listeners = [];

      // @private {function[][]} - during emit() keep track of which listeners should receive events in order to manage
      //                         - removal of listeners during emit()
      this.activeListenersStack = [];

      // If an initial listener was specified, add it now
      options.listener && this.addListener( options.listener );
    }

    /**
     * Dispose an Emitter that is no longer used.  Like Property.dispose, this method checks that there are no leaked
     * listeners.
     */
    dispose() {

      // See https://github.com/phetsims/axon/issues/124
      this.listeners.length = 0;

      PhetioObject.prototype.dispose.call( this );
    }

    /**
     * Adds a listener.  When emitting, the listeners are called in the order they were added.
     * @param {function} listener
     * @public
     */
    addListener( listener ) {

      assert && assert( this.listeners.indexOf( listener ) === -1, 'Cannot add the same listener twice' );

      // If a listener is added during an emit(), we must make a copy of the current list of listeners--the newly added
      // listener will be available for the next emit() but not the one in progress.  This is to match behavior with
      // removeListener.
      this.defendListeners();

      this.listeners.push( listener );
    }

    /**
     * Removes a listener
     * @param {function} listener
     * @public
     */
    removeListener( listener ) {

      const index = this.listeners.indexOf( listener );

      // Throw an error when removing a non-listener (except when the Emitter has already been disposed, see
      // https://github.com/phetsims/sun/issues/394#issuecomment-419998231
      if ( assert && !this.isDisposed ) {
        assert && assert( index >= 0, 'tried to removeListener on something that wasn\'t a listener' );
      }

      // If an emit is in progress, make a copy of the current list of listeners--the removed listener will remain in
      // the list and be called for this emit call, see #72
      this.defendListeners();

      this.listeners.splice( index, 1 );
    }

    /**
     * Removes all the listeners
     * @public
     */
    removeAllListeners() {
      while ( this.listeners.length > 0 ) {
        this.removeListener( this.listeners[ 0 ] );
      }
    }

    /**
     * If addListener/removeListener is called while emit() is in progress, we must make a defensive copy of the array
     * of listeners before changing the array, and use it for the rest of the notifications until the emit call has
     * completed.
     * @private
     */
    defendListeners() {

      for ( let i = this.activeListenersStack.length - 1; i >= 0; i-- ) {

        // Once we meet a level that was already defended, we can stop, since all previous levels are also defended
        if ( this.activeListenersStack[ i ].defended ) {
          break;
        }
        else {
          const defendedListeners = this.listeners.slice();

          // Mark copies as 'defended' so that it will use the original listeners when emit started and not the modified
          // list.
          defendedListeners.defended = true;
          this.activeListenersStack[ i ] = defendedListeners;
        }
      }
    }

    /**
     * Emits a single event.  This method is called many times in a simulation and must be well-optimized.  Listeners
     * are notified in the order they were added via addListener.
     * @params - expected parameters are based on options.argumentTypes, see constructor
     * @public
     */
    emit() {

      // validate the args
      this.validate && this.validate.apply( this, arguments );

      // handle phet-io data stream for the emitted event
      if ( this.isPhetioInstrumented() ) {

        // null if there are no arguments.  dataStream.js omits null values for data
        let data = null;
        if ( this.phetioType.elements.length > 0 ) {

          // Enumerate named argsObject for the data stream.
          data = {};
          for ( let i = 0; i < this.phetioType.elements.length; i++ ) {
            const element = this.phetioType.elements[ i ];
            data[ element.name ] = element.type.toStateObject( arguments[ i ] );
          }
        }

        this.phetioStartEvent( 'emitted', data );
      }

      // Notify wired-up listeners, if any
      if ( this.listeners.length > 0 ) {

        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        // Notify listeners
        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ].apply( null, arguments );
        }

        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();
    }

    /**
     * Emits a single event with one argument.  This is a copy-paste of emit() for performance reasons.
     * @param {*} arg0
     * @public
     * @deprecated - please use emit()
     */
    emit1( arg0 ) {

      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', {
        args: [ this.phetioType.parameterTypes[ 0 ].toStateObject( arg0 ) ]
      } );

      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ]( arg0 );
        }

        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();
    }

    /**
     * Emits a single event with two arguments.  This is a copy-paste of emit() for performance reasons.
     * @param {*} arg0
     * @param {*} arg1
     * @public
     * @deprecated - please use emit()
     */
    emit2( arg0, arg1 ) {
      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', {
        args: [
          this.phetioType.parameterTypes[ 0 ].toStateObject( arg0 ),
          this.phetioType.parameterTypes[ 1 ].toStateObject( arg1 )
        ]
      } );

      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ]( arg0, arg1 );
        }
        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();
    }

    /**
     * Emits a single event with three arguments.  This is a copy-paste of emit() for performance reasons.
     * @param {*} arg0
     * @param {*} arg1
     * @param {*} arg2
     * @public
     * @deprecated - please use emit()
     */
    emit3( arg0, arg1, arg2 ) {
      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', {
        args: [
          this.phetioType.parameterTypes[ 0 ].toStateObject( arg0 ),
          this.phetioType.parameterTypes[ 1 ].toStateObject( arg1 ),
          this.phetioType.parameterTypes[ 2 ].toStateObject( arg2 )
        ]
      } );

      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ]( arg0, arg1, arg2 );
        }

        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();
    }

    /**
     * Checks whether a listener is registered with this Emitter
     * @param {function} listener
     * @returns {boolean}
     * @public
     */
    hasListener( listener ) {
      assert && assert( arguments.length === 1, 'Emitter.hasListener should be called with 1 argument' );
      return this.listeners.indexOf( listener ) >= 0;
    }

    /**
     * Returns true if there are any listeners.
     * @returns {boolean}
     * @public
     */
    hasListeners() {
      assert && assert( arguments.length === 0, 'Emitter.hasListeners should be called without arguments' );
      return this.listeners.length > 0;
    }

    /**
     * Returns the number of listeners.
     * @returns {number}
     * @public
     */
    getListenerCount() {
      return this.listeners.length;
    }
  }

  return axon.register( 'Emitter', Emitter );
} );<commit_msg>Add Emitter.before, after and replace usages of Emitter.listener with before, see https://github.com/phetsims/scenery/issues/928<commit_after>// Copyright 2015-2018, University of Colorado Boulder

/**
 * Lightweight event & listener abstraction for a single event type.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const axon = require( 'AXON/axon' );
  const EmitterIO = require( 'AXON/EmitterIO' );
  const PhetioObject = require( 'TANDEM/PhetioObject' );
  const Tandem = require( 'TANDEM/Tandem' );
  const Validator = require( 'AXON/Validator' );

  // constants
  const EmitterIOWithNoArgs = EmitterIO( [] );

  // Simulations have thousands of Emitters, so we re-use objects where possible.
  const EMPTY_ARRAY = [];
  assert && Object.freeze( EMPTY_ARRAY );

  /**
   * @param {Object} [options]
   */
  class Emitter extends PhetioObject {
    constructor( options ) {

      options = _.extend( {

        // {Array.<Object>|null} - array of "Validator Options" Objects that hold options for how to validate each
        // argument, see Validator.js for details.
        argumentTypes: EMPTY_ARRAY,

        tandem: Tandem.optional,
        phetioState: false,
        phetioType: EmitterIOWithNoArgs, // subtypes can override with EmitterIO([...]), see EmitterIO.js
        before: null, // {function} [before] optional function to call before listeners
        after: null // {function} [before] optional function to call after listeners
      }, options );

      assert && assert( !options.hasOwnProperty( 'listener' ), 'listener option no longer supported, please use before' );

      // phetioPlayback events need to know the order the arguments occur in order to call EmitterIO.emit()
      // Indicate whether the event is for playback, but leave this "sparse"--only indicate when this happens to be true
      if ( options.phetioPlayback ) {
        options.phetioEventMetadata = options.phetioEventMetadata || {};
        assert && assert( !options.phetioEventMetadata.hasOwnProperty( 'dataKeys' ), 'dataKeys should be supplied by Emitter, not elsewhere' );
        options.phetioEventMetadata.dataKeys = options.phetioType.elements.map( element => element.name );
      }

      super( options );

      Validator.validate( options.argumentTypes, { valueType: Array } );

      if ( assert ) {

        // Iterate through all argumentType validator options and make sure that they won't validate options on validating value
        options.argumentTypes.forEach( validatorOptions => {
          assert && assert(
            validatorOptions.validateOptionsOnValidateValue === undefined,
            'emitter sets its own validateOptionsOnValidateValue for each argument type'
          );
          validatorOptions.validateOptionsOnValidateValue = false;

          // Changing the validator options after construction indicates a logic error
          assert && Object.freeze( validatorOptions );

          // validate the options passed in to validate each emitter argument
          Validator.validateOptions( validatorOptions );
        } );

        // Changing after construction indicates a logic error
        assert && Object.freeze( options.argumentTypes );
      }

      // @private {function|false}
      this.validate = assert && function() {
        assert(
          arguments.length === options.argumentTypes.length,
          `Emitted unexpected number of args. Expected: ${options.argumentTypes.length} and received ${arguments.length}`
        );
        for ( let i = 0; i < options.argumentTypes.length; i++ ) {
          Validator.validate( arguments[ i ], options.argumentTypes[ i ] );
        }
      };

      // @private {function[]} - the listeners that will be called on emit
      this.listeners = [];

      // @private {function[][]} - during emit() keep track of which listeners should receive events in order to manage
      //                         - removal of listeners during emit()
      this.activeListenersStack = [];

      // @private {function|undefined} if defined, called before listeners are notified
      this.before = options.before;

      // @private {function|undefined} if defined, called after listeners are notified
      this.after = options.after;
    }

    /**
     * Dispose an Emitter that is no longer used.  Like Property.dispose, this method checks that there are no leaked
     * listeners.
     */
    dispose() {

      // See https://github.com/phetsims/axon/issues/124
      this.listeners.length = 0;

      PhetioObject.prototype.dispose.call( this );
    }

    /**
     * Adds a listener.  When emitting, the listeners are called in the order they were added.
     * @param {function} listener
     * @public
     */
    addListener( listener ) {

      assert && assert( this.listeners.indexOf( listener ) === -1, 'Cannot add the same listener twice' );

      // If a listener is added during an emit(), we must make a copy of the current list of listeners--the newly added
      // listener will be available for the next emit() but not the one in progress.  This is to match behavior with
      // removeListener.
      this.defendListeners();

      this.listeners.push( listener );
    }

    /**
     * Removes a listener
     * @param {function} listener
     * @public
     */
    removeListener( listener ) {

      const index = this.listeners.indexOf( listener );

      // Throw an error when removing a non-listener (except when the Emitter has already been disposed, see
      // https://github.com/phetsims/sun/issues/394#issuecomment-419998231
      if ( assert && !this.isDisposed ) {
        assert && assert( index >= 0, 'tried to removeListener on something that wasn\'t a listener' );
      }

      // If an emit is in progress, make a copy of the current list of listeners--the removed listener will remain in
      // the list and be called for this emit call, see #72
      this.defendListeners();

      this.listeners.splice( index, 1 );
    }

    /**
     * Removes all the listeners
     * @public
     */
    removeAllListeners() {
      while ( this.listeners.length > 0 ) {
        this.removeListener( this.listeners[ 0 ] );
      }
    }

    /**
     * If addListener/removeListener is called while emit() is in progress, we must make a defensive copy of the array
     * of listeners before changing the array, and use it for the rest of the notifications until the emit call has
     * completed.
     * @private
     */
    defendListeners() {

      for ( let i = this.activeListenersStack.length - 1; i >= 0; i-- ) {

        // Once we meet a level that was already defended, we can stop, since all previous levels are also defended
        if ( this.activeListenersStack[ i ].defended ) {
          break;
        }
        else {
          const defendedListeners = this.listeners.slice();

          // Mark copies as 'defended' so that it will use the original listeners when emit started and not the modified
          // list.
          defendedListeners.defended = true;
          this.activeListenersStack[ i ] = defendedListeners;
        }
      }
    }

    /**
     * Emits a single event.  This method is called many times in a simulation and must be well-optimized.  Listeners
     * are notified in the order they were added via addListener.
     * @params - expected parameters are based on options.argumentTypes, see constructor
     * @public
     */
    emit() {

      // validate the args
      this.validate && this.validate.apply( this, arguments );

      this.before && this.before.apply( null, arguments );

      // handle phet-io data stream for the emitted event
      if ( this.isPhetioInstrumented() ) {

        // null if there are no arguments.  dataStream.js omits null values for data
        let data = null;
        if ( this.phetioType.elements.length > 0 ) {

          // Enumerate named argsObject for the data stream.
          data = {};
          for ( let i = 0; i < this.phetioType.elements.length; i++ ) {
            const element = this.phetioType.elements[ i ];
            data[ element.name ] = element.type.toStateObject( arguments[ i ] );
          }
        }

        this.phetioStartEvent( 'emitted', data );
      }

      // Notify wired-up listeners, if any
      if ( this.listeners.length > 0 ) {

        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        // Notify listeners
        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ].apply( null, arguments );
        }

        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();

      this.after && this.after.apply( null, arguments );
    }

    /**
     * Emits a single event with one argument.  This is a copy-paste of emit() for performance reasons.
     * @param {*} arg0
     * @public
     * @deprecated - please use emit()
     */
    emit1( arg0 ) {

      this.before && this.before( arg0 );

      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', {
        args: [ this.phetioType.parameterTypes[ 0 ].toStateObject( arg0 ) ]
      } );

      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ]( arg0 );
        }

        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();

      this.after && this.after( arg0 );
    }

    /**
     * Emits a single event with two arguments.  This is a copy-paste of emit() for performance reasons.
     * @param {*} arg0
     * @param {*} arg1
     * @public
     * @deprecated - please use emit()
     */
    emit2( arg0, arg1 ) {
      this.before && this.before( arg0, arg1 );
      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', {
        args: [
          this.phetioType.parameterTypes[ 0 ].toStateObject( arg0 ),
          this.phetioType.parameterTypes[ 1 ].toStateObject( arg1 )
        ]
      } );

      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ]( arg0, arg1 );
        }
        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();
      this.after && this.after( arg0, arg1 );
    }

    /**
     * Emits a single event with three arguments.  This is a copy-paste of emit() for performance reasons.
     * @param {*} arg0
     * @param {*} arg1
     * @param {*} arg2
     * @public
     * @deprecated - please use emit()
     */
    emit3( arg0, arg1, arg2 ) {
      this.before && this.before( arg0, arg1, arg2 );
      this.isPhetioInstrumented() && this.phetioStartEvent( 'emitted', {
        args: [
          this.phetioType.parameterTypes[ 0 ].toStateObject( arg0 ),
          this.phetioType.parameterTypes[ 1 ].toStateObject( arg1 ),
          this.phetioType.parameterTypes[ 2 ].toStateObject( arg2 )
        ]
      } );

      if ( this.listeners.length > 0 ) {
        this.activeListenersStack.push( this.listeners );
        const lastEntry = this.activeListenersStack.length - 1;

        for ( let i = 0; i < this.activeListenersStack[ lastEntry ].length; i++ ) {
          this.activeListenersStack[ lastEntry ][ i ]( arg0, arg1, arg2 );
        }

        this.activeListenersStack.pop();
      }
      this.isPhetioInstrumented() && this.phetioEndEvent();
      this.after && this.after( arg0, arg1, arg2 );
    }

    /**
     * Checks whether a listener is registered with this Emitter
     * @param {function} listener
     * @returns {boolean}
     * @public
     */
    hasListener( listener ) {
      assert && assert( arguments.length === 1, 'Emitter.hasListener should be called with 1 argument' );
      return this.listeners.indexOf( listener ) >= 0;
    }

    /**
     * Returns true if there are any listeners.
     * @returns {boolean}
     * @public
     */
    hasListeners() {
      assert && assert( arguments.length === 0, 'Emitter.hasListeners should be called without arguments' );
      return this.listeners.length > 0;
    }

    /**
     * Returns the number of listeners.
     * @returns {number}
     * @public
     */
    getListenerCount() {
      return this.listeners.length;
    }
  }

  return axon.register( 'Emitter', Emitter );
} );<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Horizontal slider.
 * Optionally snaps to min when released.
 *
 * Moved from beers-law-lab/EvaporationSlider on 9/15/2013
 * see https://github.com/phetsims/sun/issues/9
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // imports
  var Dimension2 = require( 'DOT/Dimension2' );
  var FillHighlightListener = require( 'SCENERY_PHET/input/FillHighlightListener' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LinearFunction = require( 'DOT/LinearFunction' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );

  /**
   * @param {Property<Number>} value
   * @param {Range} range
   * @param {Property<Boolean>} enabled
   * @param {*} options
   * @constructor
   */
  function HSlider( value, range, enabled, options ) {

    var thisSlider = this;
    Node.call( thisSlider );

    // default options, these will not be passed to supertype
    var defaultOptions = {
      trackSize: new Dimension2( 100, 5 ),
      thumbSize: new Dimension2( 22, 45 ),
      thumbFillEnabled: 'rgb(50,145,184)',
      thumbFillHighlighted: 'rgb(71,207,255)',
      thumbFillDisabled: '#F0F0F0',
      majorTickLength: 30,
      minorTickLength: 16,
      endDrag: function() { /* do nothing */ } // called when thumb is released at end of drag sequence
    };

    // fill in options with defaults
    thisSlider._options = _.extend( defaultOptions, options );

    // ticks are added to this parent, so they are behind knob
    thisSlider._ticksParent = new Node();
    thisSlider.addChild( thisSlider._ticksParent );

    // track
    thisSlider._track = new Rectangle( 0, 0, thisSlider._options.trackSize.width, thisSlider._options.trackSize.height, { fill: 'white', stroke: 'black', lineWidth: 1 } );
    thisSlider.addChild( thisSlider._track );

    // thumb, points up
    var arcWidth = 0.25 * this._options.thumbSize.width;
    var thumbFill = enabled.get() ? thisSlider._options.thumbFillEnabled : thisSlider._options.thumbFillDisabled;
    var thumb = new Rectangle( -thisSlider._options.thumbSize.width / 2, -thisSlider._options.thumbSize.height / 2, thisSlider._options.thumbSize.width, thisSlider._options.thumbSize.height, arcWidth, arcWidth,
      { cursor: 'pointer', fill: thumbFill, stroke: 'black', lineWidth: 1 } );
    var centerLineYMargin = 3;
    thumb.addChild( new Path( Shape.lineSegment( 0, -( thisSlider._options.thumbSize.height / 2 ) + centerLineYMargin, 0, ( thisSlider._options.thumbSize.height / 2 ) - centerLineYMargin ), { stroke: 'white' } ) );
    thumb.centerY = thisSlider._track.centerY;
    thisSlider.addChild( thumb );

    // thumb touch area
    var dx = 0.5 * thumb.width;
    var dy = 0.25 * thumb.height;
    thumb.touchArea = Shape.rectangle( ( -thumb.width / 2 ) - dx, ( -thumb.height / 2 ) - dy, thumb.width + dx + dx, thumb.height + dy + dy );

    // mapping between value and track position
    thisSlider._valueToPosition = new LinearFunction( range.min, range.max, 0, this._options.trackSize.width, true /* clamp */ );

    // highlight on mouse enter
    thumb.addInputListener( new FillHighlightListener( thisSlider._options.thumbFillEnabled, thisSlider._options.thumbFillHighlighted, enabled ) );

    // update value when thumb is dragged
    var clickXOffset = 0; // x-offset between initial click and thumb's origin
    var dragHandler = new SimpleDragHandler( {
      allowTouchSnag: true,
      start: function( event ) {
        clickXOffset = thumb.globalToParentPoint( event.pointer.point ).x - thumb.x;
      },
      drag: function( event ) {
        if ( enabled.get() ) {
          var x = thumb.globalToParentPoint( event.pointer.point ).x - clickXOffset;
          value.set( thisSlider._valueToPosition.inverse( x ) );
        }
      },
      end: function() {
        thisSlider._options.endDrag();
      },
      translate: function() { /* override default behavior, do nothing */ }
    } );
    thumb.addInputListener( dragHandler );

    // enable/disable thumb
    enabled.link( function( enabled ) {
      thumb.fill = enabled ? thisSlider._options.thumbFillEnabled : thisSlider._options.thumbFillDisabled;
      thumb.cursor = enabled ? 'pointer' : 'default';
      if ( !enabled && dragHandler.dragging ) {
        dragHandler.endDrag();
      }
    } );

    // update thumb location when value changes
    value.link( function( value ) {
      thumb.centerX = thisSlider._valueToPosition( value );
    } );

    thisSlider.mutate( _.omit( thisSlider._options, Object.keys( defaultOptions ) ) );
  }

  inherit( Node, HSlider, {

    /**
     * Adds a major tick mark.
     * @param {Number} value
     * @param {Node} label optional
     */
    addMajorTick: function( value, label ) {
      this._addTick( this._options.majorTickLength, value, label );
    },

    /**
     * Adds a minor tick mark.
     * @param {Number} value
     * @param {Node} label optional
     */
    addMinorTick: function( value, label ) {
      this._addTick( this._options.majorTickLength, value, label );
    },

    /*
     * Adds a tick mark above the track.
     * @param {Number} tickLength
     * @param {Number} value
     * @param {Node} label optional
     */
    _addTick: function( tickLength, value, label ) {
      var labelX = this._valueToPosition( value );
      // ticks
      var tick = new Path( new Shape()
        .moveTo( labelX, this._track.top )
        .lineTo( labelX, this._track.bottom - tickLength ),
        { lineWidth: 1, stroke: 'black' } );
      this._ticksParent.addChild( tick );
      // label
      if ( label ) {
        this._ticksParent.addChild( label );
        label.centerX = tick.centerX;
        label.bottom = tick.top - 6;
      }
    }
  } );

  return HSlider;
} );<commit_msg>#9, add 'Property' suffix to parameters of type axon.Property<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Horizontal slider.
 * Optionally snaps to min when released.
 *
 * Moved from beers-law-lab/EvaporationSlider on 9/15/2013
 * see https://github.com/phetsims/sun/issues/9
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // imports
  var Dimension2 = require( 'DOT/Dimension2' );
  var FillHighlightListener = require( 'SCENERY_PHET/input/FillHighlightListener' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LinearFunction = require( 'DOT/LinearFunction' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );

  /**
   * @param {Property<Number>} valueProperty
   * @param {Range} range
   * @param {Property<Boolean>} enabledProperty
   * @param {*} options
   * @constructor
   */
  function HSlider( valueProperty, range, enabledProperty, options ) {

    var thisSlider = this;
    Node.call( thisSlider );

    // default options, these will not be passed to supertype
    var defaultOptions = {
      trackSize: new Dimension2( 100, 5 ),
      thumbSize: new Dimension2( 22, 45 ),
      thumbFillEnabled: 'rgb(50,145,184)',
      thumbFillHighlighted: 'rgb(71,207,255)',
      thumbFillDisabled: '#F0F0F0',
      majorTickLength: 30,
      minorTickLength: 16,
      endDrag: function() { /* do nothing */ } // called when thumb is released at end of drag sequence
    };

    // fill in options with defaults
    thisSlider._options = _.extend( defaultOptions, options );

    // ticks are added to this parent, so they are behind knob
    thisSlider._ticksParent = new Node();
    thisSlider.addChild( thisSlider._ticksParent );

    // track
    thisSlider._track = new Rectangle( 0, 0, thisSlider._options.trackSize.width, thisSlider._options.trackSize.height, { fill: 'white', stroke: 'black', lineWidth: 1 } );
    thisSlider.addChild( thisSlider._track );

    // thumb, points up
    var arcWidth = 0.25 * this._options.thumbSize.width;
    var thumbFill = enabledProperty.get() ? thisSlider._options.thumbFillEnabled : thisSlider._options.thumbFillDisabled;
    var thumb = new Rectangle( -thisSlider._options.thumbSize.width / 2, -thisSlider._options.thumbSize.height / 2, thisSlider._options.thumbSize.width, thisSlider._options.thumbSize.height, arcWidth, arcWidth,
      { cursor: 'pointer', fill: thumbFill, stroke: 'black', lineWidth: 1 } );
    var centerLineYMargin = 3;
    thumb.addChild( new Path( Shape.lineSegment( 0, -( thisSlider._options.thumbSize.height / 2 ) + centerLineYMargin, 0, ( thisSlider._options.thumbSize.height / 2 ) - centerLineYMargin ), { stroke: 'white' } ) );
    thumb.centerY = thisSlider._track.centerY;
    thisSlider.addChild( thumb );

    // thumb touch area
    var dx = 0.5 * thumb.width;
    var dy = 0.25 * thumb.height;
    thumb.touchArea = Shape.rectangle( ( -thumb.width / 2 ) - dx, ( -thumb.height / 2 ) - dy, thumb.width + dx + dx, thumb.height + dy + dy );

    // mapping between value and track position
    thisSlider._valueToPosition = new LinearFunction( range.min, range.max, 0, this._options.trackSize.width, true /* clamp */ );

    // highlight on mouse enter
    thumb.addInputListener( new FillHighlightListener( thisSlider._options.thumbFillEnabled, thisSlider._options.thumbFillHighlighted, enabledProperty ) );

    // update value when thumb is dragged
    var clickXOffset = 0; // x-offset between initial click and thumb's origin
    var dragHandler = new SimpleDragHandler( {
      allowTouchSnag: true,
      start: function( event ) {
        clickXOffset = thumb.globalToParentPoint( event.pointer.point ).x - thumb.x;
      },
      drag: function( event ) {
        if ( enabledProperty.get() ) {
          var x = thumb.globalToParentPoint( event.pointer.point ).x - clickXOffset;
          valueProperty.set( thisSlider._valueToPosition.inverse( x ) );
        }
      },
      end: function() {
        thisSlider._options.endDrag();
      },
      translate: function() { /* override default behavior, do nothing */ }
    } );
    thumb.addInputListener( dragHandler );

    // enable/disable thumb
    enabledProperty.link( function( enabled ) {
      thumb.fill = enabled ? thisSlider._options.thumbFillEnabled : thisSlider._options.thumbFillDisabled;
      thumb.cursor = enabled ? 'pointer' : 'default';
      if ( !enabled && dragHandler.dragging ) {
        dragHandler.endDrag();
      }
    } );

    // update thumb location when value changes
    valueProperty.link( function( value ) {
      thumb.centerX = thisSlider._valueToPosition( value );
    } );

    thisSlider.mutate( _.omit( thisSlider._options, Object.keys( defaultOptions ) ) );
  }

  inherit( Node, HSlider, {

    /**
     * Adds a major tick mark.
     * @param {Number} value
     * @param {Node} label optional
     */
    addMajorTick: function( value, label ) {
      this._addTick( this._options.majorTickLength, value, label );
    },

    /**
     * Adds a minor tick mark.
     * @param {Number} value
     * @param {Node} label optional
     */
    addMinorTick: function( value, label ) {
      this._addTick( this._options.majorTickLength, value, label );
    },

    /*
     * Adds a tick mark above the track.
     * @param {Number} tickLength
     * @param {Number} value
     * @param {Node} label optional
     */
    _addTick: function( tickLength, value, label ) {
      var labelX = this._valueToPosition( value );
      // ticks
      var tick = new Path( new Shape()
        .moveTo( labelX, this._track.top )
        .lineTo( labelX, this._track.bottom - tickLength ),
        { lineWidth: 1, stroke: 'black' } );
      this._ticksParent.addChild( tick );
      // label
      if ( label ) {
        this._ticksParent.addChild( label );
        label.centerX = tick.centerX;
        label.bottom = tick.top - 6;
      }
    }
  } );

  return HSlider;
} );<|endoftext|>


<commit_before>/*
* indices page implementation
*/
var Indices = new function(){

	var section;

	/*
	* initialize indices page; load facets
	*/
	this.initialize = function(){

		var gui = this;

		this.selectionContainer = $("#indicesSelection");
		$('
'+Util.getString('indexSelection')+'
').appendTo(this.selectionContainer);
		$('
'+Util.getString('pleaseSelectIndex')+'
').appendTo(this.selectionContainer);

		var form = $('<form/>').appendTo(this.selectionContainer);
		var fieldset = $('<fieldset/>').appendTo(form);
		this.selectionDropdown = $('<select class="selectIndices"/>').appendTo(fieldset);
		$(this.selectionDropdown).change(function(){
			$("select option:selected",this.selectionDropdown).each(function(){
				if( $(this).attr('id').indexOf('tei') != -1 ){
					gui.showFacetSection($(this).attr('id'));
				}
			});
		});

		$('<option>'+Util.getString('selectIndex')+'</option>').appendTo(this.selectionDropdown);

		this.sectionContainer = $("#sectionContainer");

		var loadFacets = function(){
			if( Util.facetsLoaded ){
				for( var i=0; i<Util.facets.length; i++ ){
					var facet = Util.facets[i];
					if( facet.render ){
						$('<option id="'+facet.facet+'">'+Util.getFacetLabel(facet)+'</option>').appendTo(gui.selectionDropdown);
					}
				}
			}
			else {
				setTimeout(function(){
					loadFacets();
				}, 1000 );
			}
		}
		loadFacets();

	};

	/*
	* show table and tags/map for facet with <facetId>
	*/
	this.showFacetSection = function(facetId){
		if( facetId == Util.getString('selectIndex') ){
			return;
		}
		var status;
		var appendStatus = function(msg,server){
			status = $('
'+msg+'
').appendTo(section);
			$(status).css('margin-top','20px');
			var img = $('').appendTo(status);
			$(img).css('padding','0px');
			$(img).css('box-shadow','none');
			$(img).css('margin-left','10px');
			if( server ){
				$("
"+Util.getString('busyServer')+"
").appendTo(status);
			}
		};
		$(this.sectionContainer).empty();
		var facet = Util.getFacet(facetId);
		section = $("
").appendTo(this.sectionContainer);
		$('
'+Util.getFacetLabel(facet)+'
').appendTo(section);
		var tableLoaded = false, cloudLoaded = false;
		appendStatus(Util.getString('loadTable'));
		setTimeout(function(){
			if( !tableLoaded ){
				$(status).remove();
				appendStatus(Util.getString('loadTable'),true);
			}
		}, IndicesProps.serverTimeout );
		$.ajax({
			url: EditionProperties.facetTableQuery.replace('FACET_ID',facet.facet),
			dataType: 'html',
			success: function(xhtml){
				$(status).remove();
				tableLoaded = true;
				$(this.sectionContainer).empty();
				var dummy = $('
');
				$(xhtml).appendTo(dummy);
				var table = $($('table',dummy)[0]).appendTo(section);
				$(table).attr('width','100%');
				$(table).attr('id','tableIncices');
				$('span',section).each(function(){
					var lang = Util.getAttribute(this,'xml:lang');
					if( Util.language != lang ){
						$(this).css('display','none');
					}
				});
				$('.editionRef',section).each(function(){
					var params = Util.getAttribute(this,'rel');
					params = params.split(';');
					var linkString = 'http://'+location.host+'/archaeo18/edition.php?docParams='+params;
					$(this).click(function(e){
						showDiv('#edition_page','#linkedition',e);
						EditionGui.gridLayout();
						var doc = Util.loadDocumentSync(params[0]);
						var page = parseInt(params[1]);
						EditionGui.openDocument(false,doc,page,"pages",undefined,facet.facet);
					});
				});
				$(table).dataTable({
					"oLanguage": {
						"sLengthMenu": "Zeige _MENU_ Einträge je Seite",
						"sZeroRecords": "Keine Einträge gefunden",
						"sInfo": "Zeige _START_ bis _END_ von _TOTAL_ Einträgen",
						"sInfoEmpty": "Zeige 0 bis 0 von 0 Einträgen",
						"sInfoFiltered": "(gefiltert aus _MAX_ Einträgen insgesamt)",
						"sSearch": "Suche:",
						"oPaginate": {
							"sNext": "Nächste Seite",
							"sPrevious": "Vorherige Seite",
						}
					}
				});
				var inputField = $('#tableIncices_filter :input')[0];
				if( facet.facet.indexOf('placeName') == -1 ){
					appendStatus(Util.getString('loadTagcloud'));
					setTimeout(function(){
						if( !cloudLoaded ){
							$(status).remove();
							appendStatus(Util.getString('loadTagcloud'),true);
						}
					}, IndicesProps.serverTimeout );
					$.ajax({
						url: EditionProperties.tagcloudQuery.replace('FACET_ID',facet.facet),
						dataType: 'xml',
						success: function(xml){
							cloudLoaded = true;
							if( $("#indices_page").css('display') == 'none' ){
								Indices.data = {
									type: 'cloud',
									data: xml
								};
							}
							else {
								Indices.displayCloud();
							}
							$(status).remove();
						}
					});
				}
				else {
					appendStatus(Util.getString('loadMap'));
					setTimeout(function(){
						if( !cloudLoaded ){
							$(status).remove();
							appendStatus(Util.getString('loadMap'),true);
						}
					}, IndicesProps.serverTimeout );
					$.ajax({
						url: EditionProperties.mapQuery.replace('FACET_ID',facet.facet),
						dataType: 'xml',
						success: function(kml){
							cloudLoaded = true;
							if( $("#indices_page").css('display') == 'none' ){
								Indices.data = {
									type: 'map',
									data: kml
								};
							}
							else {
								Indices.displayMap();
							}
							$(status).remove();
						}
					});
				}
			}
		});
	};

	this.displayCloud = function(xml){
		var tagArray = Util.getTags($(xml).find('tag'));
		var tagsDiv = $("
").appendTo(section);
		$(tagsDiv).css('height','200px');
		$(tagsDiv).css('margin-top','50px');
		$(tagsDiv).css('margin-bottom','50px');
		$(tagsDiv).jQCloud(tagArray);
		var clicked = false;
		$(tagsDiv).mouseenter(function(){
			if( !clicked ){
				clicked = true;
				$('.tagcloudTag',tagsDiv).each(function(){
					$(this).removeAttr("href");
					$(this).css('cursor','pointer');
					$(this).click(function(evt){
						$(inputField).val($(this).html());
						$(inputField).trigger('keyup');
					});
				});
			}
		});
	};

	this.displayMap = function(kml){
		var mapDiv = $("
").appendTo(section);
		$(mapDiv).css('position','relative');
		$(mapDiv).css('height','400px');
		$(mapDiv).css('margin-top','50px');
		$(mapDiv).css('margin-bottom','50px');
		var loadMap = function(){
			if( typeof GeoTemConfig == 'undefined' ){
				setTimeout( function(){ loadMap(); }, 1000 );
			}
			var map = new WidgetWrapper();
			var mapWidget = new MapWidget(map,$(mapDiv)[0],{
				mapWidth: false,
				mapHeight: false,
				mapSelection: false,
				mapSelectionTools: false,
				dataInformation: false,
				mapCanvasFrom: '#DDD',
				mapCanvasTo: '#DDD',
				maxPlaceLabels: 8
			});
			map.display([new Dataset(GeoTemConfig.loadKml(kml))]);
		}
		loadMap();
	};

	this.checkDisplay = function(){
		if( typeof this.data != 'undefined' ){
			if( this.data.type == 'cloud' ){
				this.displayCloud(this.data.data);
			}
			if( this.data.type == 'map' ){
				this.displayMap(this.data.data);
			}
			this.data = undefined;
		}
	};

};

IndicesProps = {
	serverTimeout: 10000,	//in ms
};

Indices.initialize();
<commit_msg>the map is back!<commit_after>/*
* indices page implementation
*/
var Indices = new function(){

	var section;

	/*
	* initialize indices page; load facets
	*/
	this.initialize = function(){

		var gui = this;

		this.selectionContainer = $("#indicesSelection");
		$('
'+Util.getString('indexSelection')+'
').appendTo(this.selectionContainer);
		$('
'+Util.getString('pleaseSelectIndex')+'
').appendTo(this.selectionContainer);

		var form = $('<form/>').appendTo(this.selectionContainer);
		var fieldset = $('<fieldset/>').appendTo(form);
		this.selectionDropdown = $('<select class="selectIndices"/>').appendTo(fieldset);
		$(this.selectionDropdown).change(function(){
			$("select option:selected",this.selectionDropdown).each(function(){
				if( $(this).attr('id').indexOf('tei') != -1 ){
					gui.showFacetSection($(this).attr('id'));
				}
			});
		});

		$('<option>'+Util.getString('selectIndex')+'</option>').appendTo(this.selectionDropdown);

		this.sectionContainer = $("#sectionContainer");

		var loadFacets = function(){
			if( Util.facetsLoaded ){
				for( var i=0; i<Util.facets.length; i++ ){
					var facet = Util.facets[i];
					if( facet.render ){
						$('<option id="'+facet.facet+'">'+Util.getFacetLabel(facet)+'</option>').appendTo(gui.selectionDropdown);
					}
				}
			}
			else {
				setTimeout(function(){
					loadFacets();
				}, 1000 );
			}
		}
		loadFacets();

	};

	/*
	* show table and tags/map for facet with <facetId>
	*/
	this.showFacetSection = function(facetId){
		if( facetId == Util.getString('selectIndex') ){
			return;
		}
		var status;
		var appendStatus = function(msg,server){
			status = $('
'+msg+'
').appendTo(section);
			$(status).css('margin-top','20px');
			var img = $('').appendTo(status);
			$(img).css('padding','0px');
			$(img).css('box-shadow','none');
			$(img).css('margin-left','10px');
			if( server ){
				$("
"+Util.getString('busyServer')+"
").appendTo(status);
			}
		};
		$(this.sectionContainer).empty();
		var facet = Util.getFacet(facetId);
		section = $("
").appendTo(this.sectionContainer);
		$('
'+Util.getFacetLabel(facet)+'
').appendTo(section);
		var tableLoaded = false, cloudLoaded = false;
		appendStatus(Util.getString('loadTable'));
		setTimeout(function(){
			if( !tableLoaded ){
				$(status).remove();
				appendStatus(Util.getString('loadTable'),true);
			}
		}, IndicesProps.serverTimeout );
		$.ajax({
			url: EditionProperties.facetTableQuery.replace('FACET_ID',facet.facet),
			dataType: 'html',
			success: function(xhtml){
				$(status).remove();
				tableLoaded = true;
				$(this.sectionContainer).empty();
				var dummy = $('
');
				$(xhtml).appendTo(dummy);
				var table = $($('table',dummy)[0]).appendTo(section);
				$(table).attr('width','100%');
				$(table).attr('id','tableIncices');
				$('span',section).each(function(){
					var lang = Util.getAttribute(this,'xml:lang');
					if( Util.language != lang ){
						$(this).css('display','none');
					}
				});
				$('.editionRef',section).each(function(){
					var params = Util.getAttribute(this,'rel');
					params = params.split(';');
					var linkString = 'http://'+location.host+'/archaeo18/edition.php?docParams='+params;
					$(this).click(function(e){
						showDiv('#edition_page','#linkedition',e);
						EditionGui.gridLayout();
						var doc = Util.loadDocumentSync(params[0]);
						var page = parseInt(params[1]);
						EditionGui.openDocument(false,doc,page,"pages",undefined,facet.facet);
					});
				});
				$(table).dataTable({
					"oLanguage": {
						"sLengthMenu": "Zeige _MENU_ Einträge je Seite",
						"sZeroRecords": "Keine Einträge gefunden",
						"sInfo": "Zeige _START_ bis _END_ von _TOTAL_ Einträgen",
						"sInfoEmpty": "Zeige 0 bis 0 von 0 Einträgen",
						"sInfoFiltered": "(gefiltert aus _MAX_ Einträgen insgesamt)",
						"sSearch": "Suche:",
						"oPaginate": {
							"sNext": "Nächste Seite",
							"sPrevious": "Vorherige Seite",
						}
					}
				});
				var inputField = $('#tableIncices_filter :input')[0];
				if( facet.facet.indexOf('placeName') == -1 ){
					appendStatus(Util.getString('loadTagcloud'));
					setTimeout(function(){
						if( !cloudLoaded ){
							$(status).remove();
							appendStatus(Util.getString('loadTagcloud'),true);
						}
					}, IndicesProps.serverTimeout );
					$.ajax({
						url: EditionProperties.tagcloudQuery.replace('FACET_ID',facet.facet),
						dataType: 'xml',
						success: function(xml){
							cloudLoaded = true;
							if( $("#indices_page").css('display') == 'none' ){
								Indices.data = {
									type: 'cloud',
									data: xml
								};
							}
							else {
								Indices.displayCloud();
							}
							$(status).remove();
						}
					});
				}
				else {
					appendStatus(Util.getString('loadMap'));
					setTimeout(function(){
						if( !cloudLoaded ){
							$(status).remove();
							appendStatus(Util.getString('loadMap'),true);
						}
					}, IndicesProps.serverTimeout );
					$.ajax({
						url: EditionProperties.mapQuery.replace('FACET_ID',facet.facet),
						dataType: 'xml',
						success: function(kml){
							cloudLoaded = true;
							if( $("#indices_page").css('display') == 'none' ){
								Indices.data = {
									type: 'map',
									data: kml
								};
							}
							else {
								Indices.displayMap(kml);
							}
							$(status).remove();
						}
					});
				}
			}
		});
	};

	this.displayCloud = function(xml){
		var tagArray = Util.getTags($(xml).find('tag'));
		var tagsDiv = $("
").appendTo(section);
		$(tagsDiv).css('height','200px');
		$(tagsDiv).css('margin-top','50px');
		$(tagsDiv).css('margin-bottom','50px');
		$(tagsDiv).jQCloud(tagArray);
		var clicked = false;
		$(tagsDiv).mouseenter(function(){
			if( !clicked ){
				clicked = true;
				$('.tagcloudTag',tagsDiv).each(function(){
					$(this).removeAttr("href");
					$(this).css('cursor','pointer');
					$(this).click(function(evt){
						$(inputField).val($(this).html());
						$(inputField).trigger('keyup');
					});
				});
			}
		});
	};

	this.displayMap = function(kml){
		var mapDiv = $("
").appendTo(section);
		$(mapDiv).css('position','relative');
		$(mapDiv).css('height','400px');
		$(mapDiv).css('margin-top','50px');
		$(mapDiv).css('margin-bottom','50px');
		var loadMap = function(){
			if( typeof GeoTemConfig == 'undefined' ){
				setTimeout( function(){ loadMap(); }, 1000 );
			}
			var map = new WidgetWrapper();
			var mapWidget = new MapWidget(map,$(mapDiv)[0],{
				mapWidth: false,
				mapHeight: false,
				mapSelection: false,
				mapSelectionTools: false,
				dataInformation: false,
				mapCanvasFrom: '#DDD',
				mapCanvasTo: '#DDD',
				maxPlaceLabels: 8
			});
			map.display([new Dataset(GeoTemConfig.loadKml(kml))]);
		}
		loadMap();
	};

	this.checkDisplay = function(){
		if( typeof this.data != 'undefined' ){
			if( this.data.type == 'cloud' ){
				this.displayCloud(this.data.data);
			}
			if( this.data.type == 'map' ){
				this.displayMap(this.data.data);
			}
			this.data = undefined;
		}
	};

};

IndicesProps = {
	serverTimeout: 10000,	//in ms
};

Indices.initialize();
<|endoftext|>


<commit_before>/*/////////////////////////////////////////////////////////////////////////////////

 MO5.js - JavaScript Library For Building Modern DOM And Canvas Applications

 Copyright (c) 2013 Jonathan Steinbeck
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

 * Neither the name MO5.js nor the names of its contributors
   may be used to endorse or promote products derived from this software
   without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

/////////////////////////////////////////////////////////////////////////////////*/

/* global MO5, module, require, window */

(function MO5MapBootstrap () {

    if (typeof MO5 === "function") {
        MO5("MO5.CoreObject", "MO5.Exception").
        define("MO5.Map", MO5MapModule);
    }
    else if (typeof window !== "undefined") {
        window.MO5.Map = MO5MapModule(MO5.CoreObject, MO5.Exception);
    }
    else {
        module.exports = MO5MapModule(
            require("./MO5.CoreObject.js"),
            require("./MO5.Exception.js")
        );
    }

    function MO5MapModule (CoreObject, Exception) {

        var prefix = "MO5Map";

        function makeKey (k) {
            return prefix + k;
        }

        function revokeKey (k) {
            return k.replace(new RegExp(prefix), "");
        }

        function Map (content) {

            var key;

            CoreObject.call(this);

            this.clear();

            if (content) {
                for (key in content) {
                    this.set(key, content[key]);
                }
            }
        }

        Map.prototype = new CoreObject();

        Map.prototype.clear = function () {
            this.items = {};
            this.unsubscribers = {};
            this.count = 0;
        };

        Map.prototype.length = function () {
            return this.count;
        };

        Map.prototype.set = function (k, value) {

            var self = this, key = makeKey(k);

            function whenDestroyed () {
                delete self.items[key];
                self.count -= 1;
            }

            if (!k) {
                throw new Exception("MO5.Map keys cannot be falsy.");
            }

            if (this.has(key)) {
                this.remove(key);
            }

            if (value && value instanceof CoreObject) {

                if (value.destroyed) {
                    throw new Exception("Trying to add an MO5.Object that has " +
                        "already been destroyed.");
                }

                value.subscribe(whenDestroyed, "destroyed");
            }

            if (k instanceof CoreObject) {

                if (k.destroyed) {
                    throw new Exception("Trying to use an MO5.Object as key that " +
                        "has already been destroyed.");
                }

                k.subscribe(whenDestroyed, "destroyed");

            }

            if (value && value instanceof CoreObject || k instanceof CoreObject) {

                this.unsubscribers[key] = function () {

                    if (value instanceof CoreObject) {
                        value.unsubscribe(whenDestroyed, "destroyed");
                    }

                    if (k instanceof CoreObject) {
                        k.unsubscribe(whenDestroyed, "destroyed");
                    }
                };
            }

            this.items[key] = value;
            this.count += 1;

            this.trigger("updated", null, false);
            this.trigger("set", key, false);

            return this;
        };

        Map.prototype.get = function (k) {

            var key = makeKey(k);

            if (!this.items.hasOwnProperty(key)) {
                return undefined;
            }

            return this.items[key];
        };

        Map.prototype.remove = function (k) {

            var key = makeKey(k);

            if (!this.has(k)) {
                throw new Exception("Trying to remove an unknown key from an MO5.Map.");
            }

            if (this.unsubscribers.hasOwnProperty(key)) {
                this.unsubscribers[key]();
                delete this.unsubscribers[key];
            }

            delete this.items[key];
            this.count -= 1;

            this.trigger("updated", null, false);
            this.trigger("removed", key, false);

            return this;
        };

        Map.prototype.has = function (k) {

            var key = makeKey(k);

            return this.items.hasOwnProperty(key);
        };

        Map.prototype.destroy = function () {

            for (var key in this.unsubscribers) {
                this.unsubscribers[key]();
                delete this.unsubscribers[key];
            }

            CoreObject.prototype.destroy.call(this);
        };

        Map.prototype.forEach = function (fn) {

            if (!fn || typeof fn !== "function") {
                throw new Exception("Parameter 1 is expected to be of type function.");
            }

            for (var key in this.items) {
                fn(this.items[key], revokeKey(key), this);
            }

            return this;
        };

        Map.prototype.keys = function () {

            var keys = [];

            this.forEach(function (item, key) {
                keys.push(key);
            });

            return keys;
        };

        /**
         * Returns the map's values in an array.
         */
        Map.prototype.values = function () {

            var values = [];

            this.forEach(function (item) {
                values.push(item);
            });

            return values;
        };

        Map.prototype.toObject = function () {

            var jsObject = {};

            this.forEach(function (item, key) {
                jsObject[key] = item;
            });

            return jsObject;
        };

        Map.prototype.clone = function () {
            var clone = new Map();

            this.forEach(function (item, key) {
                clone.set(key, item);
            });

            return clone;
        };

        /**
         * Adds the content of another map to this map's content.
         * @param otherMap Another MO5.Map.
         */
        Map.prototype.addMap = function (otherMap) {

            var self = this;

            otherMap.forEach(function (item, key) {
                self.set(key, item);
            });

            return this;
        };

        /**
         * Returns a new map which is the result of joining this map
         * with another map. This map isn't changed in the process.
         * The keys from otherMap will replace any keys from this map that
         * are the same.
         * @param otherMap A map to join with this map.
         */
        Map.prototype.join = function (otherMap) {
            return this.clone().addMap(otherMap);
        };

        return Map;

    }

}());
<commit_msg>Added .filter() methods to MO5.List and MO5.Map.<commit_after>/*/////////////////////////////////////////////////////////////////////////////////

 MO5.js - JavaScript Library For Building Modern DOM And Canvas Applications

 Copyright (c) 2013 Jonathan Steinbeck
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

 * Neither the name MO5.js nor the names of its contributors
   may be used to endorse or promote products derived from this software
   without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

/////////////////////////////////////////////////////////////////////////////////*/

/* global MO5, module, require, window */

(function MO5MapBootstrap () {

    if (typeof MO5 === "function") {
        MO5("MO5.CoreObject", "MO5.Exception").
        define("MO5.Map", MO5MapModule);
    }
    else if (typeof window !== "undefined") {
        window.MO5.Map = MO5MapModule(MO5.CoreObject, MO5.Exception);
    }
    else {
        module.exports = MO5MapModule(
            require("./MO5.CoreObject.js"),
            require("./MO5.Exception.js")
        );
    }

    function MO5MapModule (CoreObject, Exception) {

        var prefix = "MO5Map";

        function makeKey (k) {
            return prefix + k;
        }

        function revokeKey (k) {
            return k.replace(new RegExp(prefix), "");
        }

        function Map (content) {

            var key;

            CoreObject.call(this);

            this.clear();

            if (content) {
                for (key in content) {
                    this.set(key, content[key]);
                }
            }
        }

        Map.prototype = new CoreObject();

        Map.prototype.clear = function () {
            this.items = {};
            this.unsubscribers = {};
            this.count = 0;
        };

        Map.prototype.length = function () {
            return this.count;
        };

        Map.prototype.set = function (k, value) {

            var self = this, key = makeKey(k);

            function whenDestroyed () {
                delete self.items[key];
                self.count -= 1;
            }

            if (!k) {
                throw new Exception("MO5.Map keys cannot be falsy.");
            }

            if (this.has(key)) {
                this.remove(key);
            }

            if (value && value instanceof CoreObject) {

                if (value.destroyed) {
                    throw new Exception("Trying to add an MO5.Object that has " +
                        "already been destroyed.");
                }

                value.subscribe(whenDestroyed, "destroyed");
            }

            if (k instanceof CoreObject) {

                if (k.destroyed) {
                    throw new Exception("Trying to use an MO5.Object as key that " +
                        "has already been destroyed.");
                }

                k.subscribe(whenDestroyed, "destroyed");

            }

            if (value && value instanceof CoreObject || k instanceof CoreObject) {

                this.unsubscribers[key] = function () {

                    if (value instanceof CoreObject) {
                        value.unsubscribe(whenDestroyed, "destroyed");
                    }

                    if (k instanceof CoreObject) {
                        k.unsubscribe(whenDestroyed, "destroyed");
                    }
                };
            }

            this.items[key] = value;
            this.count += 1;

            this.trigger("updated", null, false);
            this.trigger("set", key, false);

            return this;
        };

        Map.prototype.get = function (k) {

            var key = makeKey(k);

            if (!this.items.hasOwnProperty(key)) {
                return undefined;
            }

            return this.items[key];
        };

        Map.prototype.remove = function (k) {

            var key = makeKey(k);

            if (!this.has(k)) {
                throw new Exception("Trying to remove an unknown key from an MO5.Map.");
            }

            if (this.unsubscribers.hasOwnProperty(key)) {
                this.unsubscribers[key]();
                delete this.unsubscribers[key];
            }

            delete this.items[key];
            this.count -= 1;

            this.trigger("updated", null, false);
            this.trigger("removed", key, false);

            return this;
        };

        Map.prototype.has = function (k) {

            var key = makeKey(k);

            return this.items.hasOwnProperty(key);
        };

        Map.prototype.destroy = function () {

            for (var key in this.unsubscribers) {
                this.unsubscribers[key]();
                delete this.unsubscribers[key];
            }

            CoreObject.prototype.destroy.call(this);
        };

        Map.prototype.forEach = function (fn) {

            if (!fn || typeof fn !== "function") {
                throw new Exception("Parameter 1 is expected to be of type function.");
            }

            for (var key in this.items) {
                fn(this.items[key], revokeKey(key), this);
            }

            return this;
        };

        Map.prototype.filter = function (fn) {

            var matches = new Map();

            this.forEach(function (item, key, all) {
                if (fn(item, key, all)) {
                    matches.set(key, item);
                }
            });

            return matches;
        };

        Map.prototype.keys = function () {

            var keys = [];

            this.forEach(function (item, key) {
                keys.push(key);
            });

            return keys;
        };

        /**
         * Returns the map's values in an array.
         */
        Map.prototype.values = function () {

            var values = [];

            this.forEach(function (item) {
                values.push(item);
            });

            return values;
        };

        Map.prototype.toObject = function () {

            var jsObject = {};

            this.forEach(function (item, key) {
                jsObject[key] = item;
            });

            return jsObject;
        };

        Map.prototype.clone = function () {
            var clone = new Map();

            this.forEach(function (item, key) {
                clone.set(key, item);
            });

            return clone;
        };

        /**
         * Adds the content of another map to this map's content.
         * @param otherMap Another MO5.Map.
         */
        Map.prototype.addMap = function (otherMap) {

            var self = this;

            otherMap.forEach(function (item, key) {
                self.set(key, item);
            });

            return this;
        };

        /**
         * Returns a new map which is the result of joining this map
         * with another map. This map isn't changed in the process.
         * The keys from otherMap will replace any keys from this map that
         * are the same.
         * @param otherMap A map to join with this map.
         */
        Map.prototype.join = function (otherMap) {
            return this.clone().addMap(otherMap);
        };

        return Map;

    }

}());
<|endoftext|>