<commit_before>'use strict';

var debug = require('debug')('bigpipe:compiler')
  , preprocess = require('smithy')
  , mkdirp = require('mkdirp')
  , crypto = require('crypto')
  , async = require('async')
  , File = require('./file')
  , path = require('path')
  , fs = require('fs');

//
// Find the pipe.js client library.
//
var pipejs = path.join(require.resolve('pipe.js'), '../dist/pipe.js')
  , local = path.resolve(__dirname, '../dist/pipe.js');

/**
 * Asset compiler and management.
 *
 * @constructor
 * @param {String} directory The directory where we save our static files.
 * @param {Pipe} pipe The configured Pipe instance.
 * @param {Object} options Configuration.
 * @api private
 */
function Compiler(directory, pipe, options) {
  options = options || {};
  this.pipe = pipe;

  // The pipe.js client code.
  this.client = fs.readFileSync(pipejs, 'utf-8');

  // The namespace where we can download files.
  this.pathname = options.pathname || '/';

  // Directory to save the compiled files.
  this.dir = directory;

  // List of pre-compiled or previous compiled files.
  this.list = [];

  // Contains template engines that are used to render.
  this.core = [];

  this.buffer = Object.create(null); // Precompiled asset cache
  this.alias = Object.create(null);  // Path aliases.

  //
  // Create the provided directory, will short circuit if present.
  //
  mkdirp.sync(directory);
}

Compiler.prototype.__proto__ = require('eventemitter3').prototype;

/**
 * Create the BigPipe base front-end framework that's required for the handling
 * of the real-time connections and the initialisation of the arriving pagelets.
 *
 * @param {String} client Core libraries that need to be loaded on every page.
 * @api private
 */
Compiler.prototype.bigPipe = function bigPipe(client) {
  var library = [
    '//',
    '// Powered by BigPipe v'+ this.pipe.version,
    '// https://github.com/bigpipe/bigpipe',
    '//',

    this.pipe.primus.library(),
    client,
    this.core.join('\n')
  ].join('\n');

  debug('creating the pipe.js front-end library which contains the Primus, client information and core');
  return this.register(new File(library, '.js', local));
};

/**
 * Merge in objects.
 *
 * @param {Object} target The object that receives the props
 * @param {Object} additional Extra object that needs to be merged in the target
 * @api private
 */
Compiler.prototype.merge = function merge(target, additional) {
  var result = target
    , compiler = this
    , undefined;

  if (Array.isArray(target)) {
    compiler.forEach(additional, function arrayForEach(index) {
      if (JSON.stringify(target).indexOf(JSON.stringify(additional[index])) === -1) {
        result.push(additional[index]);
      }
    });
  } else if ('object' === typeof target) {
    compiler.forEach(additional, function objectForEach(key, value) {
      if (target[key] === undefined) {
        result[key] = value;
      } else {
        result[key] = compiler.merge(target[key], additional[key]);
      }
    });
  } else {
    result = additional;
  }

  return result;
};

/**
 * Iterate over a collection. When you return false, it will stop the iteration.
 *
 * @param {Mixed} collection Either an Array or Object.
 * @param {Function} iterator Function to be called for each item
 * @api private
 */
Compiler.prototype.forEach = function forEach(collection, iterator, context) {
  if (arguments.length === 1) {
    iterator = collection;
    collection = this;
  }

  var isArray = Array.isArray(collection || this)
    , length = collection.length
    , i = 0
    , value;

  if (context) {
    if (isArray) {
      for (; i < length; i++) {
        value = iterator.apply(collection[i], context);
        if (value === false) break;
      }
    } else {
      for (i in collection) {
        value = iterator.apply(collection[i], context);
        if (value === false) break;
      }
    }
  } else {
    if (isArray) {
      for (; i < length; i++) {
        value = iterator.call(collection[i], i, collection[i]);
        if (value === false) break;
      }
    } else {
      for (i in collection) {
        value = iterator.call(collection[i], i, collection[i]);
        if (value === false) break;
      }
    }
  }

  return this;
};

/**
 * Get the processed extension for a certain file.
 *
 * @param {String} filepath full path to file
 * @api public
 */
Compiler.prototype.type = function type(filepath) {
  var processor = this.processor(filepath);
  return processor ? '.' + processor.export : path.extname(filepath);
};

/**
 * Get preprocessor.
 *
 * @param {String} filepath
 * @returns {Function}
 * @api public
 */
Compiler.prototype.processor = function processor(filepath) {
  return preprocess[path.extname(filepath).substr(1)];
};

/**
 * Upsert new file in compiler cache.
 *
 * @param {String} filepath full path to file
 * @api private
 */
Compiler.prototype.put = function put(filepath) {
  var compiler = this;

  compiler.process(filepath, function processed(error, code) {
    if (error) return compiler.emit('error', error);

    compiler.emit('preprocessed', filepath);
    compiler.register(new File(code, compiler.type(filepath), filepath));
  });
};

/**
 * Read the file from disk and preprocess it depending on extension.
 *
 * @param {String} filepath full path to file
 * @param {Function} fn callback
 * @api private
 */
Compiler.prototype.process = function process(filepath, fn) {
  var processor = this.processor(filepath)
    , paths = [ path.dirname(filepath) ];

  fs.readFile(filepath, 'utf-8', function read(error, code) {
    if (error || !processor) return fn(error, code);

    //
    // Only preprocess the file if required.
    //
    processor(code, { location: filepath, paths: paths }, fn);
  });
};

/**
 * Prefix selectors of CSS with [data-pagelet='name'] to contain CSS to
 * specific pagelets. File can have the following properties.
 *
 * @param {File} file instance of File
 * @param {Function} fn completion callback.
 * @api public
 */
Compiler.prototype.namespace = function prefix(file, fn) {
  //
  // Only prefix if the code is CSS content.
  //
  if (!file.is('css')) return fn(null, file);

  var processor = preprocess.css
    , options = {}
    , pagelets;

  //
  // Transform the pagelets names to data selectors.
  //
  pagelets = file.pagelets.map(function prepare(pagelet) {
    return '[data-pagelet="'+ pagelet +'"]';
  });

  options.plugins = [ processor.plugins.namespace(pagelets) ];
  processor(file.code, options, function done(error, code) {
    if (error) return fn(error);
    fn(null, file.set(code, true));
  });
};

/**
 * Register a new library with the compiler. The following traits can be
 * provided to register a specific file.
 *
 * @param {File} file instance of File
 * @api private
 */
Compiler.prototype.register = function register(file) {
  var compiler = this;

  //
  // Add file to the buffer collection.
  //
  this.buffer[file.location] = file;

  //
  // Add file references to alias.
  //
  file.aliases.forEach(function add(alias) {
    compiler.alias[alias] = file.location;
  });

  this.emit('register', file);
  return this.save(file);
};

/**
 * Catalog the pages. As we're not caching the file look ups, this method can be
 * called when a file changes so we will generate new.
 *
 * @param {Array} pages The array of pages.
 * @param {Function} done callback
 * @api private
 */
Compiler.prototype.catalog = function catalog(pages, done) {
  var temper = this.pipe.temper
    , core = this.core
    , compiler = this
    , list = {};

  /**
   * Process the dependencies.
   *
   * @param {Object} assemble generated collection of file properties.
   * @param {String} filepath The location of a file.
   * @param {Function} next completion callback.
   * @api private
   */
  function prefab(assemble, filepath, next) {
    compiler.process(filepath, function store(error, code) {
      if (error) return next(error);

      var file = new File(code, compiler.type(filepath), filepath);
      file = file.hash in assemble ? assemble[file.hash] : file;

      file.pagelets = (file.pagelets || []).concat(list[filepath]);
      file.aliases = file.aliases.concat(filepath);

      assemble[file.hash] = file;
      next(null, assemble);
    });
  }

  /**
   * Register the files in the assembly, prefix CSS first.
   *
   * @param {Object} assemble generated collection of file properties.
   * @param {Function} next completion callback.
   * @api private
   */
  function register(assemble, next) {
    async.each(Object.keys(assemble), function prefix(hash, fn) {
      compiler.namespace(assemble[hash], function namespaced(error, file) {
        if (error) return fn(error);

        compiler.register(file);
        fn();
      });
    }, next);
  }

  //
  // Check all pages for dependencies and files to add to the list.
  //
  pages.forEach(function each(Page) {
    var dependencies = [local]
      , page = Page.prototype;

    page.pagelets.forEach(function each(Pagelet) {
      var pagelet = Pagelet.prototype
        , view;

      /**
       * Add file to the process list.
       *
       * @param {file} file path to file.
       * @api private
       */
      function add(file) {
        if (/^(http:|https:)?\/\//.test(file)) return;

        file = list[file] = list[file] || [];
        if (!~file.indexOf(pagelet.name)) file.push(pagelet.name);
      }

      if (pagelet.js) add(pagelet.js);
      if (pagelet.css) add(pagelet.css);

      pagelet.dependencies.forEach(function each(dependency) {
        add(dependency);

        if (!~dependencies.indexOf(dependency)) {
          dependencies.push(dependency);
        }
      });

      if (!pagelet.view) return;

      //
      // The views can be rendered on the client, but some of them require
      // a library, this library should be cached in the core library.
      //
      view = temper.fetch(pagelet.view);
      if (view.library && !~core.indexOf(view.library)) {
        core.push(view.library);
      }

      compiler.register(new File(view.client, '.js', pagelet.view));
    });

    page.dependencies = dependencies.reduce(function reduce(memo, dependency) {
      var extname = path.extname(dependency);

      memo[extname] = memo[extname] || [];
      memo[extname].push(dependency);

      return memo;
    }, Object.create(null));
  });

  //
  // Process and register the CSS/JS of all the pagelets.
  //
  async.waterfall([
    async.apply(async.reduce, Object.keys(list), {}, prefab),
    register
  ], done);

  //
  // Last, but not least, update the pipe.js library.
  //
  this.bigPipe(this.client);
};

/**
 * Find all required dependencies for given page constructor.
 *
 * @param {Page} page The initialised page.
 * @param {Array} assets The array where the assets needs to be pushed in.
 * @returns {Object}
 * @api private
 */
Compiler.prototype.page = function find(page, assets) {
  var compiler = this;

  //
  // The page is rendered in `sync` mode, so add all the required CSS files from
  // the pagelet to the head of the page.
  //
  if (!('.css' in page.dependencies)) page.dependencies['.css'] = [];
  if ('sync' === page.mode) page.enabled.forEach(function enabled(pagelet) {
    Array.prototype.push.apply(page.dependencies['.css'], compiler.pagelet(pagelet).css);
  });

  preprocess.extensions.concat('.css', '.js').forEach(function map(type) {
    if (!(type in page.dependencies)) return;

    page.dependencies[type].forEach(function each(dependency) {
      dependency = compiler.html(compiler.resolve(dependency));
      if (!~assets.indexOf(dependency)) assets.push(dependency);
    });
  });

  return this;
};

/**
 * Generate HTML.
 *
 * @param {String} file The filename that needs to be added to a DOM.
 * @returns {String} A correctly wrapped HTML tag.
 * @api private
 */
Compiler.prototype.html = function html(file) {
  switch (path.extname(file)) {
    case '.css': return '<link rel=stylesheet href="'+ file +'" />';
    case '.js': return '<script src="'+ file +'"></script>';
    default: return '';
  }
};

/**
 * Resolve all dependencies to their hashed versions.
 *
 * @param {String} original The original file path.
 * @returns {String} The hashed version.
 * @api private
 */
Compiler.prototype.resolve = function resolve(original) {
  return this.alias[original] || original;
};

/**
 * A list of resources that need to be loaded for the given pagelet.
 *
 * @param {Pagelet} pagelet The initialised pagelet.
 * @param {Boolean} streaming The pagelet is streaming, so include the view.
 * @returns {Object}
 * @api private
 */
Compiler.prototype.pagelet = function find(pagelet, streaming) {
  var frag = {}
    , css = []
    , js = [];

  debug('Compiling data from pagelet %s/%s', pagelet.name, pagelet.id);

  if (pagelet.js) js.push(this.resolve(pagelet.js));
  if (pagelet.css) css.push(this.resolve(pagelet.css));
  if (streaming && pagelet.view) js.push(this.resolve(pagelet.view));

  frag.css = css;               // Add the compiled css.
  frag.js = js;                 // Add the required js.

  return frag;
};

/**
 * Store the compiled files to disk. This a vital part of the compiler as we're
 * changing the file names every single time there is a change. But these files
 * can still be cached on the client and it would result in 404's and or broken
 * functionality.
 *
 * @param {File} file The file instance.
 * @api private
 */
Compiler.prototype.save = function save(file) {
  var directory = path.resolve(this.dir)
    , pathname = this.pathname;

  fs.writeFileSync(path.join(directory, file.location), file.buffer);

  this.list = fs.readdirSync(directory).reduce(function reduce(memo, file) {
    if (path.extname(file)) {
      memo[pathname + file] = path.resolve(directory, file);
    }

    return memo;
  }, {});

  return this;
};

/**
 * Serve the file.
 *
 * @param {Request} req Incoming HTTP request.
 * @param {Response} res Outgoing HTTP response.
 * @returns {Boolean} The request is handled by the compiler.
 * @api private
 */
Compiler.prototype.serve = function serve(req, res) {
  var file = (this.compiler || this).buffer[req.uri.pathname];

  if (!file) return undefined;

  res.setHeader('Content-Type', file.type);
  res.setHeader('Content-Length', file.length);
  res.end(file.buffer);

  return true;
};

//
// Expose the module.
//
module.exports = Compiler;
<commit_msg>[debug] Added some addition logging information to the compiler steps<commit_after>'use strict';

var debug = require('debug')('bigpipe:compiler')
  , preprocess = require('smithy')
  , mkdirp = require('mkdirp')
  , crypto = require('crypto')
  , async = require('async')
  , File = require('./file')
  , path = require('path')
  , fs = require('fs');

//
// Find the pipe.js client library.
//
var pipejs = path.join(require.resolve('pipe.js'), '../dist/pipe.js')
  , local = path.resolve(__dirname, '../dist/pipe.js');

/**
 * Asset compiler and management.
 *
 * @constructor
 * @param {String} directory The directory where we save our static files.
 * @param {Pipe} pipe The configured Pipe instance.
 * @param {Object} options Configuration.
 * @api private
 */
function Compiler(directory, pipe, options) {
  options = options || {};
  this.pipe = pipe;

  // The pipe.js client code.
  this.client = fs.readFileSync(pipejs, 'utf-8');

  // The namespace where we can download files.
  this.pathname = options.pathname || '/';

  // Directory to save the compiled files.
  this.dir = directory;

  // List of pre-compiled or previous compiled files.
  this.list = [];

  // Contains template engines that are used to render.
  this.core = [];

  this.buffer = Object.create(null); // Precompiled asset cache
  this.alias = Object.create(null);  // Path aliases.

  //
  // Create the provided directory, will short circuit if present.
  //
  mkdirp.sync(directory);
}

Compiler.prototype.__proto__ = require('eventemitter3').prototype;

/**
 * Create the BigPipe base front-end framework that's required for the handling
 * of the real-time connections and the initialisation of the arriving pagelets.
 *
 * @param {String} client Core libraries that need to be loaded on every page.
 * @api private
 */
Compiler.prototype.bigPipe = function bigPipe(client) {
  var library = [
    '//',
    '// Powered by BigPipe v'+ this.pipe.version,
    '// https://github.com/bigpipe/bigpipe',
    '//',

    this.pipe.primus.library(),
    client,
    this.core.join('\n')
  ].join('\n');

  debug('creating the pipe.js front-end library which contains the Primus, client information and core');
  return this.register(new File(library, '.js', local));
};

/**
 * Merge in objects.
 *
 * @param {Object} target The object that receives the props
 * @param {Object} additional Extra object that needs to be merged in the target
 * @api private
 */
Compiler.prototype.merge = function merge(target, additional) {
  var result = target
    , compiler = this
    , undefined;

  if (Array.isArray(target)) {
    compiler.forEach(additional, function arrayForEach(index) {
      if (JSON.stringify(target).indexOf(JSON.stringify(additional[index])) === -1) {
        result.push(additional[index]);
      }
    });
  } else if ('object' === typeof target) {
    compiler.forEach(additional, function objectForEach(key, value) {
      if (target[key] === undefined) {
        result[key] = value;
      } else {
        result[key] = compiler.merge(target[key], additional[key]);
      }
    });
  } else {
    result = additional;
  }

  return result;
};

/**
 * Iterate over a collection. When you return false, it will stop the iteration.
 *
 * @param {Mixed} collection Either an Array or Object.
 * @param {Function} iterator Function to be called for each item
 * @api private
 */
Compiler.prototype.forEach = function forEach(collection, iterator, context) {
  if (arguments.length === 1) {
    iterator = collection;
    collection = this;
  }

  var isArray = Array.isArray(collection || this)
    , length = collection.length
    , i = 0
    , value;

  if (context) {
    if (isArray) {
      for (; i < length; i++) {
        value = iterator.apply(collection[i], context);
        if (value === false) break;
      }
    } else {
      for (i in collection) {
        value = iterator.apply(collection[i], context);
        if (value === false) break;
      }
    }
  } else {
    if (isArray) {
      for (; i < length; i++) {
        value = iterator.call(collection[i], i, collection[i]);
        if (value === false) break;
      }
    } else {
      for (i in collection) {
        value = iterator.call(collection[i], i, collection[i]);
        if (value === false) break;
      }
    }
  }

  return this;
};

/**
 * Get the processed extension for a certain file.
 *
 * @param {String} filepath full path to file
 * @api public
 */
Compiler.prototype.type = function type(filepath) {
  var processor = this.processor(filepath);
  return processor ? '.' + processor.export : path.extname(filepath);
};

/**
 * Get preprocessor.
 *
 * @param {String} filepath
 * @returns {Function}
 * @api public
 */
Compiler.prototype.processor = function processor(filepath) {
  return preprocess[path.extname(filepath).substr(1)];
};

/**
 * Upsert new file in compiler cache.
 *
 * @param {String} filepath full path to file
 * @api private
 */
Compiler.prototype.put = function put(filepath) {
  var compiler = this;

  compiler.process(filepath, function processed(error, code) {
    if (error) return compiler.emit('error', error);

    compiler.emit('preprocessed', filepath);
    compiler.register(new File(code, compiler.type(filepath), filepath));
  });
};

/**
 * Read the file from disk and preprocess it depending on extension.
 *
 * @param {String} filepath full path to file
 * @param {Function} fn callback
 * @api private
 */
Compiler.prototype.process = function process(filepath, fn) {
  var processor = this.processor(filepath)
    , paths = [ path.dirname(filepath) ];

  fs.readFile(filepath, 'utf-8', function read(error, code) {
    if (error || !processor) return fn(error, code);

    //
    // Only preprocess the file if required.
    //
    processor(code, { location: filepath, paths: paths }, fn);
  });
};

/**
 * Prefix selectors of CSS with [data-pagelet='name'] to contain CSS to
 * specific pagelets. File can have the following properties.
 *
 * @param {File} file instance of File
 * @param {Function} fn completion callback.
 * @api public
 */
Compiler.prototype.namespace = function prefix(file, fn) {
  //
  // Only prefix if the code is CSS content.
  //
  if (!file.is('css')) return fn(null, file);

  var processor = preprocess.css
    , options = {}
    , pagelets;

  //
  // Transform the pagelets names to data selectors.
  //
  pagelets = file.pagelets.map(function prepare(pagelet) {
    return '[data-pagelet="'+ pagelet +'"]';
  });

  options.plugins = [ processor.plugins.namespace(pagelets) ];
  processor(file.code, options, function done(error, code) {
    if (error) return fn(error);
    fn(null, file.set(code, true));
  });
};

/**
 * Register a new library with the compiler. The following traits can be
 * provided to register a specific file.
 *
 * @param {File} file instance of File
 * @api private
 */
Compiler.prototype.register = function register(file) {
  var compiler = this;

  //
  // Add file to the buffer collection.
  //
  this.buffer[file.location] = file;

  //
  // Add file references to alias.
  //
  file.aliases.forEach(function add(alias) {
    compiler.alias[alias] = file.location;
  });

  this.emit('register', file);
  return this.save(file);
};

/**
 * Catalog the pages. As we're not caching the file look ups, this method can be
 * called when a file changes so we will generate new.
 *
 * @param {Array} pages The array of pages.
 * @param {Function} done callback
 * @api private
 */
Compiler.prototype.catalog = function catalog(pages, done) {
  var temper = this.pipe.temper
    , core = this.core
    , compiler = this
    , list = {};

  /**
   * Process the dependencies.
   *
   * @param {Object} assemble generated collection of file properties.
   * @param {String} filepath The location of a file.
   * @param {Function} next completion callback.
   * @api private
   */
  function prefab(assemble, filepath, next) {
    compiler.process(filepath, function store(error, code) {
      if (error) return next(error);

      var file = new File(code, compiler.type(filepath), filepath);
      file = file.hash in assemble ? assemble[file.hash] : file;

      file.pagelets = (file.pagelets || []).concat(list[filepath]);
      file.aliases = file.aliases.concat(filepath);

      assemble[file.hash] = file;
      next(null, assemble);
    });
  }

  /**
   * Register the files in the assembly, prefix CSS first.
   *
   * @param {Object} assemble generated collection of file properties.
   * @param {Function} next completion callback.
   * @api private
   */
  function register(assemble, next) {
    async.each(Object.keys(assemble), function prefix(hash, fn) {
      compiler.namespace(assemble[hash], function namespaced(error, file) {
        if (error) return fn(error);

        compiler.register(file);
        fn();
      });
    }, next);
  }

  //
  // Check all pages for dependencies and files to add to the list.
  //
  pages.forEach(function each(Page) {
    var dependencies = [local]
      , page = Page.prototype;

    page.pagelets.forEach(function each(Pagelet) {
      var pagelet = Pagelet.prototype
        , view;

      /**
       * Add file to the process list.
       *
       * @param {file} file path to file.
       * @api private
       */
      function add(file) {
        if (/^(http:|https:)?\/\//.test(file)) return;

        file = list[file] = list[file] || [];
        if (!~file.indexOf(pagelet.name)) file.push(pagelet.name);
      }

      if (pagelet.js) add(pagelet.js);
      if (pagelet.css) add(pagelet.css);

      pagelet.dependencies.forEach(function each(dependency) {
        add(dependency);

        if (!~dependencies.indexOf(dependency)) {
          dependencies.push(dependency);
        }
      });

      if (!pagelet.view) return;

      //
      // The views can be rendered on the client, but some of them require
      // a library, this library should be cached in the core library.
      //
      debug('Attempting to compile the view of '+ pagelet.name);
      view = temper.fetch(pagelet.view);

      if (view.library && !~core.indexOf(view.library)) {
        core.push(view.library);
      }

      compiler.register(new File(view.client, '.js', pagelet.view));
    });

    page.dependencies = dependencies.reduce(function reduce(memo, dependency) {
      var extname = path.extname(dependency);

      memo[extname] = memo[extname] || [];
      memo[extname].push(dependency);

      return memo;
    }, Object.create(null));
  });

  //
  // Process and register the CSS/JS of all the pagelets.
  //
  async.waterfall([
    async.apply(async.reduce, Object.keys(list), {}, prefab),
    register
  ], done);

  //
  // Last, but not least, update the pipe.js library.
  //
  this.bigPipe(this.client);
};

/**
 * Find all required dependencies for given page constructor.
 *
 * @param {Page} page The initialised page.
 * @param {Array} assets The array where the assets needs to be pushed in.
 * @returns {Object}
 * @api private
 */
Compiler.prototype.page = function find(page, assets) {
  var compiler = this;

  //
  // The page is rendered in `sync` mode, so add all the required CSS files from
  // the pagelet to the head of the page.
  //
  if (!('.css' in page.dependencies)) page.dependencies['.css'] = [];
  if ('sync' === page.mode) page.enabled.forEach(function enabled(pagelet) {
    Array.prototype.push.apply(page.dependencies['.css'], compiler.pagelet(pagelet).css);
  });

  preprocess.extensions.concat('.css', '.js').forEach(function map(type) {
    if (!(type in page.dependencies)) return;

    page.dependencies[type].forEach(function each(dependency) {
      dependency = compiler.html(compiler.resolve(dependency));
      if (!~assets.indexOf(dependency)) assets.push(dependency);
    });
  });

  return this;
};

/**
 * Generate HTML.
 *
 * @param {String} file The filename that needs to be added to a DOM.
 * @returns {String} A correctly wrapped HTML tag.
 * @api private
 */
Compiler.prototype.html = function html(file) {
  switch (path.extname(file)) {
    case '.css': return '<link rel=stylesheet href="'+ file +'" />';
    case '.js': return '<script src="'+ file +'"></script>';
    default: return '';
  }
};

/**
 * Resolve all dependencies to their hashed versions.
 *
 * @param {String} original The original file path.
 * @returns {String} The hashed version.
 * @api private
 */
Compiler.prototype.resolve = function resolve(original) {
  return this.alias[original] || original;
};

/**
 * A list of resources that need to be loaded for the given pagelet.
 *
 * @param {Pagelet} pagelet The initialised pagelet.
 * @param {Boolean} streaming The pagelet is streaming, so include the view.
 * @returns {Object}
 * @api private
 */
Compiler.prototype.pagelet = function find(pagelet, streaming) {
  var frag = {}
    , css = []
    , js = [];

  debug('Compiling data from pagelet %s/%s', pagelet.name, pagelet.id);

  if (pagelet.js) js.push(this.resolve(pagelet.js));
  if (pagelet.css) css.push(this.resolve(pagelet.css));
  if (streaming && pagelet.view) js.push(this.resolve(pagelet.view));

  frag.css = css;               // Add the compiled css.
  frag.js = js;                 // Add the required js.

  return frag;
};

/**
 * Store the compiled files to disk. This a vital part of the compiler as we're
 * changing the file names every single time there is a change. But these files
 * can still be cached on the client and it would result in 404's and or broken
 * functionality.
 *
 * @param {File} file The file instance.
 * @api private
 */
Compiler.prototype.save = function save(file) {
  var directory = path.resolve(this.dir)
    , pathname = this.pathname;

  fs.writeFileSync(path.join(directory, file.location), file.buffer);

  this.list = fs.readdirSync(directory).reduce(function reduce(memo, file) {
    if (path.extname(file)) {
      memo[pathname + file] = path.resolve(directory, file);
    }

    return memo;
  }, {});

  return this;
};

/**
 * Serve the file.
 *
 * @param {Request} req Incoming HTTP request.
 * @param {Response} res Outgoing HTTP response.
 * @returns {Boolean} The request is handled by the compiler.
 * @api private
 */
Compiler.prototype.serve = function serve(req, res) {
  var file = (this.compiler || this).buffer[req.uri.pathname];

  if (!file) return undefined;

  res.setHeader('Content-Type', file.type);
  res.setHeader('Content-Length', file.length);
  res.end(file.buffer);

  return true;
};

//
// Expose the module.
//
module.exports = Compiler;
<|endoftext|>


<commit_before>
/*!
 * Jade - Compiler
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var nodes = require('./nodes')
  , filters = require('./filters')
  , doctypes = require('./doctypes')
  , selfClosing = require('./self-closing')
  , utils = require('./utils');

/**
 * Initialize `Compiler` with the given `node`.
 *
 * @param {Node} node
 * @param {Object} options
 * @api public
 */

var Compiler = module.exports = function Compiler(node, options) {
  this.options = options = options || {};
  this.node = node;
};

/**
 * Compiler prototype.
 */

Compiler.prototype = {

  /**
   * Compile parse tree to JavaScript.
   *
   * @api public
   */

  compile: function(){
    this.buf = [];
    this.visit(this.node);
    return this.buf.join('\n');
  },

  /**
   * Buffer the given `str` optionally escaped.
   *
   * @param {String} str
   * @param {Boolean} esc
   * @api public
   */

  buffer: function(str, esc){
    if (esc) str = utils.escape(str);
    this.buf.push("buf.push('" + str + "');");
  },

  /**
   * Buffer the given `node`'s lineno.
   *
   * @param {Node} node
   * @api public
   */

  line: function(node){
    if (node.instrumentLineNumber === false) return;
    this.buf.push('__.lineno = ' + node.line + ';');
  },

  /**
   * Visit `node`.
   *
   * @param {Node} node
   * @api public
   */

  visit: function(node){
    this.line(node);
    return this.visitNode(node);
  },

  /**
   * Visit `node`.
   *
   * @param {Node} node
   * @api public
   */

  visitNode: function(node){
    return this['visit' + node.constructor.name](node);
  },

  /**
   * Visit all nodes in `block`.
   *
   * @param {Block} block
   * @api public
   */

  visitBlock: function(block){
    for (var i = 0, len = block.length; i < len; ++i) {
      this.visit(block[i]);
    }
  },

  /**
   * Visit `doctype`. Sets terse mode to `true` when html 5
   * is used, causing self-closing tags to end with ">" vs "/>",
   * and boolean attributes are not mirrored.
   *
   * @param {Doctype} doctype
   * @api public
   */

  visitDoctype: function(doctype){
    var name = doctype.val;
    if ('5' == name) this.terse = true;
    doctype = doctypes[name || 'default'];
    this.xml = 0 == doctype.indexOf('<?xml');
    if (!doctype) throw new Error('unknown doctype "' + name + '"');
    this.buffer(doctype);
  },

  /**
   * Visit `tag` buffering tag markup, generating
   * attributes, visiting the `tag`'s code and block.
   *
   * @param {Tag} tag
   * @api public
   */

  visitTag: function(tag){
    var name = tag.name;

    if (~selfClosing.indexOf(name) && !this.xml) {
      this.buffer('<' + name);
      this.visitAttributes(tag.attrs);
      this.terse
        ? this.buffer('>')
        : this.buffer('/>');
    } else {
      // Optimize attributes buffering
      if (tag.attrs.length) {
        this.buffer('<' + name);
        if (tag.attrs.length) this.visitAttributes(tag.attrs);
        this.buffer('>');
      } else {
        this.buffer('<' + name + '>');
      }
      if (tag.code) this.visitCode(tag.code);
      if (tag.text) this.buffer(utils.text(tag.text[0].trimLeft()));
      this.escape = 'pre' == tag.name;
      this.visit(tag.block);
      this.buffer('</' + name + '>');
    }
  },

  /**
   * Visit `filter`, throwing when the filter does not exist.
   *
   * @param {Filter} filter
   * @api public
   */

  visitFilter: function(filter){
    var fn = filters[filter.name];
    if (!fn) throw new Error('unknown filter ":' + filter.name + '"');
    if (filter.block instanceof nodes.Block) {
      this.buf.push(fn(filter.block, this, filter.attrs));
    } else {
      this.buffer(fn(utils.text(filter.block.join('\\n')), filter.attrs));
    }
  },

  /**
   * Visit `text` node.
   *
   * @param {Text} text
   * @api public
   */

  visitText: function(text){
    text = utils.text(text.join('\\n'));
    if (this.escape) text = escape(text);
    this.buffer(text);
    this.buffer('\\n');
  },

  /**
   * Visit a `comment`, only buffering when the buffer flag is set.
   *
   * @param {Comment} comment
   * @api public
   */

  visitComment: function(comment){
    if (!comment.buffer) return;
    this.buffer('<!--' + utils.escape(comment.val) + '-->');
  },

  /**
   * Visit a `BlockComment`.
   *
   * @param {Comment} comment
   * @api public
   */

  visitBlockComment: function(comment){
    if (0 == comment.val.indexOf('if')) {
      this.buffer('<!--[' + comment.val + ']>');
      this.visit(comment.block);
      this.buffer('<![endif]-->');
    } else {
      this.buffer('<!--' + comment.val);
      this.visit(comment.block);
      this.buffer('-->');
    }
  },

  /**
   * Visit `code`, respecting buffer / escape flags.
   * If the code is followed by a block, wrap it in
   * a self-calling function.
   *
   * @param {Code} code
   * @api public
   */

  visitCode: function(code){
    // Wrap code blocks with {}.
    // we only wrap unbuffered code blocks ATM
    // since they are usually flow control

    // Buffer code
    if (code.buffer) {
      var val = code.val.trimLeft();
      val = '(' + val + ') == null ? "" : (' + val + ')';
      if (code.escape) val = 'escape(' + val + ')';
      this.buf.push("buf.push(" + val + ");");
    } else {
      this.buf.push(code.val);
    }

    // Block support
    if (code.block) {
      if (!code.buffer) this.buf.push('{');
      this.visit(code.block);
      if (!code.buffer) this.buf.push('}');
    }
  },

  /**
   * Visit `each` block.
   *
   * @param {Each} each
   * @api public
   */

  visitEach: function(each){
    this.buf.push(''
      + '// iterate ' + each.obj + '\n'
      + '(function(){\n'
      + '  if (\'number\' == typeof ' + each.obj + '.length) {\n'
      + '    for (var ' + each.key + ' = 0, $$l = ' + each.obj + '.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n'
      + '      var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');

    this.visit(each.block);

    this.buf.push(''
      + '    }\n'
      + '  } else {\n'
      + '    for (var ' + each.key + ' in ' + each.obj + ') {\n'
      + '      var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');

    this.visit(each.block);

    this.buf.push('   }\n  }\n}).call(this);\n');
  },

  /**
   * Visit `attrs`.
   *
   * @param {Array} attrs
   * @api public
   */

  visitAttributes: function(attrs){
    var buf = []
      , classes = [];
    if (this.terse) buf.push('terse: true');
    attrs.forEach(function(attr){
      if (attr.name == 'class') {
        classes.push('(' + attr.val + ')');
      } else {
        var pair = "'" + attr.name + "':(" + attr.val + ')';
        buf.push(pair);
      }
    });
    if (classes.length) {
      classes = classes.join(" + ' ' + ");
      buf.push("class: " + classes);
    }
    this.buf.push("buf.push(attrs({ " + buf.join(', ') + " }));");
  }
};

/**
 * Escape the given string of `html`.
 *
 * @param {String} html
 * @return {String}
 * @api private
 */

function escape(html){
  return String(html)
    .replace(/&(?!\w+;)/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"');
}
<commit_msg>Fixed issue with expressions being evaluated several times. closes #162<commit_after>
/*!
 * Jade - Compiler
 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var nodes = require('./nodes')
  , filters = require('./filters')
  , doctypes = require('./doctypes')
  , selfClosing = require('./self-closing')
  , utils = require('./utils');

/**
 * Initialize `Compiler` with the given `node`.
 *
 * @param {Node} node
 * @param {Object} options
 * @api public
 */

var Compiler = module.exports = function Compiler(node, options) {
  this.options = options = options || {};
  this.node = node;
};

/**
 * Compiler prototype.
 */

Compiler.prototype = {

  /**
   * Compile parse tree to JavaScript.
   *
   * @api public
   */

  compile: function(){
    this.buf = [];
    this.visit(this.node);
    return this.buf.join('\n');
  },

  /**
   * Buffer the given `str` optionally escaped.
   *
   * @param {String} str
   * @param {Boolean} esc
   * @api public
   */

  buffer: function(str, esc){
    if (esc) str = utils.escape(str);
    this.buf.push("buf.push('" + str + "');");
  },

  /**
   * Buffer the given `node`'s lineno.
   *
   * @param {Node} node
   * @api public
   */

  line: function(node){
    if (node.instrumentLineNumber === false) return;
    this.buf.push('__.lineno = ' + node.line + ';');
  },

  /**
   * Visit `node`.
   *
   * @param {Node} node
   * @api public
   */

  visit: function(node){
    this.line(node);
    return this.visitNode(node);
  },

  /**
   * Visit `node`.
   *
   * @param {Node} node
   * @api public
   */

  visitNode: function(node){
    return this['visit' + node.constructor.name](node);
  },

  /**
   * Visit all nodes in `block`.
   *
   * @param {Block} block
   * @api public
   */

  visitBlock: function(block){
    for (var i = 0, len = block.length; i < len; ++i) {
      this.visit(block[i]);
    }
  },

  /**
   * Visit `doctype`. Sets terse mode to `true` when html 5
   * is used, causing self-closing tags to end with ">" vs "/>",
   * and boolean attributes are not mirrored.
   *
   * @param {Doctype} doctype
   * @api public
   */

  visitDoctype: function(doctype){
    var name = doctype.val;
    if ('5' == name) this.terse = true;
    doctype = doctypes[name || 'default'];
    this.xml = 0 == doctype.indexOf('<?xml');
    if (!doctype) throw new Error('unknown doctype "' + name + '"');
    this.buffer(doctype);
  },

  /**
   * Visit `tag` buffering tag markup, generating
   * attributes, visiting the `tag`'s code and block.
   *
   * @param {Tag} tag
   * @api public
   */

  visitTag: function(tag){
    var name = tag.name;

    if (~selfClosing.indexOf(name) && !this.xml) {
      this.buffer('<' + name);
      this.visitAttributes(tag.attrs);
      this.terse
        ? this.buffer('>')
        : this.buffer('/>');
    } else {
      // Optimize attributes buffering
      if (tag.attrs.length) {
        this.buffer('<' + name);
        if (tag.attrs.length) this.visitAttributes(tag.attrs);
        this.buffer('>');
      } else {
        this.buffer('<' + name + '>');
      }
      if (tag.code) this.visitCode(tag.code);
      if (tag.text) this.buffer(utils.text(tag.text[0].trimLeft()));
      this.escape = 'pre' == tag.name;
      this.visit(tag.block);
      this.buffer('</' + name + '>');
    }
  },

  /**
   * Visit `filter`, throwing when the filter does not exist.
   *
   * @param {Filter} filter
   * @api public
   */

  visitFilter: function(filter){
    var fn = filters[filter.name];
    if (!fn) throw new Error('unknown filter ":' + filter.name + '"');
    if (filter.block instanceof nodes.Block) {
      this.buf.push(fn(filter.block, this, filter.attrs));
    } else {
      this.buffer(fn(utils.text(filter.block.join('\\n')), filter.attrs));
    }
  },

  /**
   * Visit `text` node.
   *
   * @param {Text} text
   * @api public
   */

  visitText: function(text){
    text = utils.text(text.join('\\n'));
    if (this.escape) text = escape(text);
    this.buffer(text);
    this.buffer('\\n');
  },

  /**
   * Visit a `comment`, only buffering when the buffer flag is set.
   *
   * @param {Comment} comment
   * @api public
   */

  visitComment: function(comment){
    if (!comment.buffer) return;
    this.buffer('<!--' + utils.escape(comment.val) + '-->');
  },

  /**
   * Visit a `BlockComment`.
   *
   * @param {Comment} comment
   * @api public
   */

  visitBlockComment: function(comment){
    if (0 == comment.val.indexOf('if')) {
      this.buffer('<!--[' + comment.val + ']>');
      this.visit(comment.block);
      this.buffer('<![endif]-->');
    } else {
      this.buffer('<!--' + comment.val);
      this.visit(comment.block);
      this.buffer('-->');
    }
  },

  /**
   * Visit `code`, respecting buffer / escape flags.
   * If the code is followed by a block, wrap it in
   * a self-calling function.
   *
   * @param {Code} code
   * @api public
   */

  visitCode: function(code){
    // Wrap code blocks with {}.
    // we only wrap unbuffered code blocks ATM
    // since they are usually flow control

    // Buffer code
    if (code.buffer) {
      var val = code.val.trimLeft();
      this.buf.push('var __val__ = ' + val);
      val = 'null == __val__ ? "" : __val__';
      if (code.escape) val = 'escape(' + val + ')';
      this.buf.push("buf.push(" + val + ");");
    } else {
      this.buf.push(code.val);
    }

    // Block support
    if (code.block) {
      if (!code.buffer) this.buf.push('{');
      this.visit(code.block);
      if (!code.buffer) this.buf.push('}');
    }
  },

  /**
   * Visit `each` block.
   *
   * @param {Each} each
   * @api public
   */

  visitEach: function(each){
    this.buf.push(''
      + '// iterate ' + each.obj + '\n'
      + '(function(){\n'
      + '  if (\'number\' == typeof ' + each.obj + '.length) {\n'
      + '    for (var ' + each.key + ' = 0, $$l = ' + each.obj + '.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n'
      + '      var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');

    this.visit(each.block);

    this.buf.push(''
      + '    }\n'
      + '  } else {\n'
      + '    for (var ' + each.key + ' in ' + each.obj + ') {\n'
      + '      var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');

    this.visit(each.block);

    this.buf.push('   }\n  }\n}).call(this);\n');
  },

  /**
   * Visit `attrs`.
   *
   * @param {Array} attrs
   * @api public
   */

  visitAttributes: function(attrs){
    var buf = []
      , classes = [];
    if (this.terse) buf.push('terse: true');
    attrs.forEach(function(attr){
      if (attr.name == 'class') {
        classes.push('(' + attr.val + ')');
      } else {
        var pair = "'" + attr.name + "':(" + attr.val + ')';
        buf.push(pair);
      }
    });
    if (classes.length) {
      classes = classes.join(" + ' ' + ");
      buf.push("class: " + classes);
    }
    this.buf.push("buf.push(attrs({ " + buf.join(', ') + " }));");
  }
};

/**
 * Escape the given string of `html`.
 *
 * @param {String} html
 * @return {String}
 * @api private
 */

function escape(html){
  return String(html)
    .replace(/&(?!\w+;)/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"');
}
<|endoftext|>


<commit_before>/*
MagJS v0.25.5
http://github.com/magnumjs/mag.js
(c) Michael Glazer
License: MIT
*/
(function(mag) {

  'use strict';

  var modules = [],
    MAGNUM = mag.MAGNUM,
    controllers = []

  var mod = {
    innerMods: [],
    cache: []
  }

  mod.getState = function(index) {
    return modules[index][1]
  }
  mod.setState = function(index, state) {
    modules[index][1] = state
  }
  mod.getView = function(index) {
    return modules[index][0]
  }
  mod.getProps = function(index) {
    return modules[index][2]
  }
  mod.setProps = function(index, props) {
    return modules[index][2] = props
  }
  mod.remove = function(index) {
    modules.splice(index, 1)
  }
  mod.getId = function(index) {
    return modules[index] && modules[index][3]
  }
  mod.exists = function(index) {
    return typeof modules[index] != 'undefined' ? true : false
  }
  mod.setFrameId = function(index, fid) {
    modules[index][4] = fid;
  }
  mod.getFrameId = function(index) {
    return modules[index][4];
  }
  mod.submodule = function(id, index, module, props) {
    if (modules[index]) {
      // new call to existing
      // update props, merge with existing if same key
      if (props.key && props.key === mod.getProps(index).key) {
        mod.setProps(index, mag.utils.copy(mag.utils.merge(mod.getProps(index), props)));
      } else {
        mod.setProps(index, props);
      }

      // reinitialize the controller ?
      return modules[index]
    }
    modules[index] = [0, 0, 0, 0, 0]
    mod.setProps(index, props);
    var controller = function(context) {
        return (module.controller || function() {}).call(context, mod.getProps(index)) || context
      },
      view = function(index, state, ele) {
        module.view && module.view.call(module, state, mod.getProps(index), ele)
      }.bind({}, index),
      output = {
        controller: controller,
        view: view
      }
    modules[index][0] = output.view;
    modules[index][3] = id;
    modules[index][1] = getController(output.controller, index, id);
    // register the module
    return modules[index]
  }

  var timers = [];

  function findMissing(change, element) {
    var prop = change.name;
    if (typeof change.object[change.name] == 'undefined' && prop[0] !== '_') {
      // prop might be hierarchical?
      // getparent Object property chain?

      // get value of property from DOM
      var a = mag.fill.find(element, prop),
        greedy = prop[0] === '$',
        // can be an array, object or string
        // for each
        tmp = [];

        if(!a.length) return MAGNUM;

      a.forEach(function(item) {
        var i;
        if (item && item.type && !~['submit', 'button'].indexOf(item.type)) {
          if (item.value && item.value.length > 0) {

            if (item.type == 'checkbox' || item.type == 'radio') {

              if (item.checked) {
                i = {};
                i._checked = true;
              }
              if (!greedy) {
                //if grouped name item
                var items = element.querySelectorAll('[name=' + item.name + ']');
                if (items.length) {
                  for (var sitem of items) {
                    if (sitem.checked) {
                      if (i) {
                        i._value = sitem.value;
                      } else {
                        i = {
                          _value: sitem.value
                        };
                      }
                    }
                  }
                }
              } else {
                if (i) {
                  i._value = item.value;
                } else {
                  i = {
                    _value: item.value
                  };
                }
              }
            } else {
              i = {
                _value: item.value
              };
            }
          }
        } else if ((typeof change.oldValue == 'undefined' && typeof change.object[change.name] == 'undefined') && item && !~['submit', 'button'].indexOf(item.type) && item.childNodes.length == 1 && item.childNodes[0].textContent.trim()) {
          i = {
            _text: item.childNodes[0].textContent.trim()
          };
        }
        if (i) tmp.push(i);
        else if(item && !item.hasChildNodes()){
          tmp.push(MAGNUM)
        }

      });

      if (tmp.length === 0) return;
      else if (greedy) return tmp;
      else {
        return tmp[0] && typeof tmp[0]._value != 'undefined' ? tmp[0]._value : tmp[0] && tmp[0]._text ? tmp[0]._text : tmp[0]
      }
    }
  }

  var findParentNodeWithPath = function(node, path) {
    var snode = node;
    var parts = path.split('/');

    for (var prop of parts) {
      if (prop) {
        var a = mag.fill.find(snode, prop);
        if (a.length) {
          snode = a[0];
        }
      }
    }
    return snode
  };

  var handler = function(type, index, change) {

    if (change.type == 'get' && type != 'props' && !~mag.fill.ignorekeys.indexOf(change.name.toString()) && typeof change.oldValue == 'undefined') {

      var rootNode = mag.doc.getElementById(mod.getId(index));

      //Get parent correct parent not just root!?

      if (change.path && change.path[0] == '/') var fnode = findParentNodeWithPath(rootNode, change.path)

      var res = findMissing(change, fnode ? fnode : rootNode);

      if (typeof res != 'undefined' && typeof res == 'object' && change.object) {
        mag.utils.merge(res, change.object[change.name]);
      }
      if (typeof res != 'undefined') {
        mod.cached[index] = 0;
        return res;
      }
    } else if (change.type == 'set' && type != 'props' && change.object[change.name] && change.object[change.name].draw && typeof change.object[change.name].draw == 'function') {

      // setting a state prop to a module

      // call unloader for module
      mod.innerMods[mod.getId(index)] = [change.name, change.object[change.name]];
    }

    // call setup handler
    var fun = mod.getFrameId(index);
    if (typeof fun == 'function' && change.type == 'set') {
      //debounce
      cancelAnimationFrame(timers[index]);
      timers[index] = requestAnimationFrame(fun);
    }

  };

  function getController(ctrl, index, id) {
    var controller;

    mod.setProps(index, mag.proxy(mod.getProps(index), handler.bind({}, 'props', index), 'prop'));

    controller = new ctrl(mag.proxy({}, handler.bind({}, 'state', index)));

    return controller;
  }

  mod.iscached = function(key) {
    var data = mag.utils.merge(mag.utils.copy(mod.getProps(key)), mod.getState(key));
    if (mod.cache[key] && mod.cache[key] === JSON.stringify(data)) {
      return true
    }
    mod.cache[key] = JSON.stringify(data);
  }

  mod.clear = function(key) {
    if (key > -1 && mod.cache[key]) {
      mod.cache.splice(key, 1);
    }
  }

  mod.cached = [];
  mod.callView = function(node, index) {
    //TODO: ASYNC, promises .. ?
    mod.runningViewInstance = index;
    if (!mod.cached[index]) {
      mag.props.attachToArgs(index, mod.getState(index), node)
      mod.cached[index] = 1
    }
    mod.getView(index)(mod.getState(index), node)
  }

  mag.mod = mod;

}(mag));
<commit_msg>Update module.js<commit_after>/*
MagJS v0.26.3
http://github.com/magnumjs/mag.js
(c) Michael Glazer
License: MIT
*/
(function(mag) {

  'use strict';

  var modules = [],
    MAGNUM = mag.MAGNUM,
    controllers = []

  var mod = {
    innerMods: [],
    cache: []
  }

  mod.getState = function(index) {
    return modules[index][1]
  }
  mod.setState = function(index, state) {
    modules[index][1] = state
  }
  mod.getView = function(index) {
    return modules[index][0]
  }
  mod.getProps = function(index) {
    return modules[index][2]
  }
  mod.setProps = function(index, props) {
    return modules[index][2] = props
  }
  mod.remove = function(index) {
    modules.splice(index, 1)
  }
  mod.getId = function(index) {
    return modules[index] && modules[index][3]
  }
  mod.exists = function(index) {
    return typeof modules[index] != 'undefined' ? true : false
  }
  mod.setFrameId = function(index, fid) {
    modules[index][4] = fid;
  }
  mod.getFrameId = function(index) {
    return modules[index][4];
  }
  mod.submodule = function(id, index, module, props) {
    if (modules[index]) {
      // new call to existing
      // update props, merge with existing if same key
      if (props.key && props.key === mod.getProps(index).key) {
        mod.setProps(index, mag.utils.copy(mag.utils.merge(mod.getProps(index), props)));
      } else {
        mod.setProps(index, props);
      }

      // reinitialize the controller ?
      return modules[index]
    }
    modules[index] = [0, 0, 0, 0, 0]
    mod.setProps(index, props);
    var controller = function(context) {
        return (module.controller || function() {}).call(context, mod.getProps(index)) || context
      },
      view = function(index, state, ele) {
        module.view && module.view.call(module, state, mod.getProps(index), ele)
      }.bind({}, index),
      output = {
        controller: controller,
        view: view
      }
    modules[index][0] = output.view;
    modules[index][3] = id;
    modules[index][1] = getController(output.controller, index, id);
    // register the module
    return modules[index]
  }

  var timers = [];

  function findMissing(change, element) {
    var prop = change.name;
    if (typeof change.object[change.name] == 'undefined' && prop[0] !== '_') {
      // prop might be hierarchical?
      // getparent Object property chain?

      // get value of property from DOM
      var a = mag.fill.find(element, prop),
        greedy = prop[0] === '$',
        // can be an array, object or string
        // for each
        tmp = [];

      if (!a.length) return MAGNUM;

      a.forEach(function(item) {
        var i;
        if (item && item.type && !~['submit', 'button'].indexOf(item.type)) {
          if (item.value && item.value.length > 0) {

            if (item.type == 'checkbox' || item.type == 'radio') {

              if (item.checked) {
                i = {};
                i._checked = true;
              }
              if (!greedy) {
                //if grouped name item
                var items = element.querySelectorAll('[name=' + item.name + ']');
                if (items.length) {
                  for (var sitem of items) {
                    if (sitem.checked) {
                      if (i) {
                        i._value = sitem.value;
                      } else {
                        i = {
                          _value: sitem.value
                        };
                      }
                    }
                  }
                }
              } else {
                if (i) {
                  i._value = item.value;
                } else {
                  i = {
                    _value: item.value
                  };
                }
              }
            } else {
              i = {
                _value: item.value
              };
            }
          }
        } else if ((typeof change.oldValue == 'undefined' && typeof change.object[change.name] == 'undefined') && item && !~['submit', 'button'].indexOf(item.type) && item.childNodes.length == 1 && item.childNodes[0].textContent.trim()) {
          i = {
            _text: item.childNodes[0].textContent.trim()
          };
        }
        if (i) tmp.push(i);
        else if (item && !item.hasChildNodes()) {
          tmp.push(MAGNUM)
        }

      });

      if (tmp.length === 0) return;
      else if (greedy) return tmp;
      else {
        return tmp[0] && typeof tmp[0]._value != 'undefined' ? tmp[0]._value : tmp[0] && tmp[0]._text ? tmp[0]._text : tmp[0]
      }
    }
  }

  var findParentNodeWithPath = function(node, path) {
    var snode = node;
    var parts = path.split('/');

    for (var prop of parts) {
      if (prop) {
        var a = mag.fill.find(snode, prop);
        if (a.length) {
          snode = a[0];
        }
      }
    }
    return snode
  };

  var handler = function(type, index, change) {

    if (change.type == 'get' && type != 'props' && !~mag.fill.ignorekeys.indexOf(change.name.toString()) && typeof change.oldValue == 'undefined') {

      var rootNode = mag.doc.getElementById(mod.getId(index));

      //Get parent correct parent not just root!?

      if (change.path && change.path[0] == '/') var fnode = findParentNodeWithPath(rootNode, change.path)

      var res = findMissing(change, fnode ? fnode : rootNode);

      if (typeof res != 'undefined' && typeof res == 'object' && change.object) {
        mag.utils.merge(res, change.object[change.name]);
      }
      if (typeof res != 'undefined') {
        mod.cached[index] = 0;
        return res;
      }
    } else if (change.type == 'set' && type != 'props' && change.object[change.name] && change.object[change.name].draw && typeof change.object[change.name].draw == 'function') {

      // setting a state prop to a module

      // call unloader for module
      mod.innerMods[mod.getId(index)] = [change.name, change.object[change.name]];
    }

    // call setup handler
    var fun = mod.getFrameId(index);
    if (typeof fun == 'function' && change.type == 'set') {
      //debounce
      cancelAnimationFrame(timers[index]);
      timers[index] = requestAnimationFrame(fun);
    }

  };

  function getController(ctrl, index, id) {
    var controller;

    mod.setProps(index, mag.proxy(mod.getProps(index), handler.bind({}, 'props', index), 'prop'));

    controller = new ctrl(mag.proxy({}, handler.bind({}, 'state', index)));

    return controller;
  }

  mod.iscached = function(key) {
    var data = mag.utils.merge(mag.utils.copy(mod.getProps(key)), mod.getState(key));
    if (mod.cache[key] && mod.cache[key] === JSON.stringify(data)) {
      return true
    }
    mod.cache[key] = JSON.stringify(data);
  }

  mod.remove = function(key) {
    //remove mod completely
    if (modules[key]) modules[key] = 0
  }

  mod.clear = function(key) {
    if (~key && mod.cache[key]) {
      mod.cache.splice(key, 1);
    }
  }

  mod.cached = [];
  mod.callView = function(node, index) {
    //TODO: ASYNC, promises .. ?
    mod.runningViewInstance = index;
    if (!mod.cached[index]) {
      mag.props.attachToArgs(index, mod.getState(index), node)
      mod.cached[index] = 1
    }
    mod.getView(index)(mod.getState(index), node)
  }

  mag.mod = mod;

}(mag));
<|endoftext|>


<commit_before>/*
MagJS v0.25.1
http://github.com/magnumjs/mag.js
(c) Michael Glazer
License: MIT
*/
(function(mag) {

  'use strict';

  var modules = [],
    MAGNUM = mag.MAGNUM,
    controllers = []

  var mod = {
    innerMods: [],
    cache: []
  }

  mod.getState = function(index) {
    return modules[index][1]
  }
  mod.setState = function(index, state) {
    modules[index][1] = state
  }
  mod.getView = function(index) {
    return modules[index][0]
  }
  mod.getProps = function(index) {
    return modules[index][2]
  }
  mod.setProps = function(index, props) {
    return modules[index][2] = props
  }
  mod.remove = function(index) {
    modules.splice(index, 1)
  }
  mod.getId = function(index) {
    return modules[index] && modules[index][3]
  }
  mod.exists = function(index) {
    return typeof modules[index] != 'undefined' ? true : false
  }
  mod.setFrameId = function(index, fid) {
    modules[index][4] = fid;
  }
  mod.getFrameId = function(index) {
    return modules[index][4];
  }
  mod.submodule = function(id, index, module, props) {
    if (modules[index]) {
      // new call to existing
      // update props, merge with existing if same key
      if (props.key && props.key === mod.getProps(index).key) {
        mod.setProps(index, mag.utils.copy(mag.utils.merge(mod.getProps(index), props)));
      } else {
        mod.setProps(index, props);
      }

      // reinitialize the controller ?
      return modules[index]
    }
    modules[index] = [0, 0, 0, 0, 0]
    mod.setProps(index, props)
    var controller = function(context) {
        return (module.controller || function() {}).call(context, mod.getProps(index)) || context
      },
      view = function(index, state, ele) {
        module.view && module.view.call(module, state, mod.getProps(index), ele)
      }.bind({}, index),
      output = {
        controller: controller,
        view: view
      }
    modules[index][0] = output.view;
    modules[index][3] = id;
    modules[index][1] = getController(output.controller, index, id);
    // register the module
    return modules[index]
  }

  var timers = [];

  function findMissing(change, element) {
    var prop = change.name;
    if (typeof change.object[change.name] == 'undefined' && prop[0] !== '_') {
      // prop might be hierarchical?
      // getparent Object property chain?

      // get value of property from DOM
      var a = mag.fill.find(element, prop),
        greedy = prop[0] === '$',
        // can be an array, object or string
        // for each
        tmp = [];
      a.forEach(function(item) {
        var i;
        if (item && item.type && !~['submit', 'button'].indexOf(item.type)) {
          if (item.value && item.value.length > 0) {

            if (item.type == 'checkbox' || item.type == 'radio') {

              if (item.checked) {
                i = {};
                i._checked = true;
              }
              if (!greedy) {
                //if grouped name item
                var items = element.querySelectorAll('[name=' + item.name + ']');
                if (items.length) {
                  for (var sitem of items) {
                    if (sitem.checked) {
                      if (i) {
                        i._value = sitem.value;
                      } else {
                        i = {
                          _value: sitem.value
                        };
                      }
                    }
                  }
                }
              } else {
                if (i) {
                  i._value = item.value;
                } else {
                  i = {
                    _value: item.value
                  };
                }
              }
            } else {
              i = {
                _value: item.value
              };
            }
          }
        } else if ((typeof change.oldValue == 'undefined' && typeof change.object[change.name] == 'undefined') && item && !~['submit', 'button'].indexOf(item.type) && item.childNodes.length == 1 && item.childNodes[0].textContent.trim()) {
          i = {
            _text: item.childNodes[0].textContent.trim()
          };
        }
        if (i) tmp.push(i);
        else if(item && !item.hasChildNodes()){
          tmp.push('__')
        }

      });

      if (tmp.length === 0) return;
      else if (greedy) return tmp;
      else {
        return tmp[0] && typeof tmp[0]._value != 'undefined' ? tmp[0]._value : tmp[0] && tmp[0]._text ? tmp[0]._text : tmp[0]
      }
    }
  }

  var findParentNodeWithPath = function(node, path) {
    var snode = node;
    var parts = path.split('/');

    for (var prop of parts) {
      if (prop) {
        var a = mag.fill.find(snode, prop);
        if (a.length) {
          snode = a[0];
        }
      }
    }
    return snode
  };

  var handler = function(type, index, change) {

    if (change.type == 'get' && type != 'props' && !~mag.fill.ignorekeys.indexOf(change.name.toString()) && typeof change.oldValue == 'undefined') {

      var rootNode = mag.doc.getElementById(mod.getId(index));

      //Get parent correct parent not just root!?

      if (change.path && change.path[0] == '/') var fnode = findParentNodeWithPath(rootNode, change.path)

      var res = findMissing(change, fnode ? fnode : rootNode);

      if (typeof res != 'undefined' && typeof res == 'object' && change.object) {
        mag.utils.merge(res, change.object[change.name]);
      }
      if (typeof res != 'undefined') {
        mod.cached[index] = 0;
        return res;
      }
    } else if (change.type == 'set' && type != 'props' && change.object[change.name] && change.object[change.name].draw && typeof change.object[change.name].draw == 'function') {

      // setting a state prop to a module

      // call unloader for module
      mod.innerMods[mod.getId(index)] = [change.name, change.object[change.name]];
    }

    // call setup handler
    var fun = mod.getFrameId(index);
    if (typeof fun == 'function' && change.type == 'set') {
      //debounce
      cancelAnimationFrame(timers[index]);
      timers[index] = requestAnimationFrame(fun);
    }

  };

  function getController(ctrl, index, id) {
    var controller;

    mod.setProps(index, mag.proxy(mod.getProps(index), handler.bind({}, 'props', index), 'prop'));

    controller = new ctrl(mag.proxy({}, handler.bind({}, 'state', index)));

    return controller;
  }

  mod.iscached = function(key) {
    var data = mag.utils.merge(mag.utils.copy(mod.getProps(key)), mod.getState(key));
    if (mod.cache[key] && mod.cache[key] === JSON.stringify(data)) {
      return true
    }
    mod.cache[key] = JSON.stringify(data);
  }

  mod.clear = function(key) {
    if (key > -1 && mod.cache[key]) {
      mod.cache.splice(key, 1);
    }
  }

  mod.cached = [];
  mod.callView = function(node, index) {
    //TODO: ASYNC, promises .. ?
    mod.runningViewInstance = index;
    if (!mod.cached[index]) {
      mag.props.attachToArgs(index, mod.getState(index), node)
      mod.cached[index] = 1
    }
    mod.getView(index)(mod.getState(index), node)
  }

  mag.mod = mod;

}(mag));
<commit_msg>Update module.js<commit_after>/*
MagJS v0.25.2
http://github.com/magnumjs/mag.js
(c) Michael Glazer
License: MIT
*/
(function(mag) {

  'use strict';

  var modules = [],
    MAGNUM = mag.MAGNUM,
    controllers = []

  var mod = {
    innerMods: [],
    cache: []
  }

  mod.getState = function(index) {
    return modules[index][1]
  }
  mod.setState = function(index, state) {
    modules[index][1] = state
  }
  mod.getView = function(index) {
    return modules[index][0]
  }
  mod.getProps = function(index) {
    return modules[index][2]
  }
  mod.setProps = function(index, props) {
    return modules[index][2] = props
  }
  mod.remove = function(index) {
    modules.splice(index, 1)
  }
  mod.getId = function(index) {
    return modules[index] && modules[index][3]
  }
  mod.exists = function(index) {
    return typeof modules[index] != 'undefined' ? true : false
  }
  mod.setFrameId = function(index, fid) {
    modules[index][4] = fid;
  }
  mod.getFrameId = function(index) {
    return modules[index][4];
  }
  mod.submodule = function(id, index, module, props) {
    if (modules[index]) {
      // new call to existing
      // update props, merge with existing if same key
      if (props.key && props.key === mod.getProps(index).key) {
        mod.setProps(index, mag.utils.copy(mag.utils.merge(mod.getProps(index), props)));
      } else {
        mod.setProps(index, props);
      }

      // reinitialize the controller ?
      return modules[index]
    }
    modules[index] = [0, 0, 0, 0, 0]
    mod.setProps(index, props)
    var controller = function(context) {
        return (module.controller || function() {}).call(context, mod.getProps(index)) || context
      },
      view = function(index, state, ele) {
        module.view && module.view.call(module, state, mod.getProps(index), ele)
      }.bind({}, index),
      output = {
        controller: controller,
        view: view
      }
    modules[index][0] = output.view;
    modules[index][3] = id;
    modules[index][1] = getController(output.controller, index, id);
    // register the module
    return modules[index]
  }

  var timers = [];

  function findMissing(change, element) {
    var prop = change.name;
    if (typeof change.object[change.name] == 'undefined' && prop[0] !== '_') {
      // prop might be hierarchical?
      // getparent Object property chain?

      // get value of property from DOM
      var a = mag.fill.find(element, prop),
        greedy = prop[0] === '$',
        // can be an array, object or string
        // for each
        tmp = [];

        if(!a.length) return '__';

      a.forEach(function(item) {
        var i;
        if (item && item.type && !~['submit', 'button'].indexOf(item.type)) {
          if (item.value && item.value.length > 0) {

            if (item.type == 'checkbox' || item.type == 'radio') {

              if (item.checked) {
                i = {};
                i._checked = true;
              }
              if (!greedy) {
                //if grouped name item
                var items = element.querySelectorAll('[name=' + item.name + ']');
                if (items.length) {
                  for (var sitem of items) {
                    if (sitem.checked) {
                      if (i) {
                        i._value = sitem.value;
                      } else {
                        i = {
                          _value: sitem.value
                        };
                      }
                    }
                  }
                }
              } else {
                if (i) {
                  i._value = item.value;
                } else {
                  i = {
                    _value: item.value
                  };
                }
              }
            } else {
              i = {
                _value: item.value
              };
            }
          }
        } else if ((typeof change.oldValue == 'undefined' && typeof change.object[change.name] == 'undefined') && item && !~['submit', 'button'].indexOf(item.type) && item.childNodes.length == 1 && item.childNodes[0].textContent.trim()) {
          i = {
            _text: item.childNodes[0].textContent.trim()
          };
        }
        if (i) tmp.push(i);
        else if(item && !item.hasChildNodes()){
          tmp.push('__')
        }

      });

      if (tmp.length === 0) return;
      else if (greedy) return tmp;
      else {
        return tmp[0] && typeof tmp[0]._value != 'undefined' ? tmp[0]._value : tmp[0] && tmp[0]._text ? tmp[0]._text : tmp[0]
      }
    }
  }

  var findParentNodeWithPath = function(node, path) {
    var snode = node;
    var parts = path.split('/');

    for (var prop of parts) {
      if (prop) {
        var a = mag.fill.find(snode, prop);
        if (a.length) {
          snode = a[0];
        }
      }
    }
    return snode
  };

  var handler = function(type, index, change) {

    if (change.type == 'get' && type != 'props' && !~mag.fill.ignorekeys.indexOf(change.name.toString()) && typeof change.oldValue == 'undefined') {

      var rootNode = mag.doc.getElementById(mod.getId(index));

      //Get parent correct parent not just root!?

      if (change.path && change.path[0] == '/') var fnode = findParentNodeWithPath(rootNode, change.path)

      var res = findMissing(change, fnode ? fnode : rootNode);

      if (typeof res != 'undefined' && typeof res == 'object' && change.object) {
        mag.utils.merge(res, change.object[change.name]);
      }
      if (typeof res != 'undefined') {
        mod.cached[index] = 0;
        return res;
      }
    } else if (change.type == 'set' && type != 'props' && change.object[change.name] && change.object[change.name].draw && typeof change.object[change.name].draw == 'function') {

      // setting a state prop to a module

      // call unloader for module
      mod.innerMods[mod.getId(index)] = [change.name, change.object[change.name]];
    }

    // call setup handler
    var fun = mod.getFrameId(index);
    if (typeof fun == 'function' && change.type == 'set') {
      //debounce
      cancelAnimationFrame(timers[index]);
      timers[index] = requestAnimationFrame(fun);
    }

  };

  function getController(ctrl, index, id) {
    var controller;

    mod.setProps(index, mag.proxy(mod.getProps(index), handler.bind({}, 'props', index), 'prop'));

    controller = new ctrl(mag.proxy({}, handler.bind({}, 'state', index)));

    return controller;
  }

  mod.iscached = function(key) {
    var data = mag.utils.merge(mag.utils.copy(mod.getProps(key)), mod.getState(key));
    if (mod.cache[key] && mod.cache[key] === JSON.stringify(data)) {
      return true
    }
    mod.cache[key] = JSON.stringify(data);
  }

  mod.clear = function(key) {
    if (key > -1 && mod.cache[key]) {
      mod.cache.splice(key, 1);
    }
  }

  mod.cached = [];
  mod.callView = function(node, index) {
    //TODO: ASYNC, promises .. ?
    mod.runningViewInstance = index;
    if (!mod.cached[index]) {
      mag.props.attachToArgs(index, mod.getState(index), node)
      mod.cached[index] = 1
    }
    mod.getView(index)(mod.getState(index), node)
  }

  mag.mod = mod;

}(mag));
<|endoftext|>


<commit_before>
moment.yesterday = function() {return moment.utc().subtract(1, 'day').startOf('day')}
moment.today = function() {return moment.utc().startOf('day')}
moment.now = function() {return moment.utc()}
moment.tomorrow = function() {return moment.utc().add(1, 'day').startOf('day')}

moment.$today = function() {
  return moment.$between(moment.today())
}

moment.$between = function(fromTime, toTime) {
  toTime = toTime || moment.now()
  return {
    '$gte': fromTime.toDate(),
    '$lte': toTime.toDate()
  }
}

;(function() {
  var applyLater = function(duration) {
    return {
      ago: function(now) {return (now || moment.now()).subtract(duration)},
      since: function(now) {return (now || moment.now()).add(duration)},
      duration: duration
    }
  }

  Number.prototype.years = Number.prototype.year = function() {return applyLater(moment.duration(this.valueOf(), 'years'))}
  Number.prototype.months = Number.prototype.month = function() {return applyLater(moment.duration(this.valueOf(), 'months'))}
  Number.prototype.days = Number.prototype.day = function() {return applyLater(moment.duration(this.valueOf(), 'days'))}
  Number.prototype.minutes = Number.prototype.minute = function() {return applyLater(moment.duration(this.valueOf(), 'minutes'))}
  Number.prototype.seconds = Number.prototype.second = function() {return applyLater(moment.duration(this.valueOf(), 'seconds'))}
})()
<commit_msg>add moment.$inDay<commit_after>
moment.yesterday = function() {return moment.utc().subtract(1, 'day').startOf('day')}
moment.today = function() {return moment.utc().startOf('day')}
moment.now = function() {return moment.utc()}
moment.tomorrow = function() {return moment.utc().add(1, 'day').startOf('day')}

moment.$today = function() {
  return moment.$between(moment.today())
}

moment.$between = function(fromTime, toTime) {
  toTime = toTime || moment.now()
  return {
    '$gte': fromTime.toDate(),
    '$lte': toTime.toDate()
  }
}

moment.$inDay = function(aDay) {
  return {
    '$gte': moment(aDay).startOf('day').toDate(),
    '$lte': moment(aDay).endOf('day').toDate()
  }
}

;(function() {
  var applyLater = function(duration) {
    return {
      ago: function(now) {return (now || moment.now()).subtract(duration)},
      since: function(now) {return (now || moment.now()).add(duration)},
      duration: duration
    }
  }

  Number.prototype.years = Number.prototype.year = function() {return applyLater(moment.duration(this.valueOf(), 'years'))}
  Number.prototype.months = Number.prototype.month = function() {return applyLater(moment.duration(this.valueOf(), 'months'))}
  Number.prototype.days = Number.prototype.day = function() {return applyLater(moment.duration(this.valueOf(), 'days'))}
  Number.prototype.minutes = Number.prototype.minute = function() {return applyLater(moment.duration(this.valueOf(), 'minutes'))}
  Number.prototype.seconds = Number.prototype.second = function() {return applyLater(moment.duration(this.valueOf(), 'seconds'))}
})()
<|endoftext|>


<commit_before>(function($){

  function escapeString (value) {
    return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
  }

  $.declare('complete',{
    defaults : {
      search : function(suggestion, queryOriginal, queryLowerCase){
        return this._getSuggestion(suggestion).toLowerCase().indexOf(queryLowerCase.toLowerCase()) !== -1;
      },
      listClass : 'complete',
      suggestionActiveClass : 'complete-active',
      suggestionClass : 'complete-suggestion',
      maxHeight : 300,
      minChars : 0,
      zIndex : 99999,
      delay : 300,
      allowOthers : false,
      sourceKey : null,
      formatSuggestion : function(suggestion, value){
        var pattern = '(' + escapeString(value) + ')';
        return this._getSuggestion(suggestion).replace(new RegExp(pattern, 'gi'), '$1<\/strong>');
      },
      query : function(query, callback){
        var queryLower = query.toLowerCase(), self = this;

        var suggestions = $.grep(this.source, function(suggestion){
          return self.search(suggestion, query, queryLower);
        });

        callback.call(this,suggestions);
      }
    },
    events : {
      'keydown' : 'keyPressed',
      'keyup' : 'keyUp',
      'blur' : 'onBlur',
      'focus' : 'onFocus'
    },
    keyCode : {
      UP : 38,
      DOWN : 40,
      TAB: 9,
      ENTER : 13,
      ESC : 27
    },
    init : function(){
      $(this.el).attr('autocomplete', 'off');
      this.createListHolder();
      this.visible = false;
      this.currentValue = this.el.value;
      this.selectedIndex = -1;
      this.suggestions = [];
    },
    _getSuggestion : function (suggestion) {
      var value;

      if (!!this.sourceKey && suggestion && suggestion[this.sourceKey]){
        value = suggestion[this.sourceKey];
      }
      else {
        value = suggestion;
      }

      return value;
    },
    debounce : function(func) {
      var self = this;
      var args = arguments;

      if(this.delay > 0) {
        clearTimeout(this.debounceTimeout);

        this.debounceTimeout = setTimeout(function(){
          func.apply(self, args);
        }, this.delay);
      } else {
        func.apply(this, args);
      }
    },
    createListHolder : function(){
      var $el = $(this.el);
      this.listHolder = $('
').addClass(this.listClass).hide().insertAfter($el);
      this.list= $('
');
      this.list.appendTo(this.listHolder);

      $(this.listHolder).css({
        "width" : $el.outerWidth(),
        "top" : $el.position().top + $el.outerHeight(),
        "left" : $el.position().left,
        "max-height" : this.maxHeight,
        "z-index" : this.zIndex
      });

      this.bindEventsList();
    },
    updatePosition: function() {
      var $el = $(this.el);
      $(this.listHolder).css({
        "top" : $el.position().top + $el.outerHeight(),
        "left" : $el.position().left,
      });
    },
    bindEventsList : function(){
      var $list = $(this.list),
          self = this;
      $list.on('mouseover', 'li', function(e){
        var target = self._getTarget(e);
        self.activateSuggestion.apply(self, [target.data('index')]);
      });
      $list.on('mouseout', 'li', this.proxy(this.deActivateSuggestion,this));
      $list.on('click', 'li', this.proxy(this.selectSuggestion,this));
    },
    keyPressed : function(event){
      switch(event.keyCode){
        case this.keyCode.UP :
          this._prevSuggestion();
          break;
        case this.keyCode.DOWN :
          this._nextSuggestion();
          break;
        case this.keyCode.ESC :
          this.el.val(this.currentValue);
          this.hide();
          break;
        case this.keyCode.TAB:
        case this.keyCode.ENTER:
          this.selectSuggestion(event);
          break;
        default:
          return;
      }

      event.stopImmediatePropagation();
      event.preventDefault();
    },
    keyUp : function(event){
      switch(event.keyCode){
        case this.keyCode.UP :
        case this.keyCode.DOWN :
          return;
      }
      this._generatedSuggestions = false;
      this.debounce(this.valueChanged);
    },
    onBlur : function(){
      $(document).on('click.complete', this.proxy(this.onClickWA,this));
    },
    onFocus: function() {
      this.updatePosition();
    },
    onClickWA : function(event){
      if ($(event.target).closest('.' + this.listClass).length === 0) {
        this.hide();
        $(document).off('click.complete');
      }
    },
    valueChanged : function(){
      if (this._getSuggestion(this.currentValue) !== $(this.el).val()){
        this.el.value = $.trim($(this.el).val());
        this.currentValue = this.el.value;
        this.selectedIndex = -1;

        if (this.currentValue.length > this.minChars){
          this.show();
        }
        else {
          this.hide();
        }
      }
      this._generatedSuggestions = true;
    },
    _getSuggestions : function(query){
      var queryLower = query.toLowerCase(), self = this;

      return $.grep(this.source, function(suggestion){
        return self.search(suggestion, query, queryLower);
      });
    },
    _nextSuggestion : function(){
      var index = this.selectedIndex;

      if (!this.visible && this.currentValue){
        this.show();
      }
      else if (index !== (this.suggestions.length - 1)) {
        this._adjustPosition(index + 1);
      }
    },
    _prevSuggestion : function(){
      var index = this.selectedIndex;

      if (index !== -1) {
        this._adjustPosition(index -1);
      }
    },
    _adjustPosition : function(index){
      var selected = this.activateSuggestion(index),
          selTop, upperLimit, lowerLimit, elementHeight,
          listHolder = $(this.listHolder);

      if (selected){
        selTop = selected.offsetTop;
        upperLimit = listHolder.scrollTop();
        elementHeight = $(this.list).children().first().outerHeight();
        lowerLimit = upperLimit + this.maxHeight - elementHeight;

        if (selTop < upperLimit){
          listHolder.scrollTop(selTop);
        }
        else if (selTop > lowerLimit){
          listHolder.scrollTop(selTop - this.maxHeight + elementHeight);
        }

        $(this.el).val(this._getSuggestion(this.suggestions[index]));
      }
    },
    hide : function(){
      this.visible = false;
      $(this.listHolder).hide();
    },
    show : function(){
      var self = this;

      this.query.call(self, this.currentValue, function(suggestions){
        if (suggestions && $.isArray(suggestions) && suggestions.length){
          self.visible = true;
          var value = self.currentValue,
              className = self.suggestionClass,
              html = '';

          self.suggestions = [];
          self.suggestions = suggestions;

          $.each(suggestions, function(i, suggestion){
            html += '
' + self.formatSuggestion(suggestion,value) + '
';
          });

          $(self.list).html(html);
          $(self.listHolder).show();
        }
        else {
          $(self.list).empty();
          self.suggestions = [];
          self.hide();
        }
      });
    },
    activateSuggestion : function(index){
      var classSelected = this.suggestionActiveClass,
          list = $(this.list);

      list.children('.' + classSelected).removeClass(classSelected);
      this.selectedIndex = index;

      if (index !== -1 && list.children().length > index){
        return $(list.children().get(index)).addClass(classSelected);
      }
    },
    deActivateSuggestion : function(e){
      this._getTarget(e).removeClass(this.suggestionActiveClass);
      this.selectedIndex = -1;
    },
    selectSuggestion : function(event){
      // If the user hits enter before the debounce finishes, we force a generation of the suggestions
      if(!this._generatedSuggestions){
        this.valueChanged();
      }

      if (event.type === "keydown" && this.allowOthers && this.selectedIndex < 0){
        var firstSuggestion = this._getSuggestion(this.suggestions[0]);

        if (this.suggestions.length === 1 && firstSuggestion === this.currentValue){
          this.currentValue = this.suggestions[0];
          $(this.el).val(this._getSuggestion(this.currentValue));
        }
        else {
          $(this.el).val(this.currentValue);
        }

        this.emit('complete:select',this.currentValue);
        this.hide();
      }
      else {
        if (this.selectedIndex === -1){
          this.selectedIndex = 0;
        }

        if (this.suggestions[this.selectedIndex]){
          $(this.el).val(this._getSuggestion(this.suggestions[this.selectedIndex]));
          this.currentValue = this.suggestions[this.selectedIndex];
          this.emit('complete:select',this.currentValue);
          this.hide();
        }
      }
    },
    _getTarget : function(e){
      return $(e.currentTarget || e.toElement);
    },
    setSource: function(source){
      this.source = source;
    }
  });
})(jQuery);
<commit_msg>allow propagation when clicking the tab key<commit_after>(function($){

  function escapeString (value) {
    return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
  }

  $.declare('complete',{
    defaults : {
      search : function(suggestion, queryOriginal, queryLowerCase){
        return this._getSuggestion(suggestion).toLowerCase().indexOf(queryLowerCase.toLowerCase()) !== -1;
      },
      listClass : 'complete',
      suggestionActiveClass : 'complete-active',
      suggestionClass : 'complete-suggestion',
      maxHeight : 300,
      minChars : 0,
      zIndex : 99999,
      delay : 300,
      allowOthers : false,
      sourceKey : null,
      formatSuggestion : function(suggestion, value){
        var pattern = '(' + escapeString(value) + ')';
        return this._getSuggestion(suggestion).replace(new RegExp(pattern, 'gi'), '$1<\/strong>');
      },
      query : function(query, callback){
        var queryLower = query.toLowerCase(), self = this;

        var suggestions = $.grep(this.source, function(suggestion){
          return self.search(suggestion, query, queryLower);
        });

        callback.call(this,suggestions);
      }
    },
    events : {
      'keydown' : 'keyPressed',
      'keyup' : 'keyUp',
      'blur' : 'onBlur',
      'focus' : 'onFocus'
    },
    keyCode : {
      UP : 38,
      DOWN : 40,
      TAB: 9,
      ENTER : 13,
      ESC : 27
    },
    init : function(){
      $(this.el).attr('autocomplete', 'off');
      this.createListHolder();
      this.visible = false;
      this.currentValue = this.el.value;
      this.selectedIndex = -1;
      this.suggestions = [];
    },
    _getSuggestion : function (suggestion) {
      var value;

      if (!!this.sourceKey && suggestion && suggestion[this.sourceKey]){
        value = suggestion[this.sourceKey];
      }
      else {
        value = suggestion;
      }

      return value;
    },
    debounce : function(func) {
      var self = this;
      var args = arguments;

      if(this.delay > 0) {
        clearTimeout(this.debounceTimeout);

        this.debounceTimeout = setTimeout(function(){
          func.apply(self, args);
        }, this.delay);
      } else {
        func.apply(this, args);
      }
    },
    createListHolder : function(){
      var $el = $(this.el);
      this.listHolder = $('
').addClass(this.listClass).hide().insertAfter($el);
      this.list= $('
');
      this.list.appendTo(this.listHolder);

      $(this.listHolder).css({
        "width" : $el.outerWidth(),
        "top" : $el.position().top + $el.outerHeight(),
        "left" : $el.position().left,
        "max-height" : this.maxHeight,
        "z-index" : this.zIndex
      });

      this.bindEventsList();
    },
    updatePosition: function() {
      var $el = $(this.el);
      $(this.listHolder).css({
        "top" : $el.position().top + $el.outerHeight(),
        "left" : $el.position().left,
      });
    },
    bindEventsList : function(){
      var $list = $(this.list),
          self = this;
      $list.on('mouseover', 'li', function(e){
        var target = self._getTarget(e);
        self.activateSuggestion.apply(self, [target.data('index')]);
      });
      $list.on('mouseout', 'li', this.proxy(this.deActivateSuggestion,this));
      $list.on('click', 'li', this.proxy(this.selectSuggestion,this));
    },
    keyPressed : function(event){
      var propagate = true;
      switch(event.keyCode){
        case this.keyCode.UP :
          this._prevSuggestion();
          break;
        case this.keyCode.DOWN :
          this._nextSuggestion();
          break;
        case this.keyCode.ESC :
          this.el.val(this.currentValue);
          this.hide();
          break;
        case this.keyCode.TAB:
          propagate = false;
          this.selectSuggestion(event);
          break;
        case this.keyCode.ENTER:
          this.selectSuggestion(event);
          break;
        default:
          return;
      }

      if (propagate) {
        event.stopImmediatePropagation();
        event.preventDefault();
      }
    },
    keyUp : function(event){
      switch(event.keyCode){
        case this.keyCode.UP :
        case this.keyCode.DOWN :
          return;
      }
      this._generatedSuggestions = false;
      this.debounce(this.valueChanged);
    },
    onBlur : function(){
      $(document).on('click.complete', this.proxy(this.onClickWA,this));
    },
    onFocus: function() {
      this.updatePosition();
    },
    onClickWA : function(event){
      if ($(event.target).closest('.' + this.listClass).length === 0) {
        this.hide();
        $(document).off('click.complete');
      }
    },
    valueChanged : function(){
      if (this._getSuggestion(this.currentValue) !== $(this.el).val()){
        this.el.value = $.trim($(this.el).val());
        this.currentValue = this.el.value;
        this.selectedIndex = -1;

        if (this.currentValue.length > this.minChars){
          this.show();
        }
        else {
          this.hide();
        }
      }
      this._generatedSuggestions = true;
    },
    _getSuggestions : function(query){
      var queryLower = query.toLowerCase(), self = this;

      return $.grep(this.source, function(suggestion){
        return self.search(suggestion, query, queryLower);
      });
    },
    _nextSuggestion : function(){
      var index = this.selectedIndex;

      if (!this.visible && this.currentValue){
        this.show();
      }
      else if (index !== (this.suggestions.length - 1)) {
        this._adjustPosition(index + 1);
      }
    },
    _prevSuggestion : function(){
      var index = this.selectedIndex;

      if (index !== -1) {
        this._adjustPosition(index -1);
      }
    },
    _adjustPosition : function(index){
      var selected = this.activateSuggestion(index),
          selTop, upperLimit, lowerLimit, elementHeight,
          listHolder = $(this.listHolder);

      if (selected){
        selTop = selected.offsetTop;
        upperLimit = listHolder.scrollTop();
        elementHeight = $(this.list).children().first().outerHeight();
        lowerLimit = upperLimit + this.maxHeight - elementHeight;

        if (selTop < upperLimit){
          listHolder.scrollTop(selTop);
        }
        else if (selTop > lowerLimit){
          listHolder.scrollTop(selTop - this.maxHeight + elementHeight);
        }

        $(this.el).val(this._getSuggestion(this.suggestions[index]));
      }
    },
    hide : function(){
      this.visible = false;
      $(this.listHolder).hide();
    },
    show : function(){
      var self = this;

      this.query.call(self, this.currentValue, function(suggestions){
        if (suggestions && $.isArray(suggestions) && suggestions.length){
          self.visible = true;
          var value = self.currentValue,
              className = self.suggestionClass,
              html = '';

          self.suggestions = [];
          self.suggestions = suggestions;

          $.each(suggestions, function(i, suggestion){
            html += '
' + self.formatSuggestion(suggestion,value) + '
';
          });

          $(self.list).html(html);
          $(self.listHolder).show();
        }
        else {
          $(self.list).empty();
          self.suggestions = [];
          self.hide();
        }
      });
    },
    activateSuggestion : function(index){
      var classSelected = this.suggestionActiveClass,
          list = $(this.list);

      list.children('.' + classSelected).removeClass(classSelected);
      this.selectedIndex = index;

      if (index !== -1 && list.children().length > index){
        return $(list.children().get(index)).addClass(classSelected);
      }
    },
    deActivateSuggestion : function(e){
      this._getTarget(e).removeClass(this.suggestionActiveClass);
      this.selectedIndex = -1;
    },
    selectSuggestion : function(event){
      // If the user hits enter before the debounce finishes, we force a generation of the suggestions
      if(!this._generatedSuggestions){
        this.valueChanged();
      }

      if (event.type === "keydown" && this.allowOthers && this.selectedIndex < 0){
        var firstSuggestion = this._getSuggestion(this.suggestions[0]);

        if (this.suggestions.length === 1 && firstSuggestion === this.currentValue){
          this.currentValue = this.suggestions[0];
          $(this.el).val(this._getSuggestion(this.currentValue));
        }
        else {
          $(this.el).val(this.currentValue);
        }

        this.emit('complete:select',this.currentValue);
        this.hide();
      }
      else {
        if (this.selectedIndex === -1){
          this.selectedIndex = 0;
        }

        if (this.suggestions[this.selectedIndex]){
          $(this.el).val(this._getSuggestion(this.suggestions[this.selectedIndex]));
          this.currentValue = this.suggestions[this.selectedIndex];
          this.emit('complete:select',this.currentValue);
          this.hide();
        }
      }
    },
    _getTarget : function(e){
      return $(e.currentTarget || e.toElement);
    },
    setSource: function(source){
      this.source = source;
    }
  });
})(jQuery);
<|endoftext|>