<commit_before>'use strict';
var log = require('./log');
module.exports = function (workers, options) {
  var DEBUG = options.debug;
  var CONCURRENCY = options.concurrency;
  var PORT = options.port;
  var serverInstance;

  function serverCreate () {
    var hash = require('string-hash');
    return require('net')
      .createServer({ pauseOnConnect: true }, function (connection) {
        var index = hash(connection.remoteAddress || '') % CONCURRENCY;
        workers.entrust(index, connection);
      });
  }

  function serverStart (callback) {
    serverInstance = serverCreate();
    serverInstance.listen(PORT, callback);
  }

  function serverStop (callback) {
    serverInstance.close(function (err) {
      if (err) console.log(err);
      else return callback();
    });
  }

  function stop () {

    // stop proxy server
    if (DEBUG) log('MASTER  stop..');
    serverStop(function () {
      if (DEBUG) log('MASTER  ..stopped');

      // stop nodes
      if (DEBUG) log('WORKERS  stop..');
      workers.stop();
    });
  }

  function start () {

    // start proxy server
    if (DEBUG) log('MASTER  start..');
    serverStart(function () {
      if (DEBUG) log('MASTER  ..started at port %d', PORT);

      // start nodes
      if (DEBUG) log('WORKERS  start..');
      workers.start();

      // stop everything when requested
      process.once('SIGINT', stop);
    });
  }

  return {
    start: start,
    stop: stop
  };
};
<commit_msg>indent<commit_after>'use strict';
var log = require('./log');
module.exports = function (workers, options) {
  var DEBUG = options.debug;
  var CONCURRENCY = options.concurrency;
  var PORT = options.port;
  var serverInstance;

  function serverCreate () {
    var hash = require('string-hash');
    return require('net')
      .createServer({ pauseOnConnect: true }, function (connection) {
        var index = hash(connection.remoteAddress || '') % CONCURRENCY;
        workers.entrust(index, connection);
      });
  }

  function serverStart (callback) {
    serverInstance = serverCreate();
    serverInstance.listen(PORT, callback);
  }

  function serverStop (callback) {
    serverInstance.close(function (err) {
      if (err) console.log(err);
      else return callback();
    });
  }

  function stop () {
    if (DEBUG) log('MASTER  stop..');
    serverStop(function () {
      if (DEBUG) {
        log('MASTER  ..stopped');
        log('WORKERS  stop..');
      }
      workers.stop();
    });
  }

  function start () {
    if (DEBUG) log('MASTER  start..');
    serverStart(function () {
      if (DEBUG) {
        log('MASTER  ..started at port %d', PORT);
        log('WORKERS  start..');
      }
      workers.start();
      process.once('SIGINT', stop);
    });
  }

  return {
    start: start,
    stop: stop
  };
};
<|endoftext|>


<commit_before>'use strict';

var bunyan = require('bunyan');
var cluster = require('cluster');
var path = require('path');
var os = require('os');
var Status = require('./status');

/**
 * Cluster master.
 *
 * Options:
 * - exec: the script to exit
 * - workers: worker count (default: host CPU count)
 * - args: arguments passed to workers (default: [])
 * - startupTimeout: how long the master waits for worker startup (default: 10000)
 * - shutdownTimeout: how long the master waits for worker shutdown (default: 30000)
 * - silent: suppress worker output
 * - log: bunyan instance for logging
 * - beforeReload: function that is executed before the master is reloaded (async, with callback)
 * - beforeShutdown: function that is executed before the master is shut down (async, with callback)
 *
 * @param {Object} opts
 * @constructor
 */
var Master = module.exports = function (opts) {
    var self = this;

    this.config = opts || {};
    if (!this.config.exec) throw new Error('Must define an "exec" script');
    this.config.workers = this.config.workers || os.cpus().length;
    this.config.startupTimeout = this.config.startupTimeout || 10000;
    this.config.shutdownTimeout = this.config.shutdownTimeout || 30000;

    if (opts.log) {
        this.log = opts.log.child({'component': 'master'});
    } else {
        this.log = bunyan.createLogger({name: 'flora-cluster', 'component': 'master'});
    }

    this.log.trace('initializing Master');

    this.state = 'initializing';
    this.isRunning = true;
    this.generation = 0;
    this.currentGenerationForked = false;
    this.inBeforeReload = false;

    this.status = new Status();
    this.status.set('state', null);
    this.status.set('startTime', new Date());
    this.status.set('pid', process.pid);
    this.status.set('generation', null);
    this.status.onStatus(function () {
        this.set('state', self.state);
        this.set('generation', self.generation);
    });

    this.serverStatusTimeout = null;
    this.serverStatusCallbacks = [];
};

/**
 * Run the cluster master.
 *
 * @api public
 */
Master.prototype.run = function () {
    var self = this;

    cluster.setupMaster({
        exec: path.resolve(this.config.exec),
        args: this.config.args || [],
        silent: this.config.silent
    });

    cluster.on('fork', onFork);

    // Setup signals

    process.on('SIGTERM', function onSigTerm() {
        self.log.info('received SIGTERM, exiting immediately');
        process.exit(0);
    });

    process.on('SIGINT', function onSigInt() {
        self.log.info('received SIGINT, exiting immediately');
        process.exit(0);
    });

    process.on('SIGQUIT', function onSigQuit() {
        self.log.info('received SIGQUIT, shutting down gracefully');
        self.shutdown();
    });

    process.on('SIGHUP', function onSigHup() {
        self.log.info('received SIGHUP, reloading gracefully');
        self.reload();
    });

    process.on('uncaughtException', function onUncaughtException(err) {
        self.log.error(err);
        process.exit(1);
    });

    this.reload();

    function onFork(worker) {
        self.log.debug('worker %d was forked', worker.process.pid);

        worker.flora = worker.flora || {};
        worker.flora.state = 'forked';
        worker.flora.generation = self.generation;
        worker.flora.status = self.status.addChild('workers');
        worker.flora.status.onStatus(function () {
            this.set('state', worker.flora.state);
            this.set('generation', worker.flora.generation);
        });

        worker.on('message', function onWorkerMessage(message) {
            if (!message.event) return;
            self.log.trace(message, 'message from worker %d', worker.process.pid);
            worker.emit('flora::' + message.event, message);
        });

        worker.on('online', function onWorkerOnline() {
            self.log.debug('worker %d is online', worker.process.pid);
            if (worker.flora.state === 'forked') {
                worker.flora.state = 'initializing';
            }
        });

        worker.on('flora::ready', function onWorkerReady() {
            self.log.debug('worker %d claims to be ready', worker.process.pid);
            if (worker.flora.state === 'initializing') {
                worker.flora.state = 'running';

                if (worker.flora._killTimeout) {
                    clearTimeout(worker.flora._killTimeout);
                    delete worker.flora._killTimeout;
                }
            }

            self.readjustCluster();
        });

        worker.on('flora::shutdown', function onWorkerShutdown() {
            self.log.debug('worker %d is shutting down', worker.process.pid);
            worker.flora.state = 'shutdown';

            self.readjustCluster();
        });

        worker.on('disconnect', function onWorkerDisconnect() {
            self.log.debug('worker %d disconnected', worker.process.pid);
            worker.flora.state = 'disconnected';

            self.readjustCluster();
        });

        worker.on('exit', function onWorkerExit(code, signal) {
            worker.flora.state = 'exited';
            worker.flora.status.close();

            if (signal) {
                self.log.warn('worker %d killed by %s', worker.process.pid, signal);
            } else if (code !== 0) {
                self.log.warn('worker %d exited with error code %d', worker.process.pid, code);
            } else {
                self.log.info('worker %d exited successfully', worker.process.pid);
            }

            if (worker.flora._killTimeout) {
                clearTimeout(worker.flora._killTimeout);
                delete worker.flora._killTimeout;
            }

            self.readjustCluster();
        });

        worker.on('flora::serverStatus', function onServerStatus() {
            self.log.trace('worker %d requested serverStatus', worker.process.pid);

            worker.flora._notifyServerStatus = true;
            self.serverStatus();
        });

        worker.on('flora::status', function onWorkerStatus(message) {
            worker.flora.status.setStatus(message.status);
            worker.flora._fullStatusIsUp2date = true;

            self.notifyServerStatus();
        });

        self.setWorkerKillTimeout(worker, 'startup');
    }
};

/**
 * Reload the workers
 *
 * @api public
 */
Master.prototype.reload = function () {
    var self = this;

    this.inBeforeReload = true;

    if (this.config.beforeReload && this.generation > 0) {
        this.config.beforeReload(function () {
            doReload();
        });
    } else {
        doReload();
    }

    function doReload() {
        self.inBeforeReload = false;

        self.generation++;
        self.currentGenerationForked = false;

        if (self.generation === 1) {
            self.state = 'starting';
            self.log.info('Starting Flora master process at PID ' + process.pid);
        } else {
            self.state = 'reloading';
            self.log.info('Gracefully reloading Flora (generation ' + self.generation + ')');
        }

        self.readjustCluster();
    }
};

/**
 * Set the cluster config. Merges config values into the existing config
 *
 * @param {Object} opts
 * @api public
 */
Master.prototype.setConfig = function (opts) {
    for (var name in opts) {
        this.config[name] = opts[name];
    }

    if (!this.inBeforeReload) {
        this.readjustCluster();
    }
};

Master.prototype.readjustCluster = function () {
    var worker, workerId, i, self = this;
    var activeWorkers = 0, runningWorkers = 0;

    // count active and running workers for current generation:
    for (workerId in cluster.workers) {
        worker = cluster.workers[workerId];
        if (worker.flora.generation !== this.generation) continue;

        if (worker.flora.state === 'running' ||
            worker.flora.state === 'initializing' ||
            worker.flora.state === 'forked') {
            activeWorkers++;

            if (worker.flora.state === 'running') {
                runningWorkers++;
            }
        }
    }

    if (this.state === 'reloading') {
        if (this.currentGenerationForked && activeWorkers < this.config.workers) {
            // rollback failing new generation (will be killed automatically afterwards):
            this.generation--;
            this.state = 'running';

            this.log.error('Graceful reload failed - keeping generation %d online', this.generation);

            this.readjustCluster();
            return;
        }
    }

    // TODO: do throttling on permanent errors

    if (this.isRunning) {
        // start missing workers:
        for (i = 0; i < this.config.workers - activeWorkers; i++) {
            this.log.info('readjustCluster: forking new worker');
            cluster.fork();
        }

        this.currentGenerationForked = true;

        // stop workers of current generation if we have too much:
        if (activeWorkers > this.config.workers) {
            var tooMuchWorkers = activeWorkers - this.config.workers;

            for (workerId in cluster.workers) {
                if (tooMuchWorkers <= 0) break;

                worker = cluster.workers[workerId];

                if (worker.flora.generation !== this.generation) continue;

                this.shutdownWorker(worker);
                tooMuchWorkers--;
            }
        }
    }

    // if all workers for new generation are up and running, shutdown the old ones -
    // same process if we are shutting down the complete server:
    if (runningWorkers >= this.config.workers || !this.isRunning) {
        if (this.isRunning) {
            this.state = 'running';
        }

        for (workerId in cluster.workers) {
            worker = cluster.workers[workerId];

            if (worker.flora.generation === this.generation && this.isRunning) continue;

            this.shutdownWorker(worker);
        }
    }

    process.nextTick(function () {
        self.notifyServerStatus(); // avoid waiting for stale workers for its status
    });
};

Master.prototype.shutdownWorker = function (worker) {
    if (worker.flora.state === 'running' ||
        worker.flora.state === 'initializing' ||
        worker.flora.state === 'forked') {
        this.log.info('Shutdown worker %d (generation %d)', worker.process.pid, worker.flora.generation);

        worker.flora.state = 'shutdownRequested';
        worker.send({event: 'shutdown'});

        this.setWorkerKillTimeout(worker, 'shutdown');
    }
};

Master.prototype.setWorkerKillTimeout = function (worker, type) {
    var self = this;

    if (worker.flora._killTimeout) {
        clearTimeout(worker.flora._killTimeout);
    }

    // we add one second to the timeouts in master, to give the worker the chance
    // to handle the same timeouts itself before we kill him

    worker.flora._killTimeout = setTimeout(function onWorkerKillTimeout() {
        self.log.warn('Killing worker ' + worker.process.pid + ' after ' + type + ' timeout by master');
        worker.kill();
    }, self.config[type + 'Timeout'] + 1000);
};

Master.prototype.notifyServerStatus = function (timeoutReached) {
    var workerStatusIsUp2date = true;
    var workerId, worker;

    for (workerId in cluster.workers) {
        worker = cluster.workers[workerId];

        if (!worker.flora._fullStatusIsUp2date) {
            workerStatusIsUp2date = false;
            if (timeoutReached) {
                worker.flora.status.set('error', 'Worker did not respond - status outdated');
            }
        }
    }

    if (workerStatusIsUp2date || timeoutReached) {
        if (this.serverStatusTimeout) {
            clearTimeout(this.serverStatusTimeout);
            this.serverStatusTimeout = null;
        }

        var serverStatus = this.status.getStatus();

        var cb;
        while (cb = this.serverStatusCallbacks.shift()) {
            cb(null, serverStatus);
        }

        for (workerId in cluster.workers) {
            worker = cluster.workers[workerId];

            if (!worker.flora._notifyServerStatus) continue;

            worker.send({
                event: 'serverStatus',
                serverStatus: serverStatus
            });

            delete worker.flora._notifyServerStatus;
        }
    }
};

/**
 * Shutdown the cluster
 *
 * @api public
 */
Master.prototype.shutdown = function () {
    var self = this;

    if (this.config.beforeShutdown) {
        this.config.beforeShutdown(function () {
            doShutdown();
        });
    } else {
        doShutdown();
    }

    function doShutdown() {
        self.isRunning = false;
        self.state = 'shutdown';

        self.readjustCluster();
    }
};

/**
 * Retrieve the cluster status
 *
 * @param {Function} callback
 * @api public
 */
Master.prototype.serverStatus = function (callback) {
    var self = this;

    if (!this.serverStatusTimeout) {
        this.serverStatusTimeout = setTimeout(function onServerStatusTimeout() {
            self.serverStatusTimeout = null;
            self.notifyServerStatus(true);
        }, 1000);
    }

    var askWorkerId, askWorker;

    for (askWorkerId in cluster.workers) {
        askWorker = cluster.workers[askWorkerId];

        try {
            askWorker.flora._fullStatusIsUp2date = false;
            askWorker.send({event: 'status'});
        } catch (err) {
            // catch rare "channel closed" errors on worker shutdown
            this.log.error(err, 'Error requesting status from worker');

            // don't wait for it:
            askWorker.flora._fullStatusIsUp2date = true;
            askWorker.flora.status.set('error', 'Error requesting status from worker - status outdated');
        }
    }

    if (callback) this.serverStatusCallbacks.push(callback);
};
<commit_msg>Make master an event emitter<commit_after>'use strict';

var bunyan = require('bunyan');
var cluster = require('cluster');
var path = require('path');
var os = require('os');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var Status = require('./status');

/**
 * Cluster master.
 *
 * Options:
 * - exec: the script to exit
 * - workers: worker count (default: host CPU count)
 * - args: arguments passed to workers (default: [])
 * - startupTimeout: how long the master waits for worker startup (default: 10000)
 * - shutdownTimeout: how long the master waits for worker shutdown (default: 30000)
 * - silent: suppress worker output
 * - log: bunyan instance for logging
 * - beforeReload: function that is executed before the master is reloaded (async, with callback)
 * - beforeShutdown: function that is executed before the master is shut down (async, with callback)
 *
 * @param {Object} opts
 * @constructor
 */
var Master = module.exports = function (opts) {
    var self = this;

    this.config = opts || {};
    if (!this.config.exec) throw new Error('Must define an "exec" script');
    this.config.workers = this.config.workers || os.cpus().length;
    this.config.startupTimeout = this.config.startupTimeout || 10000;
    this.config.shutdownTimeout = this.config.shutdownTimeout || 30000;

    if (opts.log) {
        this.log = opts.log.child({'component': 'master'});
    } else {
        this.log = bunyan.createLogger({name: 'flora-cluster', 'component': 'master'});
    }

    this.log.trace('initializing Master');

    this.state = 'initializing';
    this.isRunning = true;
    this.generation = 0;
    this.currentGenerationForked = false;
    this.inBeforeReload = false;

    this.status = new Status();
    this.status.set('state', null);
    this.status.set('startTime', new Date());
    this.status.set('pid', process.pid);
    this.status.set('generation', null);
    this.status.onStatus(function () {
        this.set('state', self.state);
        this.set('generation', self.generation);
    });

    this.serverStatusTimeout = null;
    this.serverStatusCallbacks = [];
};

util.inherits(Master, EventEmitter);

/**
 * Run the cluster master.
 *
 * @api public
 */
Master.prototype.run = function () {
    var self = this;

    cluster.setupMaster({
        exec: path.resolve(this.config.exec),
        args: this.config.args || [],
        silent: this.config.silent
    });

    cluster.on('fork', onFork);

    // Setup signals

    process.on('SIGTERM', function onSigTerm() {
        self.log.info('received SIGTERM, exiting immediately');
        process.exit(0);
    });

    process.on('SIGINT', function onSigInt() {
        self.log.info('received SIGINT, exiting immediately');
        process.exit(0);
    });

    process.on('SIGQUIT', function onSigQuit() {
        self.log.info('received SIGQUIT, shutting down gracefully');
        self.shutdown();
    });

    process.on('SIGHUP', function onSigHup() {
        self.log.info('received SIGHUP, reloading gracefully');
        self.reload();
    });

    process.on('uncaughtException', function onUncaughtException(err) {
        self.log.error(err);
        process.exit(1);
    });

    this.reload();

    function onFork(worker) {
        self.log.debug('worker %d was forked', worker.process.pid);

        worker.flora = worker.flora || {};
        worker.flora.state = 'forked';
        worker.flora.generation = self.generation;
        worker.flora.status = self.status.addChild('workers');
        worker.flora.status.onStatus(function () {
            this.set('state', worker.flora.state);
            this.set('generation', worker.flora.generation);
        });

        worker.on('message', function onWorkerMessage(message) {
            if (!message.event) return;
            self.log.trace(message, 'message from worker %d', worker.process.pid);
            worker.emit('flora::' + message.event, message);
        });

        worker.on('online', function onWorkerOnline() {
            self.log.debug('worker %d is online', worker.process.pid);
            if (worker.flora.state === 'forked') {
                worker.flora.state = 'initializing';
            }
        });

        worker.on('flora::ready', function onWorkerReady() {
            self.log.debug('worker %d claims to be ready', worker.process.pid);
            if (worker.flora.state === 'initializing') {
                worker.flora.state = 'running';

                if (worker.flora._killTimeout) {
                    clearTimeout(worker.flora._killTimeout);
                    delete worker.flora._killTimeout;
                }
            }

            self.readjustCluster();
        });

        worker.on('flora::shutdown', function onWorkerShutdown() {
            self.log.debug('worker %d is shutting down', worker.process.pid);
            worker.flora.state = 'shutdown';

            self.readjustCluster();
        });

        worker.on('disconnect', function onWorkerDisconnect() {
            self.log.debug('worker %d disconnected', worker.process.pid);
            worker.flora.state = 'disconnected';

            self.readjustCluster();
        });

        worker.on('exit', function onWorkerExit(code, signal) {
            worker.flora.state = 'exited';
            worker.flora.status.close();

            if (signal) {
                self.log.warn('worker %d killed by %s', worker.process.pid, signal);
            } else if (code !== 0) {
                self.log.warn('worker %d exited with error code %d', worker.process.pid, code);
            } else {
                self.log.info('worker %d exited successfully', worker.process.pid);
            }

            if (worker.flora._killTimeout) {
                clearTimeout(worker.flora._killTimeout);
                delete worker.flora._killTimeout;
            }

            self.readjustCluster();
        });

        worker.on('flora::serverStatus', function onServerStatus() {
            self.log.trace('worker %d requested serverStatus', worker.process.pid);

            worker.flora._notifyServerStatus = true;
            self.serverStatus();
        });

        worker.on('flora::status', function onWorkerStatus(message) {
            worker.flora.status.setStatus(message.status);
            worker.flora._fullStatusIsUp2date = true;

            self.notifyServerStatus();
        });

        self.setWorkerKillTimeout(worker, 'startup');
    }

    this.emit('init');
};

/**
 * Reload the workers
 *
 * @api public
 */
Master.prototype.reload = function () {
    var self = this;

    this.inBeforeReload = true;

    if (this.config.beforeReload && this.generation > 0) {
        this.config.beforeReload(function () {
            doReload();
        });
    } else {
        doReload();
    }

    function doReload() {
        self.inBeforeReload = false;

        self.generation++;
        self.currentGenerationForked = false;

        if (self.generation === 1) {
            self.state = 'starting';
            self.log.info('Starting Flora master process at PID ' + process.pid);
        } else {
            self.state = 'reloading';
            self.log.info('Gracefully reloading Flora (generation ' + self.generation + ')');
        }

        self.readjustCluster();
    }
};

/**
 * Set the cluster config. Merges config values into the existing config
 *
 * @param {Object} opts
 * @api public
 */
Master.prototype.setConfig = function (opts) {
    for (var name in opts) {
        this.config[name] = opts[name];
    }

    if (!this.inBeforeReload) {
        this.readjustCluster();
    }
};

Master.prototype.readjustCluster = function () {
    var worker, workerId, i, self = this;
    var activeWorkers = 0, runningWorkers = 0;

    // count active and running workers for current generation:
    for (workerId in cluster.workers) {
        worker = cluster.workers[workerId];
        if (worker.flora.generation !== this.generation) continue;

        if (worker.flora.state === 'running' ||
            worker.flora.state === 'initializing' ||
            worker.flora.state === 'forked') {
            activeWorkers++;

            if (worker.flora.state === 'running') {
                runningWorkers++;
            }
        }
    }

    if (this.state === 'reloading') {
        if (this.currentGenerationForked && activeWorkers < this.config.workers) {
            // rollback failing new generation (will be killed automatically afterwards):
            this.generation--;
            this.state = 'running';

            this.log.error('Graceful reload failed - keeping generation %d online', this.generation);

            this.readjustCluster();
            return;
        }
    }

    // TODO: do throttling on permanent errors

    if (this.isRunning) {
        // start missing workers:
        for (i = 0; i < this.config.workers - activeWorkers; i++) {
            this.log.info('readjustCluster: forking new worker');
            cluster.fork();
        }

        this.currentGenerationForked = true;

        // stop workers of current generation if we have too much:
        if (activeWorkers > this.config.workers) {
            var tooMuchWorkers = activeWorkers - this.config.workers;

            for (workerId in cluster.workers) {
                if (tooMuchWorkers <= 0) break;

                worker = cluster.workers[workerId];

                if (worker.flora.generation !== this.generation) continue;

                this.shutdownWorker(worker);
                tooMuchWorkers--;
            }
        }
    }

    // if all workers for new generation are up and running, shutdown the old ones -
    // same process if we are shutting down the complete server:
    if (runningWorkers >= this.config.workers || !this.isRunning) {
        if (this.isRunning) {
            this.state = 'running';
        }

        for (workerId in cluster.workers) {
            worker = cluster.workers[workerId];

            if (worker.flora.generation === this.generation && this.isRunning) continue;

            this.shutdownWorker(worker);
        }
    }

    process.nextTick(function () {
        self.notifyServerStatus(); // avoid waiting for stale workers for its status
    });
};

Master.prototype.shutdownWorker = function (worker) {
    if (worker.flora.state === 'running' ||
        worker.flora.state === 'initializing' ||
        worker.flora.state === 'forked') {
        this.log.info('Shutdown worker %d (generation %d)', worker.process.pid, worker.flora.generation);

        worker.flora.state = 'shutdownRequested';
        worker.send({event: 'shutdown'});

        this.setWorkerKillTimeout(worker, 'shutdown');
    }
};

Master.prototype.setWorkerKillTimeout = function (worker, type) {
    var self = this;

    if (worker.flora._killTimeout) {
        clearTimeout(worker.flora._killTimeout);
    }

    // we add one second to the timeouts in master, to give the worker the chance
    // to handle the same timeouts itself before we kill him

    worker.flora._killTimeout = setTimeout(function onWorkerKillTimeout() {
        self.log.warn('Killing worker ' + worker.process.pid + ' after ' + type + ' timeout by master');
        worker.kill();
    }, self.config[type + 'Timeout'] + 1000);
};

Master.prototype.notifyServerStatus = function (timeoutReached) {
    var workerStatusIsUp2date = true;
    var workerId, worker;

    for (workerId in cluster.workers) {
        worker = cluster.workers[workerId];

        if (!worker.flora._fullStatusIsUp2date) {
            workerStatusIsUp2date = false;
            if (timeoutReached) {
                worker.flora.status.set('error', 'Worker did not respond - status outdated');
            }
        }
    }

    if (workerStatusIsUp2date || timeoutReached) {
        if (this.serverStatusTimeout) {
            clearTimeout(this.serverStatusTimeout);
            this.serverStatusTimeout = null;
        }

        var serverStatus = this.status.getStatus();

        var cb;
        while (cb = this.serverStatusCallbacks.shift()) {
            cb(null, serverStatus);
        }

        for (workerId in cluster.workers) {
            worker = cluster.workers[workerId];

            if (!worker.flora._notifyServerStatus) continue;

            worker.send({
                event: 'serverStatus',
                serverStatus: serverStatus
            });

            delete worker.flora._notifyServerStatus;
        }
    }
};

/**
 * Shutdown the cluster
 *
 * @api public
 */
Master.prototype.shutdown = function () {
    var self = this;

    if (this.config.beforeShutdown) {
        this.config.beforeShutdown(function () {
            doShutdown();
        });
    } else {
        doShutdown();
    }

    function doShutdown() {
        self.isRunning = false;
        self.state = 'shutdown';

        self.emit('shutdown');
        self.readjustCluster();
    }
};

/**
 * Retrieve the cluster status
 *
 * @param {Function} callback
 * @api public
 */
Master.prototype.serverStatus = function (callback) {
    var self = this;

    if (!this.serverStatusTimeout) {
        this.serverStatusTimeout = setTimeout(function onServerStatusTimeout() {
            self.serverStatusTimeout = null;
            self.notifyServerStatus(true);
        }, 1000);
    }

    var askWorkerId, askWorker;

    for (askWorkerId in cluster.workers) {
        askWorker = cluster.workers[askWorkerId];

        try {
            askWorker.flora._fullStatusIsUp2date = false;
            askWorker.send({event: 'status'});
        } catch (err) {
            // catch rare "channel closed" errors on worker shutdown
            this.log.error(err, 'Error requesting status from worker');

            // don't wait for it:
            askWorker.flora._fullStatusIsUp2date = true;
            askWorker.flora.status.set('error', 'Error requesting status from worker - status outdated');
        }
    }

    if (callback) this.serverStatusCallbacks.push(callback);
};
<|endoftext|>


<commit_before>// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('karma-mocha-reporter'),
      require('@angular-devkit/build-angular/plugins/karma')
    ],
    client: {
      clearContext: false // leave Jasmine Spec Runner output visible in browser
    },
    coverageIstanbulReporter: {
      dir: require('path').join(__dirname, '../coverage'),
      reports: ['html', 'lcovonly'],
      fixWebpackSourcePaths: true
    },
    reporters: ['kjhtml', 'mocha'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    singleRun: false,
    customLaunchers: {
      ChromeHeadlessForDocker: {
        base: 'ChromeHeadless',
        flags: [
          '--disable-web-security',
          '--disable-gpu',
          '--no-sandbox'
        ],
        displayName: 'Chrome Headless for docker'
      }
    },
    browsers: ['Chrome']
  });
};
<commit_msg>Fix: Use headless Chrome for Angular tests again.<commit_after>// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('karma-mocha-reporter'),
      require('@angular-devkit/build-angular/plugins/karma')
    ],
    client: {
      clearContext: false // leave Jasmine Spec Runner output visible in browser
    },
    coverageIstanbulReporter: {
      dir: require('path').join(__dirname, '../coverage'),
      reports: ['html', 'lcovonly'],
      fixWebpackSourcePaths: true
    },
    reporters: ['kjhtml', 'mocha'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    singleRun: false,
    customLaunchers: {
      ChromeHeadlessForDocker: {
        base: 'ChromeHeadless',
        flags: [
          '--disable-web-security',
          '--disable-gpu',
          '--no-sandbox'
        ],
        displayName: 'Chrome Headless for docker'
      }
    },
    browsers: ['ChromeHeadlessForDocker']
  });
};
<|endoftext|>


<commit_before>var path = require('path');
var assert = require('assert');
var fs = require('fs');
var files = require(path.join(__dirname, '..', 'files.js'));
var bundler = require(path.join(__dirname, '..', 'bundler.js'));
var inFiber = require(path.join(__dirname, '..', 'fiber-helpers.js')).inFiber;

///
/// SETUP
///

// print stack track and exit with error code if an assertion fails
process.on('uncaughtException', function (err) {
  console.log(err.stack);
  console.log();
  console.log('Bundle can be found at ' + lastTmpDir);
  process.exit(1);
});

///
/// UTILITIES
///

var tmpBaseDir = files.mkdtemp('test_bundler');
var tmpCounter = 1;
var lastTmpDir;
var tmpDir = function () {
  lastTmpDir = path.join(tmpBaseDir, "" + (tmpCounter++) /* path.join likes string, not numbers */);
  files.mkdir_p(lastTmpDir);
  return lastTmpDir;
};

///
/// TEST APPS
///

// an empty app with a .meteor/version file whose contents are "0.1"
var versionedAppDir = path.join(__dirname, 'empty-versioned-app');
// an empty app with no .meteor/version file
var unversionedAppDir = path.join(__dirname, 'empty-unversioned-app');

///
/// TESTS
///

// versioned app, nodeModules: 'skip'
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(versionedAppDir, tmpOutputDir, {nodeModulesMode: 'skip'});
  assert.strictEqual(errors, undefined);

  // sanity check -- main.js has expected contents.
  assert.strictEqual(fs.readFileSync(path.join(tmpOutputDir, "main.js"), "utf8").trim(),
                     "require(require('path').join(__dirname, 'server', 'server.js'));");
  // no node_modules directory
  assert(!fs.existsSync(path.join(tmpOutputDir, "server", "node_modules")));
  // verify that contents are minified
  var appHtml = fs.readFileSync(path.join(tmpOutputDir, "app.html"));
  assert(/src=\"\/[0-9a-f]{40,40}.js\"/.test(appHtml));
  assert(!(/src=\"\/packages/.test(appHtml)));
}));

// versioned app, nodeModules: 'skip', noMinify
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(versionedAppDir, tmpOutputDir, {nodeModulesMode: 'skip', noMinify: true});
  assert.strictEqual(errors, undefined);

  // sanity check -- main.js has expected contents.
  assert.strictEqual(fs.readFileSync(path.join(tmpOutputDir, "main.js"), "utf8").trim(),
                     "require(require('path').join(__dirname, 'server', 'server.js'));");
  // verify that contents are not minified
  var appHtml = fs.readFileSync(path.join(tmpOutputDir, "app.html"));
  assert(!(/src=\"\/[0-9a-f]{40,40}.js\"/.test(appHtml)));
  assert(/src=\"\/packages\/meteor/.test(appHtml));
  assert(/src=\"\/packages\/deps/.test(appHtml));
  // verify that tests aren't included
  assert(!(/src=\"\/packages\/meteor\/url_tests.js/.test(appHtml)));
}));

// versioned app, nodeModules: 'skip', noMinify, testPackages: ['meteor']
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(
    versionedAppDir, tmpOutputDir, {nodeModulesMode: 'skip', noMinify: true, testPackages: ['meteor']});
  assert.strictEqual(errors, undefined);

  // sanity check -- main.js has expected contents.
  assert.strictEqual(fs.readFileSync(path.join(tmpOutputDir, "main.js"), "utf8").trim(),
                     "require(require('path').join(__dirname, 'server', 'server.js'));");
  // verify that tests for the meteor package are included
  var appHtml = fs.readFileSync(path.join(tmpOutputDir, "app.html"));
  assert(/src=\"\/packages\/meteor\/url_tests.js/.test(appHtml));
}));

// versioned app, nodeModules: 'copy'
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(versionedAppDir, tmpOutputDir, {nodeModulesMode: 'copy'});
  assert.strictEqual(errors, undefined);

  // sanity check -- main.js has expected contents.
  assert.strictEqual(fs.readFileSync(path.join(tmpOutputDir, "main.js"), "utf8").trim(),
                     "require(require('path').join(__dirname, 'server', 'server.js'));");
  // node_modules directory exists and is not a symlink
  assert(!fs.lstatSync(path.join(tmpOutputDir, "server", "node_modules")).isSymbolicLink());
  // node_modules contains fibers
  assert(fs.existsSync(path.join(tmpOutputDir, "server", "node_modules", "fibers")));
}));

// versioned app, nodeModules: 'symlink'
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(versionedAppDir, tmpOutputDir, {nodeModulesMode: 'symlink'});
  assert.strictEqual(errors, undefined);

  // sanity check -- main.js has expected contents.
  assert.strictEqual(fs.readFileSync(path.join(tmpOutputDir, "main.js"), "utf8").trim(),
                     "require(require('path').join(__dirname, 'server', 'server.js'));");
  // node_modules directory exists and is not a symlink
  assert(fs.lstatSync(path.join(tmpOutputDir, "server", "node_modules")).isSymbolicLink());
  // node_modules contains fibers
  assert(fs.existsSync(path.join(tmpOutputDir, "server", "node_modules", "fibers")));
}));

// unversioned app, no options
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(unversionedAppDir, tmpOutputDir, {nodeModulesMode: 'skip'});
  assert.notEqual(errors.length, 0);
  assert.notEqual(errors[0].indexOf('Exception while bundling'), -1);
  assert.notEqual(errors[0].indexOf('Package not found: meteor'), -1);
}));

// unversioned app, using `versionOverride`
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(unversionedAppDir, tmpOutputDir, {versionOverride: '0.1', nodeModulesMode: 'skip'});
  assert.strictEqual(errors, undefined);

  // sanity check -- main.js has expected contents.
  assert.strictEqual(fs.readFileSync(path.join(tmpOutputDir, "main.js"), "utf8").trim(),
                     "require(require('path').join(__dirname, 'server', 'server.js'));");
}));

/// SUCCESS

files.rm_recursive(tmpBaseDir);<commit_msg>fix typo<commit_after>var path = require('path');
var assert = require('assert');
var fs = require('fs');
var files = require(path.join(__dirname, '..', 'files.js'));
var bundler = require(path.join(__dirname, '..', 'bundler.js'));
var inFiber = require(path.join(__dirname, '..', 'fiber-helpers.js')).inFiber;

///
/// SETUP
///

// print stack track and exit with error code if an assertion fails
process.on('uncaughtException', function (err) {
  console.log(err.stack);
  console.log();
  console.log('Bundle can be found at ' + lastTmpDir);
  process.exit(1);
});

///
/// UTILITIES
///

var tmpBaseDir = files.mkdtemp('test_bundler');
var tmpCounter = 1;
var lastTmpDir;
var tmpDir = function () {
  lastTmpDir = path.join(tmpBaseDir, "" + (tmpCounter++) /* path.join likes string, not numbers */);
  files.mkdir_p(lastTmpDir);
  return lastTmpDir;
};

///
/// TEST APPS
///

// an empty app with a .meteor/version file whose contents are "0.1"
var versionedAppDir = path.join(__dirname, 'empty-versioned-app');
// an empty app with no .meteor/version file
var unversionedAppDir = path.join(__dirname, 'empty-unversioned-app');

///
/// TESTS
///

// versioned app, nodeModules: 'skip'
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(versionedAppDir, tmpOutputDir, {nodeModulesMode: 'skip'});
  assert.strictEqual(errors, undefined);

  // sanity check -- main.js has expected contents.
  assert.strictEqual(fs.readFileSync(path.join(tmpOutputDir, "main.js"), "utf8").trim(),
                     "require(require('path').join(__dirname, 'server', 'server.js'));");
  // no node_modules directory
  assert(!fs.existsSync(path.join(tmpOutputDir, "server", "node_modules")));
  // verify that contents are minified
  var appHtml = fs.readFileSync(path.join(tmpOutputDir, "app.html"));
  assert(/src=\"\/[0-9a-f]{40,40}.js\"/.test(appHtml));
  assert(!(/src=\"\/packages/.test(appHtml)));
}));

// versioned app, nodeModules: 'skip', noMinify
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(versionedAppDir, tmpOutputDir, {nodeModulesMode: 'skip', noMinify: true});
  assert.strictEqual(errors, undefined);

  // sanity check -- main.js has expected contents.
  assert.strictEqual(fs.readFileSync(path.join(tmpOutputDir, "main.js"), "utf8").trim(),
                     "require(require('path').join(__dirname, 'server', 'server.js'));");
  // verify that contents are not minified
  var appHtml = fs.readFileSync(path.join(tmpOutputDir, "app.html"));
  assert(!(/src=\"\/[0-9a-f]{40,40}.js\"/.test(appHtml)));
  assert(/src=\"\/packages\/meteor/.test(appHtml));
  assert(/src=\"\/packages\/deps/.test(appHtml));
  // verify that tests aren't included
  assert(!(/src=\"\/packages\/meteor\/url_tests.js/.test(appHtml)));
}));

// versioned app, nodeModules: 'skip', noMinify, testPackages: ['meteor']
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(
    versionedAppDir, tmpOutputDir, {nodeModulesMode: 'skip', noMinify: true, testPackages: ['meteor']});
  assert.strictEqual(errors, undefined);

  // sanity check -- main.js has expected contents.
  assert.strictEqual(fs.readFileSync(path.join(tmpOutputDir, "main.js"), "utf8").trim(),
                     "require(require('path').join(__dirname, 'server', 'server.js'));");
  // verify that tests for the meteor package are included
  var appHtml = fs.readFileSync(path.join(tmpOutputDir, "app.html"));
  assert(/src=\"\/packages\/meteor\/url_tests.js/.test(appHtml));
}));

// versioned app, nodeModules: 'copy'
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(versionedAppDir, tmpOutputDir, {nodeModulesMode: 'copy'});
  assert.strictEqual(errors, undefined);

  // sanity check -- main.js has expected contents.
  assert.strictEqual(fs.readFileSync(path.join(tmpOutputDir, "main.js"), "utf8").trim(),
                     "require(require('path').join(__dirname, 'server', 'server.js'));");
  // node_modules directory exists and is not a symlink
  assert(!fs.lstatSync(path.join(tmpOutputDir, "server", "node_modules")).isSymbolicLink());
  // node_modules contains fibers
  assert(fs.existsSync(path.join(tmpOutputDir, "server", "node_modules", "fibers")));
}));

// versioned app, nodeModules: 'symlink'
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(versionedAppDir, tmpOutputDir, {nodeModulesMode: 'symlink'});
  assert.strictEqual(errors, undefined);

  // sanity check -- main.js has expected contents.
  assert.strictEqual(fs.readFileSync(path.join(tmpOutputDir, "main.js"), "utf8").trim(),
                     "require(require('path').join(__dirname, 'server', 'server.js'));");
  // node_modules directory exists and is a symlink
  assert(fs.lstatSync(path.join(tmpOutputDir, "server", "node_modules")).isSymbolicLink());
  // node_modules contains fibers
  assert(fs.existsSync(path.join(tmpOutputDir, "server", "node_modules", "fibers")));
}));

// unversioned app, no options
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(unversionedAppDir, tmpOutputDir, {nodeModulesMode: 'skip'});
  assert.notEqual(errors.length, 0);
  assert.notEqual(errors[0].indexOf('Exception while bundling'), -1);
  assert.notEqual(errors[0].indexOf('Package not found: meteor'), -1);
}));

// unversioned app, using `versionOverride`
assert.doesNotThrow(inFiber(function () {
  var tmpOutputDir = tmpDir();
  var errors = bundler.bundle(unversionedAppDir, tmpOutputDir, {versionOverride: '0.1', nodeModulesMode: 'skip'});
  assert.strictEqual(errors, undefined);

  // sanity check -- main.js has expected contents.
  assert.strictEqual(fs.readFileSync(path.join(tmpOutputDir, "main.js"), "utf8").trim(),
                     "require(require('path').join(__dirname, 'server', 'server.js'));");
}));

/// SUCCESS

files.rm_recursive(tmpBaseDir);<|endoftext|>


<commit_before>import { exit } from 'node:process'
import { Server } from '@hapi/hapi'
import { log } from '@serverless/utils/log.js'
import { invocationsRoute, invokeAsyncRoute } from './routes/index.js'

export default class HttpServer {
  #lambda = null

  #options = null

  #server = null

  constructor(options, lambda) {
    this.#lambda = lambda
    this.#options = options

    const { host, lambdaPort } = options

    const serverOptions = {
      host,
      port: lambdaPort,
    }

    this.#server = new Server(serverOptions)
  }

  async start() {
    // add routes
    const _invocationsRoute = invocationsRoute(this.#lambda, this.#options)
    const _invokeAsyncRoute = invokeAsyncRoute(this.#lambda, this.#options)

    this.#server.route([_invokeAsyncRoute, _invocationsRoute])

    const { host, httpsProtocol, lambdaPort } = this.#options

    try {
      await this.#server.start()
    } catch (err) {
      log.error(
        `Unexpected error while starting serverless-offline lambda server on port ${lambdaPort}:`,
        err,
      )
      exit(1)
    }

    log.notice(
      `Offline [http for lambda] listening on http${
        httpsProtocol ? 's' : ''
      }://${host}:${lambdaPort}`,
    )

    // Print all the invocation routes to debug
    const basePath = `http${httpsProtocol ? 's' : ''}://${host}:${lambdaPort}`
    const funcNamePairs = this.#lambda.listFunctionNamePairs()

    log.notice(
      [
        `Function names exposed for local invocation by aws-sdk:`,
        ...this.#lambda
          .listFunctionNames()
          .map(
            (functionName) =>
              `           * ${funcNamePairs[functionName]}: ${functionName}`,
          ),
      ].join('\n'),
    )
    log.debug(
      [
        `Lambda Invocation Routes (for AWS SDK or AWS CLI):`,
        ...this.#lambda
          .listFunctionNames()
          .map(
            (functionName) =>
              `           * ${
                _invocationsRoute.method
              } ${basePath}${_invocationsRoute.path.replace(
                '{functionName}',
                functionName,
              )}`,
          ),
      ].join('\n'),
    )

    log.debug(
      [
        `Lambda Async Invocation Routes (for AWS SDK or AWS CLI):`,
        ...this.#lambda
          .listFunctionNames()
          .map(
            (functionName) =>
              `           * ${
                _invokeAsyncRoute.method
              } ${basePath}${_invokeAsyncRoute.path.replace(
                '{functionName}',
                functionName,
              )}`,
          ),
      ].join('\n'),
    )
  }

  // stops the server
  stop(timeout) {
    return this.#server.stop({
      timeout,
    })
  }
}
<commit_msg>chore: rename vars, remove underscore<commit_after>import { exit } from 'node:process'
import { Server } from '@hapi/hapi'
import { log } from '@serverless/utils/log.js'
import { invocationsRoute, invokeAsyncRoute } from './routes/index.js'

export default class HttpServer {
  #lambda = null

  #options = null

  #server = null

  constructor(options, lambda) {
    this.#lambda = lambda
    this.#options = options

    const { host, lambdaPort } = options

    const serverOptions = {
      host,
      port: lambdaPort,
    }

    this.#server = new Server(serverOptions)
  }

  async start() {
    // add routes
    const invRoute = invocationsRoute(this.#lambda, this.#options)
    const invAsyncRoute = invokeAsyncRoute(this.#lambda, this.#options)

    this.#server.route([invAsyncRoute, invRoute])

    const { host, httpsProtocol, lambdaPort } = this.#options

    try {
      await this.#server.start()
    } catch (err) {
      log.error(
        `Unexpected error while starting serverless-offline lambda server on port ${lambdaPort}:`,
        err,
      )
      exit(1)
    }

    log.notice(
      `Offline [http for lambda] listening on http${
        httpsProtocol ? 's' : ''
      }://${host}:${lambdaPort}`,
    )

    // Print all the invocation routes to debug
    const basePath = `http${httpsProtocol ? 's' : ''}://${host}:${lambdaPort}`
    const funcNamePairs = this.#lambda.listFunctionNamePairs()

    log.notice(
      [
        `Function names exposed for local invocation by aws-sdk:`,
        ...this.#lambda
          .listFunctionNames()
          .map(
            (functionName) =>
              `           * ${funcNamePairs[functionName]}: ${functionName}`,
          ),
      ].join('\n'),
    )
    log.debug(
      [
        `Lambda Invocation Routes (for AWS SDK or AWS CLI):`,
        ...this.#lambda
          .listFunctionNames()
          .map(
            (functionName) =>
              `           * ${
                invRoute.method
              } ${basePath}${invRoute.path.replace(
                '{functionName}',
                functionName,
              )}`,
          ),
      ].join('\n'),
    )

    log.debug(
      [
        `Lambda Async Invocation Routes (for AWS SDK or AWS CLI):`,
        ...this.#lambda
          .listFunctionNames()
          .map(
            (functionName) =>
              `           * ${
                invAsyncRoute.method
              } ${basePath}${invAsyncRoute.path.replace(
                '{functionName}',
                functionName,
              )}`,
          ),
      ].join('\n'),
    )
  }

  // stops the server
  stop(timeout) {
    return this.#server.stop({
      timeout,
    })
  }
}
<|endoftext|>


<commit_before>/*function getCards( game_state ) {
  var me = game_state.players[game_state.in_action];
  var myCards = me.hole_cards;

}; */

module.exports = {

  VERSION: "Awesome JS player",

  bet_request: function(game_state, bet) {
    var play = Math.floor( Math.random( ) * 10 );
    if( play > 3 ) {
      bet( 0 );
      return;
    }
    var me = game_state.players[game_state.in_action];
    var betAmount = game_state.current_buy_in - me.bet + game_state.minimum_raise;
    bet(betAmount);
  },

  showdown: function(game_state) {

  }
};
<commit_msg>Updating<commit_after>/*cardsSetup = {
  pair: function( cards ) {
    if( ! cards ) return false;

  },

  getCards: function( game_state ) {
    var me = game_state.players[game_state.in_action];
    var myCards = me.hole_cards;
    if( pair( myCards ))
  },
};*/

module.exports = {

  VERSION: "Awesome JS player",

  bet_request: function(game_state, bet) {
    var play = Math.floor( Math.random( ) * 10 );
    if( play > 3 ) {
      bet( 0 );
      return;
    }
    var me = game_state.players[game_state.in_action];
    var betAmount = game_state.current_buy_in - me.bet + game_state.minimum_raise;
    if( betAmount >= me.stack ) {
      bet( 0 );
      return;
    }
    bet(betAmount);
  },

  showdown: function(game_state) {

  }
};
<|endoftext|>


<commit_before>/*
 *  Pop Up Calendar
 *  By Paul Geerts
 *  11 Oct 2002
 *
 *  Designed as a replacement for current Tapestry DatePicker control
 *  to work on both IE5+ and Mozilla based browsers
*/

// Reference Data arrays
var months = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
var days = new Array("S", "M", "T", "W", "T", "F", "S");
var monthLengths =new Array(31,28,31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
var monthLongNames = new Array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');
var dayNames = new Array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday','Friday', 'Saturday');

// is the calendar created?
var calDiv = null;

// is the calendar showing?
var showing = false;

var pickArea = null;

// function to create the calendar if necessary and pop it up
// the button is the "V" button that triggers the popup event
function goCalendar(button) {
    if (showing) {   // if we're currently showing, stop it
        closeCalendar();
    }

    format = button.previousSibling;      // get the hidden text field containng the desired date format
    val = format.previousSibling;         // get the date as milliseconds since 1970
    disp = val.previousSibling;           // get the display field

    currentDate = new Date();             // create a new date to hold the
    if (val.value != "") {
        currentDate.setTime(val.value);   // start value
        oldDate = currentDate;            // Rememeber the old date so we can show it as blue
    } else {
        oldDate = null;
    }

    var point = getPoint(disp);

    if (!calDiv) {                        // if we haven't create the calendar, go to
        calDiv = document.createElement("DIV");
        calDiv.style.backgroundColor = "#cccccc";
        calDiv.style.border = "2px outset";
        calDiv.style.position = "absolute";
        calDiv.style.top = point.y + disp.offsetHeight + 1;
        calDiv.style.left = point.x;
        calDiv.style.fontFamily = "sans-serif";
        calDiv.appendChild(createCalendar(currentDate));
        document.body.appendChild(calDiv);
    } else {                             // otherwise, just move and unhide it
        calDiv.style.display = "block";
        calDiv.style.top = point.y + disp.offsetHeight + 1;
        calDiv.style.left = point.x;
        updatePickArea();               // update the selection area
    }

    showing = true;  // we are now showing

    // this needs to be done outsite a click event for some reason
    setTimeout(addBodyClick, 1);

}

function addBodyClick() {   // this adds the handler which hides the
                            // picker when something else is clicked
    addEvent("click", document.body, closeCalendar);
}

function createCalendar(date) {   // create calendar using W3C DOM methods

    var table = document.createElement("TABLE");
    var tbody = document.createElement("TBODY");
    table.appendChild(tbody);
    // create head row with dropdowns
    var headtr = document.createElement("TR");
    tbody.appendChild(headtr);
    headtr.appendChild(getHeadTD(currentDate));

    // create body TR with picker
    var bodytr = document.createElement("TR");
    tbody.appendChild(bodytr);
    pickArea = createPickArea();
    updatePickArea();
    bodytr.appendChild(pickArea);

    // create footer TR with "Today" link
    var foottr = document.createElement("TR");
    foottd = document.createElement("TD");
    foottr.appendChild(foottd);
    foottd.align = "middle";
    var today = new Date();
    foottd.innerHTML = "Today : " + months[today.getMonth()] + " " + today.getDate()  + ", " + today.getFullYear();
    foottd.date = new Date();
    foottd.align="center";
    foottd.style.fontSize="8pt";
    setCursor(foottd);
    foottd.style.backgroundColor="#aaaaaa";

    // add events using the approprate method for each browser
    addEvent("mouseover", foottd, tdMouseOver);
    addEvent("mouseout", foottd, tdMouseOut);
    addEvent("click", foottd, tdClick);
    tbody.appendChild(foottr);

    return table;

}

function getHeadTD(currentDate) {  // create dropdown TD
    var td = document.createElement("TD");
    td.align = "center";
    td.valign = "top";

    monthSelect = getMonthSelect(currentDate);  // remember these so we can update them later
    yearSelect = getYearSelect(currentDate);
    td.appendChild(monthSelect);
    td.appendChild(yearSelect);

    return td;
}

function getMonthSelect(currentDate) {  // return a select with a list of months

    var sel = document.createElement("SELECT");
    for (var i = 0 ; i < months.length ; i++) {
        var opt = document.createElement("OPTION");
        opt.innerHTML = monthLongNames[i];
        opt.value = i;
        if (i == currentDate.getMonth()) {
            opt.selected = true;
        }
        sel.appendChild(opt);
    }

    // add events
    addEvent("change", sel, changeMonth);

    // the doNuffin event is to cancel the bubble
    // without it, the calendar would disappear
    // when you clicked on the dropdpwn
    addEvent("click", sel, doNuffin);

    return sel;
}

function getYearSelect(currentDate) {   // generate a list
                                        // of years
    var sel = document.createElement("SELECT");
    var year = currentDate.getFullYear();
    for (var i = year - 10 ; i < year + 10 ; i++) {
        var opt = document.createElement("OPTION");
        opt.innerHTML = i;
        opt.value = i;
        if (i == year) {
            opt.selected = true;
        }
        sel.appendChild(opt);
    }

    addEvent("change", sel, changeYear);
    addEvent("click", sel, doNuffin);
    return sel;
}

function createPickArea() {    // create the area where the calendar actually sits
    var bigtd = document.createElement("TD");
    var table = document.createElement("TABLE");
    var tbody = document.createElement("TBODY");
    bigtd.appendChild(table);
    table.appendChild(tbody);
    table.style.width="100%";
    table.cellSpacing = 0;
    table.style.backgroundColor="#ffffff";
    var trhead = document.createElement("TR");

    // create the header labels
    for (var i = 0 ; i < 7 ; i ++) {
        var td = document.createElement("TD");
        td.innerHTML = days[i];
        td.align="center";
        td.style.fontSize="8pt";
        td.style.color = "#ffffff";
        td.style.backgroundColor = "#777777";
        trhead.appendChild(td);
    }
    tbody.appendChild(trhead);
    // create the grid for the
    // date picking area
    for (var i = 0 ; i < 6 ; i ++) {
        var tr = document.createElement("TR");
        for (var j = 0 ; j < 7 ; j ++) {
            var td = document.createElement("TD");
            td.style.fontSize="8pt";
            setCursor(td);
            td.innerHTML = i*7+j;
            td.align="center";
            // add events for highlighting and clicking
            addEvent("mouseover", td, tdMouseOver);
            addEvent("mouseout", td, tdMouseOut);
            addEvent("click", td, tdClick);
            tr.appendChild(td);
        }
        tbody.appendChild(tr);
    }

    bigtd.tbody = tbody;
    return bigtd;
}

function updatePickArea() {  // this changes the pick area to reflect the current date

    monthSelect.value = currentDate.getMonth();
    yearSelect.value = currentDate.getFullYear();
    var first = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
    var dayNumber = new Array();
    var monthOffset = new Array();
    var currdn = 0;

    // work out how what days from the previous month should be displayed
    for (var i = 0 ; i < first.getDay() ; i++) {
        var back = new Date();
        var daysback = first.getDay() - i;
        back.setTime(first - daysback * 1000 * 60 * 60 * 24);
        dayNumber[currdn] = back.getDate();
        monthOffset[currdn] = -1;
        currdn++;
    }

    // work out how what days from the current month should be displayed
    for (var i = 1 ; i <= monthLengths[first.getMonth()] ; i++) {
        dayNumber[currdn] = i;
        monthOffset[currdn] = 0;
        currdn++;
    }

    // work out how what days from the next month should be displayed
    var nm = 1;
    while (currdn < 42) {
        dayNumber[currdn] = nm++;
        monthOffset[currdn] = 1;
        currdn++;
    }

    // update the cells to the correct values
    var c = 0;
    for (var i = 1 ; i <= 6 ; i++) {
        row = pickArea.tbody.childNodes[i];
        for (var j = 0 ; j < 7 ; j++) {
            col = row.childNodes[j];
            col.innerHTML = dayNumber[c];
            // store the date for this cell as an ad-hoc property of the cell
            // using the month-offset like this could lead to having negative
            // months (months start at zero for some reason)
            // but the JS Date object works everything out fine
            col.date = new Date(currentDate.getFullYear(),
                                            currentDate.getMonth() + monthOffset[c],
                                            dayNumber[c]);

            // format the cell colour
            col.style.backgroundColor = "#ffffff";
            if (monthOffset[c]!=0) {
                col.style.color = "#aaaaaa";
            } else {
                col.style.color = "#000000";
            }

            // blue if current date
            if (oldDate != null &&
                col.date.getFullYear() == oldDate.getFullYear() &&
                col.date.getMonth() == oldDate.getMonth() &&
                col.date.getDate() == oldDate.getDate()) {
                col.style.color = "#0000FF";
            }
            c++;

        }
    }
}

function changeMonth(e) {    // event handler for changing month
    var sel = getEventObject(e);
    currentDate = new Date(currentDate.getFullYear(), sel.value, currentDate.getDate());
    updatePickArea();

}

function changeYear(e) {  // event handler for changing year
    var sel = getEventObject(e);
    currentDate = new Date(sel.value,currentDate.getMonth(), currentDate.getDate());
    updatePickArea();

}

function doNuffin(e) {  // cancels bubble, see comment on line 140
    e.cancelBubble = true;
}

// highlite the cell
function tdMouseOver(e) {
    var td = getEventObject(e);
    td.oldBackground = td.style.backgroundColor;
    td.style.backgroundColor = "yellow";
}

// put it back the way it was
function tdMouseOut(e) {
    var td = getEventObject(e);
    td.style.backgroundColor = td.oldBackground;
}

// select a date by clicking
function tdClick(e) {
    tdMouseOut(e);   // ensure bg colour is reset, because we are reusing the date picker
    var td = getEventObject(e);
    setDate(td.date);
}

function setDate(date) {   // update the date display and hidden value
    var dstring = formatDate(date, format.value);

    disp.value = dstring;
    val.value = date.getTime();
    closeCalendar();
}

function closeCalendar() {   // put the calendar away
    deleteEvent("click", document.body, closeCalendar);  // remove the event
    calDiv.style.display = "none";
    showing = false;
}

function getEventObject(e) {  // utility function to retrieve object from event
    if (navigator.appName == "Microsoft Internet Explorer") {
        return e.srcElement;
    } else {  // is mozilla/netscape
        // need to crawl up the tree to get the first "real" element
        // i.e. a tag, not raw text
        var o = e.target;
        while (!o.tagName) {
            o = o.parentNode;
        }
        return o;
    }
}

function pad(number,X) {   // utility function to pad a number to a given width
	X = (!X ? 2 : X);
	number = ""+number;
	while (number.length < X) {
	    number = "0" + number;
	}
	return number;
}

function formatDate(date, format) {  // formats the date based on the date format specified
    var bits = new Array();
    // work out what each bit should be
    bits['d'] = date.getDate();
    bits['dd'] = pad(date.getDate(),2);
    bits['dddd'] = dayNames[date.getDay()];

    bits['M'] = date.getMonth()+1;
    bits['MM'] = pad(date.getMonth()+1,2);
    bits['MMM'] = months[date.getMonth()];
    bits['MMMM'] = monthLongNames[date.getMonth()];

    var yearStr = "" + date.getFullYear();
    yearStr = (yearStr.length == 2) ? '19' + yearStr: yearStr;
    bits['yyyy'] = yearStr;
    bits['yy'] = bits['yyyy'].toString().substr(2,2);

    // do some funky regexs to replace the format string
    // with the real values
    var frm = new String(format);
    var sect;
    for (sect in bits) {
      frm = eval("frm.replace(/\\b" + sect + "\\b/,'" + bits[sect] + "');");
    }

    return frm;
}

function addEvent(name, obj, funct) { // utility function to add event handlers

    if (navigator.appName == "Microsoft Internet Explorer") {
        obj.attachEvent("on"+name, funct);
    } else {  // is mozilla/netscape
        obj.addEventListener(name, funct, false);
    }
}

function deleteEvent(name, obj, funct) { // utility function to delete event handlers

    if (navigator.appName == "Microsoft Internet Explorer") {
        obj.detachEvent("on"+name, funct);
    } else {  // is mozilla/netscape
        obj.removeEventListener(name, funct, false);
    }
}

function setCursor(obj) {
   if (navigator.appName == "Microsoft Internet Explorer") {
        obj.style.cursor = "hand";
    } else {  // is mozilla/netscape
        obj.style.cursor = "pointer";
    }
}

// Point x, y class
function Point(iX, iY)
{
   this.x = iX;
   this.y = iY;
}

// Get the Point of the given tag
function getPoint(aTag)
{
   var oTmp = aTag;
   var point = new Point(0,0);

   do
   {
      point.x += oTmp.offsetLeft;
      point.y += oTmp.offsetTop;
      oTmp = oTmp.offsetParent;
   }
   while (oTmp.tagName != "BODY");

   return point;
}
<commit_msg>Extended option list date range: 1920-2020<commit_after>/*
 *  Pop Up Calendar
 *  By Paul Geerts
 *  11 Oct 2002
 *
 *  Designed as a replacement for current Tapestry DatePicker control
 *  to work on both IE5+ and Mozilla based browsers
*/

// Reference Data arrays
var months = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
var days = new Array("S", "M", "T", "W", "T", "F", "S");
var monthLengths =new Array(31,28,31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
var monthLongNames = new Array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');
var dayNames = new Array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday','Friday', 'Saturday');

// is the calendar created?
var calDiv = null;

// is the calendar showing?
var showing = false;

var pickArea = null;

// function to create the calendar if necessary and pop it up
// the button is the "V" button that triggers the popup event
function goCalendar(button) {
    if (showing) {   // if we're currently showing, stop it
        closeCalendar();
    }

    format = button.previousSibling;      // get the hidden text field containng the desired date format
    val = format.previousSibling;         // get the date as milliseconds since 1970
    disp = val.previousSibling;           // get the display field

    currentDate = new Date();             // create a new date to hold the
    if (val.value != "") {
        currentDate.setTime(val.value);   // start value
        oldDate = currentDate;            // Rememeber the old date so we can show it as blue
    } else {
        oldDate = null;
    }

    var point = getPoint(disp);

    if (!calDiv) {                        // if we haven't create the calendar, go to
        calDiv = document.createElement("DIV");
        calDiv.style.backgroundColor = "#cccccc";
        calDiv.style.border = "2px outset";
        calDiv.style.position = "absolute";
        calDiv.style.top = point.y + disp.offsetHeight + 1;
        calDiv.style.left = point.x;
        calDiv.style.fontFamily = "sans-serif";
        calDiv.appendChild(createCalendar(currentDate));
        document.body.appendChild(calDiv);
    } else {                             // otherwise, just move and unhide it
        calDiv.style.display = "block";
        calDiv.style.top = point.y + disp.offsetHeight + 1;
        calDiv.style.left = point.x;
        updatePickArea();               // update the selection area
    }

    showing = true;  // we are now showing

    // this needs to be done outsite a click event for some reason
    setTimeout(addBodyClick, 1);

}

function addBodyClick() {   // this adds the handler which hides the
                            // picker when something else is clicked
    addEvent("click", document.body, closeCalendar);
}

function createCalendar(date) {   // create calendar using W3C DOM methods

    var table = document.createElement("TABLE");
    var tbody = document.createElement("TBODY");
    table.appendChild(tbody);
    // create head row with dropdowns
    var headtr = document.createElement("TR");
    tbody.appendChild(headtr);
    headtr.appendChild(getHeadTD(currentDate));

    // create body TR with picker
    var bodytr = document.createElement("TR");
    tbody.appendChild(bodytr);
    pickArea = createPickArea();
    updatePickArea();
    bodytr.appendChild(pickArea);

    // create footer TR with "Today" link
    var foottr = document.createElement("TR");
    foottd = document.createElement("TD");
    foottr.appendChild(foottd);
    foottd.align = "middle";
    var today = new Date();
    foottd.innerHTML = "Today : " + months[today.getMonth()] + " " + today.getDate()  + ", " + today.getFullYear();
    foottd.date = new Date();
    foottd.align="center";
    foottd.style.fontSize="8pt";
    setCursor(foottd);
    foottd.style.backgroundColor="#aaaaaa";

    // add events using the approprate method for each browser
    addEvent("mouseover", foottd, tdMouseOver);
    addEvent("mouseout", foottd, tdMouseOut);
    addEvent("click", foottd, tdClick);
    tbody.appendChild(foottr);

    return table;

}

function getHeadTD(currentDate) {  // create dropdown TD
    var td = document.createElement("TD");
    td.align = "center";
    td.valign = "top";

    monthSelect = getMonthSelect(currentDate);  // remember these so we can update them later
    yearSelect = getYearSelect(currentDate);
    td.appendChild(monthSelect);
    td.appendChild(yearSelect);

    return td;
}

function getMonthSelect(currentDate) {  // return a select with a list of months

    var sel = document.createElement("SELECT");
    for (var i = 0 ; i < months.length ; i++) {
        var opt = document.createElement("OPTION");
        opt.innerHTML = monthLongNames[i];
        opt.value = i;
        if (i == currentDate.getMonth()) {
            opt.selected = true;
        }
        sel.appendChild(opt);
    }

    // add events
    addEvent("change", sel, changeMonth);

    // the doNuffin event is to cancel the bubble
    // without it, the calendar would disappear
    // when you clicked on the dropdpwn
    addEvent("click", sel, doNuffin);

    return sel;
}

function getYearSelect(currentDate) {   // generate a list
                                        // of years
    var sel = document.createElement("SELECT");
    var year = currentDate.getFullYear();
    for (var i = 1920; i < 2021; i++) {
        var opt = document.createElement("OPTION");
        opt.innerHTML = i;
        opt.value = i;
        if (i == year) {
            opt.selected = true;
        }
        sel.appendChild(opt);
    }

    addEvent("change", sel, changeYear);
    addEvent("click", sel, doNuffin);
    return sel;
}

function createPickArea() {    // create the area where the calendar actually sits
    var bigtd = document.createElement("TD");
    var table = document.createElement("TABLE");
    var tbody = document.createElement("TBODY");
    bigtd.appendChild(table);
    table.appendChild(tbody);
    table.style.width="100%";
    table.cellSpacing = 0;
    table.style.backgroundColor="#ffffff";
    var trhead = document.createElement("TR");

    // create the header labels
    for (var i = 0 ; i < 7 ; i ++) {
        var td = document.createElement("TD");
        td.innerHTML = days[i];
        td.align="center";
        td.style.fontSize="8pt";
        td.style.color = "#ffffff";
        td.style.backgroundColor = "#777777";
        trhead.appendChild(td);
    }
    tbody.appendChild(trhead);
    // create the grid for the
    // date picking area
    for (var i = 0 ; i < 6 ; i ++) {
        var tr = document.createElement("TR");
        for (var j = 0 ; j < 7 ; j ++) {
            var td = document.createElement("TD");
            td.style.fontSize="8pt";
            setCursor(td);
            td.innerHTML = i*7+j;
            td.align="center";
            // add events for highlighting and clicking
            addEvent("mouseover", td, tdMouseOver);
            addEvent("mouseout", td, tdMouseOut);
            addEvent("click", td, tdClick);
            tr.appendChild(td);
        }
        tbody.appendChild(tr);
    }

    bigtd.tbody = tbody;
    return bigtd;
}

function updatePickArea() {  // this changes the pick area to reflect the current date

    monthSelect.value = currentDate.getMonth();
    yearSelect.value = currentDate.getFullYear();
    var first = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
    var dayNumber = new Array();
    var monthOffset = new Array();
    var currdn = 0;

    // work out how what days from the previous month should be displayed
    for (var i = 0 ; i < first.getDay() ; i++) {
        var back = new Date();
        var daysback = first.getDay() - i;
        back.setTime(first - daysback * 1000 * 60 * 60 * 24);
        dayNumber[currdn] = back.getDate();
        monthOffset[currdn] = -1;
        currdn++;
    }

    // work out how what days from the current month should be displayed
    for (var i = 1 ; i <= monthLengths[first.getMonth()] ; i++) {
        dayNumber[currdn] = i;
        monthOffset[currdn] = 0;
        currdn++;
    }

    // work out how what days from the next month should be displayed
    var nm = 1;
    while (currdn < 42) {
        dayNumber[currdn] = nm++;
        monthOffset[currdn] = 1;
        currdn++;
    }

    // update the cells to the correct values
    var c = 0;
    for (var i = 1 ; i <= 6 ; i++) {
        row = pickArea.tbody.childNodes[i];
        for (var j = 0 ; j < 7 ; j++) {
            col = row.childNodes[j];
            col.innerHTML = dayNumber[c];
            // store the date for this cell as an ad-hoc property of the cell
            // using the month-offset like this could lead to having negative
            // months (months start at zero for some reason)
            // but the JS Date object works everything out fine
            col.date = new Date(currentDate.getFullYear(),
                                            currentDate.getMonth() + monthOffset[c],
                                            dayNumber[c]);

            // format the cell colour
            col.style.backgroundColor = "#ffffff";
            if (monthOffset[c]!=0) {
                col.style.color = "#aaaaaa";
            } else {
                col.style.color = "#000000";
            }

            // blue if current date
            if (oldDate != null &&
                col.date.getFullYear() == oldDate.getFullYear() &&
                col.date.getMonth() == oldDate.getMonth() &&
                col.date.getDate() == oldDate.getDate()) {
                col.style.color = "#0000FF";
            }
            c++;

        }
    }
}

function changeMonth(e) {    // event handler for changing month
    var sel = getEventObject(e);
    currentDate = new Date(currentDate.getFullYear(), sel.value, currentDate.getDate());
    updatePickArea();

}

function changeYear(e) {  // event handler for changing year
    var sel = getEventObject(e);
    currentDate = new Date(sel.value,currentDate.getMonth(), currentDate.getDate());
    updatePickArea();

}

function doNuffin(e) {  // cancels bubble, see comment on line 140
    e.cancelBubble = true;
}

// highlite the cell
function tdMouseOver(e) {
    var td = getEventObject(e);
    td.oldBackground = td.style.backgroundColor;
    td.style.backgroundColor = "yellow";
}

// put it back the way it was
function tdMouseOut(e) {
    var td = getEventObject(e);
    td.style.backgroundColor = td.oldBackground;
}

// select a date by clicking
function tdClick(e) {
    tdMouseOut(e);   // ensure bg colour is reset, because we are reusing the date picker
    var td = getEventObject(e);
    setDate(td.date);
}

function setDate(date) {   // update the date display and hidden value
    var dstring = formatDate(date, format.value);

    disp.value = dstring;
    val.value = date.getTime();
    closeCalendar();
}

function closeCalendar() {   // put the calendar away
    deleteEvent("click", document.body, closeCalendar);  // remove the event
    calDiv.style.display = "none";
    showing = false;
}

function getEventObject(e) {  // utility function to retrieve object from event
    if (navigator.appName == "Microsoft Internet Explorer") {
        return e.srcElement;
    } else {  // is mozilla/netscape
        // need to crawl up the tree to get the first "real" element
        // i.e. a tag, not raw text
        var o = e.target;
        while (!o.tagName) {
            o = o.parentNode;
        }
        return o;
    }
}

function pad(number,X) {   // utility function to pad a number to a given width
	X = (!X ? 2 : X);
	number = ""+number;
	while (number.length < X) {
	    number = "0" + number;
	}
	return number;
}

function formatDate(date, format) {  // formats the date based on the date format specified
    var bits = new Array();
    // work out what each bit should be
    bits['d'] = date.getDate();
    bits['dd'] = pad(date.getDate(),2);
    bits['dddd'] = dayNames[date.getDay()];

    bits['M'] = date.getMonth()+1;
    bits['MM'] = pad(date.getMonth()+1,2);
    bits['MMM'] = months[date.getMonth()];
    bits['MMMM'] = monthLongNames[date.getMonth()];

    var yearStr = "" + date.getFullYear();
    yearStr = (yearStr.length == 2) ? '19' + yearStr: yearStr;
    bits['yyyy'] = yearStr;
    bits['yy'] = bits['yyyy'].toString().substr(2,2);

    // do some funky regexs to replace the format string
    // with the real values
    var frm = new String(format);
    var sect;
    for (sect in bits) {
      frm = eval("frm.replace(/\\b" + sect + "\\b/,'" + bits[sect] + "');");
    }

    return frm;
}

function addEvent(name, obj, funct) { // utility function to add event handlers

    if (navigator.appName == "Microsoft Internet Explorer") {
        obj.attachEvent("on"+name, funct);
    } else {  // is mozilla/netscape
        obj.addEventListener(name, funct, false);
    }
}

function deleteEvent(name, obj, funct) { // utility function to delete event handlers

    if (navigator.appName == "Microsoft Internet Explorer") {
        obj.detachEvent("on"+name, funct);
    } else {  // is mozilla/netscape
        obj.removeEventListener(name, funct, false);
    }
}

function setCursor(obj) {
   if (navigator.appName == "Microsoft Internet Explorer") {
        obj.style.cursor = "hand";
    } else {  // is mozilla/netscape
        obj.style.cursor = "pointer";
    }
}

// Point x, y class
function Point(iX, iY)
{
   this.x = iX;
   this.y = iY;
}

// Get the Point of the given tag
function getPoint(aTag)
{
   var oTmp = aTag;
   var point = new Point(0,0);

   do
   {
      point.x += oTmp.offsetLeft;
      point.y += oTmp.offsetTop;
      oTmp = oTmp.offsetParent;
   }
   while (oTmp.tagName != "BODY");

   return point;
}
<|endoftext|>