<commit_before>var events         = require('events')
  , expat          = require('node-expat')
  , FiniteAutomata = require('./finite-automata')
  ;

// Tests if object is empty (has no own properties).
function isEmpty(obj) {
  for (var key in obj) if (obj.hasOwnProperty(key)) {
    return false;
  }
  return true;
}

// XML entities.
var entities = {
  '"': '"',
  '&': '&',
  '\'': ''',
  '<': '<',
  '>': '>'
};

// Escapes text for XML.
function escape(value) {
  return value.replace(/"|&|'|<|>/g, function(entity) {
    return entities[entity];
  });
}

// Parser events to finite automata events mapping.
var faModes = {
  'startElement': 'enter',
  'endElement': 'leave',
  'text': 'state'
};

// I accidentally the whole class.
module.exports = XmlStream;

// **XmlStream** is an XML stream filter based on Expat.
// It traverses a given stream and emits events for predefined selectors.
// Event listeners receive selected elements, context, and trace from root.
function XmlStream(stream) {
  events.EventEmitter.call(this);
  this._stream = stream;
  this._fa = new FiniteAutomata();
  this._lastState = 0;
  this._startState = {};
  this._finalStates = {};
  this._emitData = false;
  this._bufferLevel = 0;
  this._collect = false;
  parse.call(this);
}

// Inherit events.EventEmitter.
XmlStream.super_ = events.EventEmitter;
XmlStream.prototype = Object.create(events.EventEmitter.prototype, {
  constructor: {
    value: XmlStream,
    enumerable: false
  }
});

// Adds a listener for the specified event.
//
// Supported events:
//
// * `data` on outgoing data chunk,
// * `end` when parsing has ended,
// * `startElement[: selector]` on opening tag for selector match,
// * `updateElement[: selector]` on finished node for selector match
//   with its contents buffered,
// * `endElement[: selector]` on closing tag for selector match,
// * `text[: selector]` on tag text for selector match.
//
// When adding listeners for `startElement`, `updateElement`, and `text` the
// callback can modify the provided node, before it is sent to the consumer.
//
// Selector syntax is CSS-like and currently supports:
//
// * `ancestor descendant`
// * `parent > child`
XmlStream.prototype.on = function(eventName, listener) {
  XmlStream.super_.prototype.on.call(this, eventName, listener);
  if (eventName === 'data') {
    this._emitData = true;
  }
  var event = parseEvent(eventName);
  if (event !== null) {
    // If we're dealing with a selector event,
    // continue with selector-specific processing logic.
    var finalState = getFinalState.call(this, event.selector);
    var self = this;
    if (event.type === 'updateElement') {
      this._fa.on('enter', finalState, function() {
        self._bufferLevel++;
      });
      this._fa.on('leave', finalState, function(element, context, trace) {
        self.emit(event.name, element, context, trace);
        if (!--self._bufferLevel && self._emitData) {
           emitElement.call(self, element, self._name, true);
        }
      });
    } else {
      var fn = function(element, context, trace) {
        self.emit(event.name, element, context, trace);
      };
      this._fa.on(faModes[event.type], finalState, fn);
    }
  }
};

//
XmlStream.prototype.collect = function(selector) {
  selector = normalizeSelector(selector);
  var finalState = getFinalState.call(this, selector);
  var self = this;
  this._fa.on('flag', finalState, function() {
    self._collect = true;
  });
};

// Normalizes the selector and returns the new version and its parts.
function normalizeSelector(selector) {
  var parts = selector.match(/[^\s>]+|>/ig);
  selector = parts.join(' ');
  return {
    normalized: parts.join(' '),
    parts: parts
  };
}

// Parses the selector event string and returns event information.
function parseEvent(event) {
  var eventParts = event.match(/^((?:start|end|update)Element|text):?(.*)/);
  if (eventParts === null) {
    return null;
  }
  var eventType = eventParts[1];
  var selector = normalizeSelector(eventParts[2]);
  return {
    selector: selector,
    type: eventType,
    name: eventType + ': ' + selector.normalized
  };
}

// Compiles a given selector object to a finite automata
// and returns its last state.
function getFinalState(selector) {
  if (this._finalStates.hasOwnProperty(selector.normalized)) {
    finalState = this._finalStates[selector.normalized];
  } else {
    var n = selector.parts.length;
    var immediate = false;
    this._startState[this._lastState] = true;
    for (var i = 0; i < n; i++) {
      var part = selector.parts[i];
      if (part === '>') {
        immediate = true;
      } else {
        if (!immediate) {
          this._fa.transition(this._lastState, '', this._lastState);
        }
        this._fa.transition(this._lastState, part, ++this._lastState);
        immediate = false;
      }
    }
    finalState = this._lastState++;
    this._finalStates[selector.normalized] = finalState;
  }
  return finalState;
}

// Emits XML for element opening tag.
function emitStart(name, attrs) {
  this.emit('data', '<' + name);
  for (var attr in attrs) if (attrs.hasOwnProperty(attr)) {
    this.emit('data', ' ' + attr + '="' + escape(attrs[attr]) + '"');
  }
  this.emit('data', '>');
}

// Emits XML for element closing tag.
function emitEnd(name) {
  this.emit('data', '</' + name + '>');
}

// Emits XML for element text.
function emitText(text) {
  this.emit('data', escape(text).trim());
}

// Emits a single element and its descendants, or an array of elements.
function emitElement(element, name, onLeave) {
  if (Array.isArray(element)) {
    var i;
    for (i = 0; i < element.length - 1; i++) {
      emitOneElement.call(this, element[i], name);
    }
    emitOneElement.call(this, element[i], name, onLeave);
  } else {
    emitOneElement.call(this, element, name, onLeave);
  }
}

// Recursively emits a given element and its descendants.
function emitOneElement(element, name, onLeave) {
  if (typeof element === 'object') {
    emitStart.call(this, name, element.$);
    var hasText = false;
    for (var child in element) {
      if (element.hasOwnProperty(child) && child !== '$' && child != '$name') {
        if (child === '$text') {
          hasText = true;
        } else {
          emitElement.call(this, element[child], child);
        }
      }
    }
    if (hasText) {
      emitText.call(this, element.$text);
    }
  } else {
    emitStart.call(this, name, element.$);
    emitText.call(this, element);
  }
  if(!onLeave) {
    emitEnd.call(this, name);
  }
}

// Starts parsing the source stream and emitting various events.
// The Expat parser is assigned several listeners for this purpose.
function parse(stream) {
  var self = this;
  var xml = new expat.Parser();
  var stack = [];
  var trace = {};
  var curr = {
    element: {},
    collect: this._collect,
    fullText: '',
    space: 0,
    path: '',
    context: {}
  };
  var fa = this._fa;
  fa.setState(this._startState);

  // A listener is assigned on opening tag encounter.
  // Here we traverse the configured finite automata use the stack
  // to form the context and trace for selector event emission.
  xml.on('startElement', function(name, attr) {
    stack.push(curr);
    trace[curr.path] = curr.element;
    var context = Object.create(curr.context);
    var element = {
      $: attr,
      $name: name,
      $text: ''
    };
    var parent = curr.element;
    curr = {
      element: element,
      collect: false,
      fullText: '',
      space: 0,
      path: curr.path + '/' + name,
      context: context
    };
    self._collect = false;
    fa.enter(name, [element, context, trace]);
    name = element.$name;
    curr.collect = self._collect;
    if (curr.collect) {
      var container;
      if (Object.hasOwnProperty.call(parent, name)) {
        container = parent[name];
        container.push(element);
      } else {
        container = [element];
        parent[name] = container;
      }
    } else {
      parent[name] = element;
      context[name] = element;
    }
    if (self._bufferLevel === 0 && self._emitData) {
      emitStart.call(self, name, element.$);
    }
  });

  // A listener is assigned on closing tag encounter.
  // Current node structure object is finalized. A selector listener is
  // invoked with current node, context, and trace; these arguments are
  // removed from the stack afterwards.
  xml.on('endElement', function(name) {
    var prev = stack.pop();
    var element = curr.element;
    var text = curr.fullText;
    var attr = element.$;
    if (typeof attr !== 'object') {
      attr = {};
    }
    var name = element.$name;
    self._name = name;
    delete element.$;
    delete element.$text;
    delete element.$name;
    var val = element;
    if (isEmpty(element) && isEmpty(attr)) {
      val = text;
    } else {
      if (text !== '') {
        element.$text = text;
      }
      if (!isEmpty(attr)) {
        element.$ = attr;
      }
    }
    curr.context[name] = val;
    if (curr.collect) {
      var container = prev.element[name];
      container[container.length - 1] = val;
    } else {
      prev.element[name] = val;
    }
    fa.leave([element, curr.context, trace]);
    if (self._bufferLevel === 0 && self._emitData) {
      emitEnd.call(self, name);
    }
    curr = prev;
    this._collect = curr.collect;
  });

  // Collect node text part by part
  // (and trim leading and trailing whitespace).
  xml.on('text', function(text) {
    curr.element.$text = text;
    fa.run('state', [curr.element, curr.context, trace]);
    if (self._bufferLevel === 0 && self._emitData) {
      emitText.call(self, text);
    }
    var trimmed = curr.element.$text.trim();
    var spaced = curr.element.$text.substr(0, 1);
    spaced = (spaced !== '') && (spaced.trim() === '');
    var after = curr.element.$text.substr(-1, 1);
    after = (after !== '') && (after.trim() === '');
    switch (curr.space) {
      // No words yet (pass through spaces).
      case 0:
        if (trimmed !== '') {
          curr.space = after ? 2 : 1;
        }
        break;

      // Immediately after text or entity.
      case 1:
        if (trimmed === '') {
          curr.space = 2;
        } else {
          if (spaced) {
            curr.fullText += ' ';
          }
          if (after) {
            curr.space = 2;
          }
        }
        break;

      // Some words were emitted, pass through spaces again.
      // Emit spaces only when a word is encountered afterwards.
      case 2:
        if (trimmed !== '') {
          curr.fullText += ' ';
          curr.space = 1;
        }
        break;
    }
    curr.fullText += trimmed;
  });

  // Pass data from stream to parser.
  this._stream.on('data', function(data) {
    xml.parse(data, false);
  });

  // End parsing on stream EOF and emit an *end* event ourselves.
  this._stream.on('end', function() {
    xml.parse('', true);
    self.emit('end');
  });
}
<commit_msg>Retain $text in selected elements with no children; fixes #4<commit_after>var events         = require('events')
  , expat          = require('node-expat')
  , FiniteAutomata = require('./finite-automata')
  ;

// Tests if object is empty (has no own properties).
function isEmpty(obj) {
  for (var key in obj) if (obj.hasOwnProperty(key)) {
    return false;
  }
  return true;
}

// XML entities.
var entities = {
  '"': '"',
  '&': '&',
  '\'': ''',
  '<': '<',
  '>': '>'
};

// Escapes text for XML.
function escape(value) {
  return value.replace(/"|&|'|<|>/g, function(entity) {
    return entities[entity];
  });
}

// Parser events to finite automata events mapping.
var faModes = {
  'startElement': 'enter',
  'endElement': 'leave',
  'text': 'state'
};

// I accidentally the whole class.
module.exports = XmlStream;

// **XmlStream** is an XML stream filter based on Expat.
// It traverses a given stream and emits events for predefined selectors.
// Event listeners receive selected elements, context, and trace from root.
function XmlStream(stream) {
  events.EventEmitter.call(this);
  this._stream = stream;
  this._fa = new FiniteAutomata();
  this._lastState = 0;
  this._startState = {};
  this._finalStates = {};
  this._emitData = false;
  this._bufferLevel = 0;
  this._collect = false;
  parse.call(this);
}

// Inherit events.EventEmitter.
XmlStream.super_ = events.EventEmitter;
XmlStream.prototype = Object.create(events.EventEmitter.prototype, {
  constructor: {
    value: XmlStream,
    enumerable: false
  }
});

// Adds a listener for the specified event.
//
// Supported events:
//
// * `data` on outgoing data chunk,
// * `end` when parsing has ended,
// * `startElement[: selector]` on opening tag for selector match,
// * `updateElement[: selector]` on finished node for selector match
//   with its contents buffered,
// * `endElement[: selector]` on closing tag for selector match,
// * `text[: selector]` on tag text for selector match.
//
// When adding listeners for `startElement`, `updateElement`, and `text` the
// callback can modify the provided node, before it is sent to the consumer.
//
// Selector syntax is CSS-like and currently supports:
//
// * `ancestor descendant`
// * `parent > child`
XmlStream.prototype.on = function(eventName, listener) {
  XmlStream.super_.prototype.on.call(this, eventName, listener);
  if (eventName === 'data') {
    this._emitData = true;
  }
  var event = parseEvent(eventName);
  if (event !== null) {
    // If we're dealing with a selector event,
    // continue with selector-specific processing logic.
    var finalState = getFinalState.call(this, event.selector);
    var self = this;
    if (event.type === 'updateElement') {
      this._fa.on('enter', finalState, function() {
        self._bufferLevel++;
      });
      this._fa.on('leave', finalState, function(element, context, trace) {
        self.emit(event.name, element, context, trace);
        if (!--self._bufferLevel && self._emitData) {
           emitElement.call(self, element, self._name, true);
        }
      });
    } else {
      var fn = function(element, context, trace) {
        self.emit(event.name, element, context, trace);
      };
      this._fa.on(faModes[event.type], finalState, fn);
    }
  }
};

//
XmlStream.prototype.collect = function(selector) {
  selector = normalizeSelector(selector);
  var finalState = getFinalState.call(this, selector);
  var self = this;
  this._fa.on('flag', finalState, function() {
    self._collect = true;
  });
};

// Normalizes the selector and returns the new version and its parts.
function normalizeSelector(selector) {
  var parts = selector.match(/[^\s>]+|>/ig);
  selector = parts.join(' ');
  return {
    normalized: parts.join(' '),
    parts: parts
  };
}

// Parses the selector event string and returns event information.
function parseEvent(event) {
  var eventParts = event.match(/^((?:start|end|update)Element|text):?(.*)/);
  if (eventParts === null) {
    return null;
  }
  var eventType = eventParts[1];
  var selector = normalizeSelector(eventParts[2]);
  return {
    selector: selector,
    type: eventType,
    name: eventType + ': ' + selector.normalized
  };
}

// Compiles a given selector object to a finite automata
// and returns its last state.
function getFinalState(selector) {
  if (this._finalStates.hasOwnProperty(selector.normalized)) {
    finalState = this._finalStates[selector.normalized];
  } else {
    var n = selector.parts.length;
    var immediate = false;
    this._startState[this._lastState] = true;
    for (var i = 0; i < n; i++) {
      var part = selector.parts[i];
      if (part === '>') {
        immediate = true;
      } else {
        if (!immediate) {
          this._fa.transition(this._lastState, '', this._lastState);
        }
        this._fa.transition(this._lastState, part, ++this._lastState);
        immediate = false;
      }
    }
    finalState = this._lastState++;
    this._finalStates[selector.normalized] = finalState;
  }
  return finalState;
}

// Emits XML for element opening tag.
function emitStart(name, attrs) {
  this.emit('data', '<' + name);
  for (var attr in attrs) if (attrs.hasOwnProperty(attr)) {
    this.emit('data', ' ' + attr + '="' + escape(attrs[attr]) + '"');
  }
  this.emit('data', '>');
}

// Emits XML for element closing tag.
function emitEnd(name) {
  this.emit('data', '</' + name + '>');
}

// Emits XML for element text.
function emitText(text) {
  this.emit('data', escape(text).trim());
}

// Emits a single element and its descendants, or an array of elements.
function emitElement(element, name, onLeave) {
  if (Array.isArray(element)) {
    var i;
    for (i = 0; i < element.length - 1; i++) {
      emitOneElement.call(this, element[i], name);
    }
    emitOneElement.call(this, element[i], name, onLeave);
  } else {
    emitOneElement.call(this, element, name, onLeave);
  }
}

// Recursively emits a given element and its descendants.
function emitOneElement(element, name, onLeave) {
  if (typeof element === 'object') {
    emitStart.call(this, name, element.$);
    var hasText = false;
    for (var child in element) {
      if (element.hasOwnProperty(child) && child !== '$' && child != '$name') {
        if (child === '$text') {
          hasText = true;
        } else {
          emitElement.call(this, element[child], child);
        }
      }
    }
    if (hasText) {
      emitText.call(this, element.$text);
    }
  } else {
    emitStart.call(this, name, element.$);
    emitText.call(this, element);
  }
  if(!onLeave) {
    emitEnd.call(this, name);
  }
}

// Starts parsing the source stream and emitting various events.
// The Expat parser is assigned several listeners for this purpose.
function parse(stream) {
  var self = this;
  var xml = new expat.Parser();
  var stack = [];
  var trace = {};
  var curr = {
    element: {},
    collect: this._collect,
    fullText: '',
    space: 0,
    path: '',
    context: {}
  };
  var fa = this._fa;
  fa.setState(this._startState);

  // A listener is assigned on opening tag encounter.
  // Here we traverse the configured finite automata use the stack
  // to form the context and trace for selector event emission.
  xml.on('startElement', function(name, attr) {
    stack.push(curr);
    trace[curr.path] = curr.element;
    var context = Object.create(curr.context);
    var element = {
      $: attr,
      $name: name,
      $text: ''
    };
    var parent = curr.element;
    curr = {
      element: element,
      collect: false,
      fullText: '',
      space: 0,
      path: curr.path + '/' + name,
      context: context
    };
    self._collect = false;
    fa.enter(name, [element, context, trace]);
    name = element.$name;
    curr.collect = self._collect;
    if (curr.collect) {
      var container;
      if (Object.hasOwnProperty.call(parent, name)) {
        container = parent[name];
        container.push(element);
      } else {
        container = [element];
        parent[name] = container;
      }
    } else {
      parent[name] = element;
      context[name] = element;
    }
    if (self._bufferLevel === 0 && self._emitData) {
      emitStart.call(self, name, element.$);
    }
  });

  // A listener is assigned on closing tag encounter.
  // Current node structure object is finalized. A selector listener is
  // invoked with current node, context, and trace; these arguments are
  // removed from the stack afterwards.
  xml.on('endElement', function(name) {
    var prev = stack.pop();
    var element = curr.element;
    var text = curr.fullText;
    var attr = element.$;
    if (typeof attr !== 'object') {
      attr = {};
    }
    var name = element.$name;
    self._name = name;
    delete element.$;
    delete element.$text;
    delete element.$name;
    var val = element;
    if (isEmpty(element) && isEmpty(attr)) {
      val = text;
    } else if (!isEmpty(attr)) {
      element.$ = attr;
    }
    if (text !== '') {
      element.$text = text;
    }
    curr.context[name] = val;
    if (curr.collect) {
      var container = prev.element[name];
      container[container.length - 1] = val;
    } else {
      prev.element[name] = val;
    }
    fa.leave([element, curr.context, trace]);
    if (self._bufferLevel === 0 && self._emitData) {
      emitEnd.call(self, name);
    }
    curr = prev;
    this._collect = curr.collect;
  });

  // Collect node text part by part
  // (and trim leading and trailing whitespace).
  xml.on('text', function(text) {
    curr.element.$text = text;
    fa.run('state', [curr.element, curr.context, trace]);
    if (self._bufferLevel === 0 && self._emitData) {
      emitText.call(self, text);
    }
    var trimmed = curr.element.$text.trim();
    var spaced = curr.element.$text.substr(0, 1);
    spaced = (spaced !== '') && (spaced.trim() === '');
    var after = curr.element.$text.substr(-1, 1);
    after = (after !== '') && (after.trim() === '');
    switch (curr.space) {
      // No words yet (pass through spaces).
      case 0:
        if (trimmed !== '') {
          curr.space = after ? 2 : 1;
        }
        break;

      // Immediately after text or entity.
      case 1:
        if (trimmed === '') {
          curr.space = 2;
        } else {
          if (spaced) {
            curr.fullText += ' ';
          }
          if (after) {
            curr.space = 2;
          }
        }
        break;

      // Some words were emitted, pass through spaces again.
      // Emit spaces only when a word is encountered afterwards.
      case 2:
        if (trimmed !== '') {
          curr.fullText += ' ';
          curr.space = 1;
        }
        break;
    }
    curr.fullText += trimmed;
  });

  // Pass data from stream to parser.
  this._stream.on('data', function(data) {
    xml.parse(data, false);
  });

  // End parsing on stream EOF and emit an *end* event ourselves.
  this._stream.on('end', function() {
    xml.parse('', true);
    self.emit('end');
  });
}
<|endoftext|>


<commit_before>/**
 * node-zip-stream
 *
 * Copyright (c) 2014 Chris Talkington, contributors.
 * Licensed under the MIT license.
 * https://github.com/ctalkington/node-zip-stream/blob/master/LICENSE-MIT
 */
var inherits = require('util').inherits;
var Transform = require('stream').Transform || require('readable-stream').Transform;
var PassThrough = require('stream').PassThrough || require('readable-stream').PassThrough;

var ChecksumStream = require('./util/ChecksumStream');
var DeflateRawChecksum = require('./util/DeflateRawChecksum');
var headers = require('./headers');
var util = require('./util');

var ZipStream = module.exports = function(options) {
  options = this.options = util.defaults(options, {
    comment: '',
    forceUTC: false,
    level: 1
  });

  if (typeof options.zlib !== 'object') {
    options.zlib = {};
  }

  if (typeof options.level === 'number' && options.level >= 0) {
    options.zlib.level = options.level;
    delete options.level;
  }

  Transform.call(this, options);

  this.offset = 0;
  this.files = [];

  this._finalize = false;
  this._finalized = false;
  this._processing = false;
};

inherits(ZipStream, Transform);

ZipStream.prototype._afterAppend = function(file) {
  this.files.push(file);
  this._processing = false;

  if (this._finalize) {
    this.finalize();
  }
};

ZipStream.prototype._appendBuffer = function(source, data, callback) {
  var self = this;

  var file = data;
  file.offset = self.offset;

  self.write(headers.encode('file', file));

  function onend() {
    self.write(headers.encode('fileDescriptor', file));

    self._afterAppend(file);

    callback(null);
  }

  if (file.store) {
    file.uncompressedSize = source.length;
    file.compressedSize = file.uncompressedSize;
    file.crc32 = util.crc32(source).digest();

    self.write(source);
    onend();
  } else {
    var processStream = self._newProcessStream(file.store);
    processStream.on('error', callback);

    processStream.on('end', function() {
      file.crc32 = processStream.digest;
      file.uncompressedSize = processStream.rawSize;
      file.compressedSize = processStream.compressedSize || processStream.rawSize;

      onend();
    });

    processStream.pipe(self, { end: false });

    processStream.end(source);
  }
};

ZipStream.prototype._appendStream = function(source, data, callback) {
  var self = this;

  var file = data;
  file.offset = self.offset;

  self.write(headers.encode('file', file));

  function onend() {
    self.write(headers.encode('fileDescriptor', file));

    self._afterAppend(file);

    callback(null);
  }

  var processStream = self._newProcessStream(file.store);
  processStream.on('error', callback);

  processStream.on('end', function() {
    file.crc32 = processStream.digest;
    file.uncompressedSize = processStream.rawSize;
    file.compressedSize = processStream.compressedSize || processStream.rawSize;

    onend();
  });

  processStream.pipe(self, { end: false });

  source.pipe(processStream);
};

ZipStream.prototype._emitErrorCallback = function(err, data) {
  if (err) {
    this.emit('error', err);
  }
};

ZipStream.prototype._newProcessStream = function(store) {
  var process;

  if (store) {
    process = new ChecksumStream();
  } else {
    process = new DeflateRawChecksum(this.options.zlib);
  }

  return process;
};

ZipStream.prototype._normalizeFileData = function(data) {
  data = util.defaults(data, {
    type: 'file',
    name: null,
    date: null,
    store: false,
    comment: ''
  });

  data.name = util.sanitizeFilePath(data.name);

  if (typeof data.lastModifiedDate !== 'number') {
    data.lastModifiedDate = util.dosDateTime(data.date, this.options.forceUTC);
  }

  if (this.options.zlib && this.options.zlib.level === 0) {
    data.store = true;
  }

  data.flags = (1 << 3);
  data.compressionMethod = data.store ? 0 : 8;
  data.uncompressedSize = 0;
  data.compressedSize = 0;

  return data;
};

ZipStream.prototype._normalizeSource = function(source) {
  if (typeof source === 'string') {
    return new Buffer(source);
  } else if (util.isStream(source) && !source._readableState) {
    var normalized = new PassThrough();
    source.pipe(normalized);

    return normalized;
  }

  return source;
};

ZipStream.prototype._transform = function(chunk, encoding, callback) {
  callback(null, chunk);
};

ZipStream.prototype._writeCentralDirectory = function() {
  var files = this.files;
  var comment = this.options.comment;
  var cdoffset = this.offset;
  var cdsize = 0;

  var centralDirectoryBuffer;
  for (var i = 0; i < files.length; i++) {
    var file = files[i];

    centralDirectoryBuffer = headers.encode('centralDirectory', file);
    this.write(centralDirectoryBuffer);
    cdsize += centralDirectoryBuffer.length;
  }

  var centralDirectoryFooterData = {
    directoryRecordsDisk: files.length,
    directoryRecords: files.length,
    centralDirectorySize: cdsize,
    centralDirectoryOffset: cdoffset,
    comment: comment
  };

  this.write(headers.encode('centralFooter', centralDirectoryFooterData));
};

ZipStream.prototype.entry = function(source, data, callback) {
  if (typeof callback !== 'function') {
    callback = this._emitErrorCallback.bind(this);
  }

  if (this._processing) {
    callback(new Error('already processing an entry'));
    return;
  }

  if (this._finalize || this._finalized) {
    callback(new Error('entry after finalize()'));
    return;
  }

  data = this._normalizeFileData(data);

  if (data.type !== 'file') {
    callback(new Error('only "file" entries are currently supported'));
    return;
  }

  if (typeof data.name !== 'string' || data.name.length === 0) {
    callback(new Error('filename must be a non-empty string value'));
    return;
  }

  this._processing = true;
  source = this._normalizeSource(source);

  if (Buffer.isBuffer(source)) {
    this._appendBuffer(source, data, callback);
  } else if (util.isStream(source)) {
    this._appendStream(source, data, callback);
  } else {
    this._processing = false;
    callback(new Error('input source must be valid Stream or Buffer instance'));
    return;
  }
};

ZipStream.prototype.finalize = function() {
  if (this._processing) {
    this._finalize = true;
    return;
  }

  this._writeCentralDirectory();
  this._finalized = true;
  this.end();
};

ZipStream.prototype.write = function(chunk, cb) {
  if (chunk) {
    this.offset += chunk.length;
  }

  return Transform.prototype.write.call(this, chunk, cb);
};<commit_msg>refine zlib option handling.<commit_after>/**
 * node-zip-stream
 *
 * Copyright (c) 2014 Chris Talkington, contributors.
 * Licensed under the MIT license.
 * https://github.com/ctalkington/node-zip-stream/blob/master/LICENSE-MIT
 */
var inherits = require('util').inherits;
var Transform = require('stream').Transform || require('readable-stream').Transform;
var PassThrough = require('stream').PassThrough || require('readable-stream').PassThrough;

var ChecksumStream = require('./util/ChecksumStream');
var DeflateRawChecksum = require('./util/DeflateRawChecksum');
var headers = require('./headers');
var util = require('./util');

var ZipStream = module.exports = function(options) {
  options = this.options = util.defaults(options, {
    comment: '',
    forceUTC: false
  });

  if (typeof options.zlib !== 'object') {
    options.zlib = {};
  }

  if (typeof options.level === 'number' && options.level >= 0) {
    options.zlib.level = options.level;
    delete options.level;
  } else if (typeof options.zlib.level !== 'number') {
    options.zlib.level = 1;
  }

  Transform.call(this, options);

  this.offset = 0;
  this.files = [];

  this._finalize = false;
  this._finalized = false;
  this._processing = false;
};

inherits(ZipStream, Transform);

ZipStream.prototype._afterAppend = function(file) {
  this.files.push(file);
  this._processing = false;

  if (this._finalize) {
    this.finalize();
  }
};

ZipStream.prototype._appendBuffer = function(source, data, callback) {
  var self = this;

  var file = data;
  file.offset = self.offset;

  self.write(headers.encode('file', file));

  function onend() {
    self.write(headers.encode('fileDescriptor', file));

    self._afterAppend(file);

    callback(null);
  }

  if (file.store) {
    file.uncompressedSize = source.length;
    file.compressedSize = file.uncompressedSize;
    file.crc32 = util.crc32(source).digest();

    self.write(source);
    onend();
  } else {
    var processStream = self._newProcessStream(file.store);
    processStream.on('error', callback);

    processStream.on('end', function() {
      file.crc32 = processStream.digest;
      file.uncompressedSize = processStream.rawSize;
      file.compressedSize = processStream.compressedSize || processStream.rawSize;

      onend();
    });

    processStream.pipe(self, { end: false });

    processStream.end(source);
  }
};

ZipStream.prototype._appendStream = function(source, data, callback) {
  var self = this;

  var file = data;
  file.offset = self.offset;

  self.write(headers.encode('file', file));

  function onend() {
    self.write(headers.encode('fileDescriptor', file));

    self._afterAppend(file);

    callback(null);
  }

  var processStream = self._newProcessStream(file.store);
  processStream.on('error', callback);

  processStream.on('end', function() {
    file.crc32 = processStream.digest;
    file.uncompressedSize = processStream.rawSize;
    file.compressedSize = processStream.compressedSize || processStream.rawSize;

    onend();
  });

  processStream.pipe(self, { end: false });

  source.pipe(processStream);
};

ZipStream.prototype._emitErrorCallback = function(err, data) {
  if (err) {
    this.emit('error', err);
  }
};

ZipStream.prototype._newProcessStream = function(store) {
  var process;

  if (store) {
    process = new ChecksumStream();
  } else {
    process = new DeflateRawChecksum(this.options.zlib);
  }

  return process;
};

ZipStream.prototype._normalizeFileData = function(data) {
  data = util.defaults(data, {
    type: 'file',
    name: null,
    date: null,
    store: false,
    comment: ''
  });

  data.name = util.sanitizeFilePath(data.name);

  if (typeof data.lastModifiedDate !== 'number') {
    data.lastModifiedDate = util.dosDateTime(data.date, this.options.forceUTC);
  }

  if (this.options.zlib && this.options.zlib.level === 0) {
    data.store = true;
  }

  data.flags = (1 << 3);
  data.compressionMethod = data.store ? 0 : 8;
  data.uncompressedSize = 0;
  data.compressedSize = 0;

  return data;
};

ZipStream.prototype._normalizeSource = function(source) {
  if (typeof source === 'string') {
    return new Buffer(source);
  } else if (util.isStream(source) && !source._readableState) {
    var normalized = new PassThrough();
    source.pipe(normalized);

    return normalized;
  }

  return source;
};

ZipStream.prototype._transform = function(chunk, encoding, callback) {
  callback(null, chunk);
};

ZipStream.prototype._writeCentralDirectory = function() {
  var files = this.files;
  var comment = this.options.comment;
  var cdoffset = this.offset;
  var cdsize = 0;

  var centralDirectoryBuffer;
  for (var i = 0; i < files.length; i++) {
    var file = files[i];

    centralDirectoryBuffer = headers.encode('centralDirectory', file);
    this.write(centralDirectoryBuffer);
    cdsize += centralDirectoryBuffer.length;
  }

  var centralDirectoryFooterData = {
    directoryRecordsDisk: files.length,
    directoryRecords: files.length,
    centralDirectorySize: cdsize,
    centralDirectoryOffset: cdoffset,
    comment: comment
  };

  this.write(headers.encode('centralFooter', centralDirectoryFooterData));
};

ZipStream.prototype.entry = function(source, data, callback) {
  if (typeof callback !== 'function') {
    callback = this._emitErrorCallback.bind(this);
  }

  if (this._processing) {
    callback(new Error('already processing an entry'));
    return;
  }

  if (this._finalize || this._finalized) {
    callback(new Error('entry after finalize()'));
    return;
  }

  data = this._normalizeFileData(data);

  if (data.type !== 'file') {
    callback(new Error('only "file" entries are currently supported'));
    return;
  }

  if (typeof data.name !== 'string' || data.name.length === 0) {
    callback(new Error('filename must be a non-empty string value'));
    return;
  }

  this._processing = true;
  source = this._normalizeSource(source);

  if (Buffer.isBuffer(source)) {
    this._appendBuffer(source, data, callback);
  } else if (util.isStream(source)) {
    this._appendStream(source, data, callback);
  } else {
    this._processing = false;
    callback(new Error('input source must be valid Stream or Buffer instance'));
    return;
  }
};

ZipStream.prototype.finalize = function() {
  if (this._processing) {
    this._finalize = true;
    return;
  }

  this._writeCentralDirectory();
  this._finalized = true;
  this.end();
};

ZipStream.prototype.write = function(chunk, cb) {
  if (chunk) {
    this.offset += chunk.length;
  }

  return Transform.prototype.write.call(this, chunk, cb);
};<|endoftext|>


<commit_before>(function(){

	/**
	 * Reticle 3D Sprite
	 * @param {THREE.Color} [color=0xfffff] - Color of the reticle sprite
	 * @param {string} [url=PANOLENS.DataImage.Reticle] - Image asset url
	 */
	PANOLENS.Reticle = function ( color, url ) {

		var map, material;

		color = color || 0xffffff;
		url = url || PANOLENS.DataImage.Reticle;

		map = PANOLENS.Utils.TextureLoader.load( url );
		material = new THREE.SpriteMaterial( { map: map, color: color } );

		THREE.Sprite.call( this, material );

		this.visible = false;

	}

	PANOLENS.Reticle.prototype = Object.create( THREE.Sprite.prototype );

	PANOLENS.Reticle.prototype.constructor = PANOLENS.Reticle;

	PANOLENS.Reticle.prototype.show = function () {

		this.visible = true;

	};

	PANOLENS.Reticle.prototype.hide = function () {

		this.visible = false;

	};

})();<commit_msg>Fix reticle render order and depthTest<commit_after>(function(){

	/**
	 * Reticle 3D Sprite
	 * @param {THREE.Color} [color=0xfffff] - Color of the reticle sprite
	 * @param {string} [url=PANOLENS.DataImage.Reticle] - Image asset url
	 */
	PANOLENS.Reticle = function ( color, url ) {

		var map, material;

		color = color || 0xffffff;
		url = url || PANOLENS.DataImage.Reticle;

		map = PANOLENS.Utils.TextureLoader.load( url );
		material = new THREE.SpriteMaterial( { map: map, color: color, depthTest: false } );

		THREE.Sprite.call( this, material );

		this.visible = false;
		this.renderOrder = 10;

	}

	PANOLENS.Reticle.prototype = Object.create( THREE.Sprite.prototype );

	PANOLENS.Reticle.prototype.constructor = PANOLENS.Reticle;

	PANOLENS.Reticle.prototype.show = function () {

		this.visible = true;

	};

	PANOLENS.Reticle.prototype.hide = function () {

		this.visible = false;

	};

})();<|endoftext|>


<commit_before>/**
 * class Jax.Texture
 * Creates a managed WebGL texture.
 **/
Jax.Texture = (function() {
  function imageLoaded(self, isImageArray) {
    var onload = self.options.onload || self.onload;

    if (!isImageArray) {
      if (onload) onload.call(self, self.image);
      self.loaded = true;
    } else {
      self.images.load_count++;
      if (self.images.load_count == self.images.length) {
        /* all done */
        if (onload) onload.call(self, self.image);
        self.loaded = true;
      }
    }
  }

  function build(self, context) {
    self.handles[context.id] = context.glCreateTexture();
  }

  function generateTexture(context, self) {
    var data_type = self.options.data_type, format = self.options.format, target = self.options.target;
    if (self.image) {
      switch(target) {
        case GL_TEXTURE_2D:
          context.glTexImage2D(target, 0, format, format, data_type, self.image);
          break;
        case GL_TEXTURE_CUBE_MAP:
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, format, format, data_type, self.image);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, format, format, data_type, self.image);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, format, format, data_type, self.image);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, format, format, data_type, self.image);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, format, format, data_type, self.image);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, format, format, data_type, self.image);
          break;
        default: throw new Error("Unexpected texture target "+target+"; use GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP");
      }
    } else if (self.images) {
      switch(target) {
        case GL_TEXTURE_2D:
          context.glTexImage2D(target, 0, format, format, data_type, self.images[0]);
          break;
        case GL_TEXTURE_CUBE_MAP:
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, format, format, data_type, self.images[GL_TEXTURE_CUBE_MAP_POSITIVE_X] || self.images[0]);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, format, format, data_type, self.images[GL_TEXTURE_CUBE_MAP_POSITIVE_Y] || self.images[1]);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, format, format, data_type, self.images[GL_TEXTURE_CUBE_MAP_POSITIVE_Z] || self.images[2]);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, format, format, data_type, self.images[GL_TEXTURE_CUBE_MAP_NEGATIVE_X] || self.images[3]);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, format, format, data_type, self.images[GL_TEXTURE_CUBE_MAP_NEGATIVE_Y] || self.images[4]);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, format, format, data_type, self.images[GL_TEXTURE_CUBE_MAP_NEGATIVE_Z] || self.images[5]);
          break;
        default: throw new Error("Unexpected texture target "+target+"; use GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP");
      }
    } else {
      // no images at all -- load the texture with empty data; it's probably for a framebuffer
      var width = self.options.width, height = self.options.height;
      if (!width || !height) throw new Error("Can't build an empty texture without at least a width and height");

      function ti2d(glEnum) {
        try {
          context.glTexImage2D(glEnum, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, null);
        } catch (e) {
          var tex = new Uint8Array(width*height*Jax.Util.sizeofFormat(format));
          context.glTexImage2D(glEnum, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, tex);
        }
      }

      switch(target) {
        case GL_TEXTURE_2D:
          ti2d(GL_TEXTURE_2D);
          break;
        case GL_TEXTURE_CUBE_MAP:
          ti2d(GL_TEXTURE_CUBE_MAP_POSITIVE_X);
          ti2d(GL_TEXTURE_CUBE_MAP_POSITIVE_Y);
          ti2d(GL_TEXTURE_CUBE_MAP_POSITIVE_Z);
          ti2d(GL_TEXTURE_CUBE_MAP_NEGATIVE_X);
          ti2d(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y);
          ti2d(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);
          break;
        default: throw new Error("Unexpected texture target "+target+"; use GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP");
      }
    }
  }

  /* pushLevel/popLevel are used for automatic management of glActiveTexture's.
    The general concept is that you can do something like:

      tex1.bind(context, function() {
        tex2.bind(context, function() {
          // render stuff.
          // tex1 => GL_TEXTURE0, tex1.textureLevel => 0
          // tex2 => GL_TEXTURE1, tex2.textureLevel => 1

          tex3.bind(context, 5, function() {
            // tex3 => GL_TEXTURE5, tex3.textureLevel => 5
          });
        });
      });
   */
  function pushLevel(self, level, context) {
    if (level == null) level = Jax.Texture._level++;
    self.textureLevel = level;
    context.glActiveTexture(GL_TEXTURES[level]);
  }

  function popLevel(self, context) {
    Jax.Texture._level = self.textureLevel - 1;
    if (Jax.Texture._level < 0) Jax.Texture._level = 0;
    delete self.textureLevel;
  }

  return Jax.Class.create({
    /**
     * new Jax.Texture(url[, options])
     * - url (String): the URL or relative path to the image to be loaded.
     * - options (Object): a generic object optionally consisting of the following properties:
     * new Jax.Texture(urls[, options])
     * - urls (Array): an array of URLs or relative paths to the images to be loaded. This is intended
     *                 for use with cube maps. If used with a cube map, 6 paths must be provided.
     *                 If used with a standard 2D texture, only the first path in the array will be used.
     * - options (Object): a generic object optionally consisting of the following properties:
     * new Jax.Texture(options)
     * - options (Object): a generic object optionally consisting of the following properties, plus a mandatory
     *                     _width_ and _height_ in pixels:
     *
     *   * min_filter: GL_NEAREST
     *   * mag_filter: GL_NEARETS
     *   * generate_mipmap: true
     *   * mipmap_hint: GL_DONT_CARE
     *   * format: GL_RGBA
     *   * target: GL_TEXTURE_2D
     *   * data_type: GL_UNSIGNED_BYTE
     *   * wrap_s: GL_REPEAT
     *   * wrap_t: GL_REPEAT
     *   * flip_y: false
     *   * premultiply_alpha: false
     *   * colorspace_conversion: true
     *   * onload: null - a function to be called after the image has been loaded. This function
     *                    will not be called if the image fails to load.
     *
     **/
    initialize: function(path_or_array, options) {
      this.handles = {};
      this.loaded = false;
      this.valid = [];

      if (!options && typeof(path_or_array) == "object" && path_or_array.length == undefined) {
        options = path_or_array;
        path_or_array = null;
      }

      var self = this;
      this.options = Jax.Util.normalizeOptions(options, {
        min_filter: GL_NEAREST,
        mag_filter: GL_NEAREST,
        generate_mipmap: true,
        mipmap_hint: GL_DONT_CARE,
        format: GL_RGBA,
        target: GL_TEXTURE_2D,
        data_type: GL_UNSIGNED_BYTE,
        wrap_s: GL_REPEAT,
        wrap_t: GL_REPEAT,
        flip_y: false,
        premultiply_alpha: false,
        colorspace_conversion: true,
        onload: null
      });

      if (path_or_array) {
        if (typeof(path_or_array) == "string") {
          this.image = new Image();
          this.image.onload = function() { imageLoaded(self, false); };
          this.image.src = path_or_array;
        } else {
          var onload = function() { imageLoaded(self, true); };
          this.images = [];
          this.images.load_count = 0;
          for (var i = 0; i < path_or_array.length; i++) {
            this.images[i] = new Image();
            this.images[i].onload = onload;
            this.images[i].src = path_or_array[i];
          }
        }
      }
      else {
        // nothing to load
        this.options.generate_mipmap = !!(options && options.generate_mipmap);
        this.loaded = true;
      }
    },

    refresh: function(context) {
      if (!this.ready()) return;

      context.glBindTexture(this.options.target, this.getHandle(context));
      generateTexture(context, this);
      context.glTexParameteri(this.options.target, GL_TEXTURE_MAG_FILTER, this.options.mag_filter);
      context.glTexParameteri(this.options.target, GL_TEXTURE_MIN_FILTER, this.options.min_filter);
      context.glTexParameteri(this.options.target, GL_TEXTURE_WRAP_S, this.options.wrap_s);
      context.glTexParameteri(this.options.target, GL_TEXTURE_WRAP_T, this.options.wrap_t);
      context.glPixelStorei(GL_UNPACK_FLIP_Y_WEBGL, this.options.flip_y);
      context.glPixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.options.premultiply_alpha);
      context.glPixelStorei(GL_UNPACK_COLORSPACE_CONVERSION_WEBGL, this.options.colorspace_conversion ? GL_BROWSER_DEFAULT_WEBGL : GL_NONE);

      if (this.options.generate_mipmap) {
        this.generateMipmap(context);
      }

      this.valid[context.id] = true;
    },

    generateMipmap: function(context) {
      // FIXME why does this raise 1280 invalid enum?
//      context.glHint(GL_GENERATE_MIPMAP_HINT, this.options.mipmap_hint);
      context.glGenerateMipmap(this.options.target);
    },

    invalidate: function() { this.valid.clear(); },

    dispose: function(context) {
      context.glDeleteTexture(getHandle(context));
      delete this.handles[context.id];
    },

    getHandle: function(context) {
      if (!this.handles[context.id]) {
        build(this, context);
        this.refresh(context);
      }
      return this.handles[context.id];
    },

    isValid: function(context) {
      return !!this.valid[context.id];
    },

    bind: function(context, level, callback) {
      if (!this.ready()) return; // no texture to display, yet... but not worth crashing over.
      if (!this.isValid(context)) this.refresh(context);

      if (typeof(level) == "number")
        pushLevel(this, level, context);
      else if (typeof(level) == "function") { callback = level; pushLevel(this, null, context); }

      context.glBindTexture(this.options.target, this.getHandle(context));
      if (callback)
      {
        callback.call(this, this.textureLevel);
        this.unbind(context);
      }
    },

    unbind: function(context) {
      if (this.textureLevel != undefined) context.glActiveTexture(GL_TEXTURES[this.textureLevel]);
      context.glBindTexture(this.options.target, null);
      popLevel(this, context);
    },

    ready: function() {
      return this.loaded;
    }
  });
})();

Jax.Texture._level = 0;
<commit_msg>Jax.Texture: added getters for all options<commit_after>/**
 * class Jax.Texture
 * Creates a managed WebGL texture.
 **/
Jax.Texture = (function() {
  function imageLoaded(self, isImageArray) {
    var onload = self.options.onload || self.onload;

    if (!isImageArray) {
      if (onload) onload.call(self, self.image);
      self.loaded = true;
    } else {
      self.images.load_count++;
      if (self.images.load_count == self.images.length) {
        /* all done */
        if (onload) onload.call(self, self.image);
        self.loaded = true;
      }
    }
  }

  function build(self, context) {
    self.handles[context.id] = context.glCreateTexture();
  }

  function generateTexture(context, self) {
    var data_type = self.options.data_type, format = self.options.format, target = self.options.target;
    if (self.image) {
      switch(target) {
        case GL_TEXTURE_2D:
          context.glTexImage2D(target, 0, format, format, data_type, self.image);
          break;
        case GL_TEXTURE_CUBE_MAP:
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, format, format, data_type, self.image);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, format, format, data_type, self.image);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, format, format, data_type, self.image);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, format, format, data_type, self.image);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, format, format, data_type, self.image);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, format, format, data_type, self.image);
          break;
        default: throw new Error("Unexpected texture target "+target+"; use GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP");
      }
    } else if (self.images) {
      switch(target) {
        case GL_TEXTURE_2D:
          context.glTexImage2D(target, 0, format, format, data_type, self.images[0]);
          break;
        case GL_TEXTURE_CUBE_MAP:
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, format, format, data_type, self.images[GL_TEXTURE_CUBE_MAP_POSITIVE_X] || self.images[0]);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, format, format, data_type, self.images[GL_TEXTURE_CUBE_MAP_POSITIVE_Y] || self.images[1]);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, format, format, data_type, self.images[GL_TEXTURE_CUBE_MAP_POSITIVE_Z] || self.images[2]);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, format, format, data_type, self.images[GL_TEXTURE_CUBE_MAP_NEGATIVE_X] || self.images[3]);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, format, format, data_type, self.images[GL_TEXTURE_CUBE_MAP_NEGATIVE_Y] || self.images[4]);
          context.glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, format, format, data_type, self.images[GL_TEXTURE_CUBE_MAP_NEGATIVE_Z] || self.images[5]);
          break;
        default: throw new Error("Unexpected texture target "+target+"; use GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP");
      }
    } else {
      // no images at all -- load the texture with empty data; it's probably for a framebuffer
      var width = self.options.width, height = self.options.height;
      if (!width || !height) throw new Error("Can't build an empty texture without at least a width and height");

      function ti2d(glEnum) {
        try {
          context.glTexImage2D(glEnum, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, null);
        } catch (e) {
          var tex = new Uint8Array(width*height*Jax.Util.sizeofFormat(format));
          context.glTexImage2D(glEnum, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, tex);
        }
      }

      switch(target) {
        case GL_TEXTURE_2D:
          ti2d(GL_TEXTURE_2D);
          break;
        case GL_TEXTURE_CUBE_MAP:
          ti2d(GL_TEXTURE_CUBE_MAP_POSITIVE_X);
          ti2d(GL_TEXTURE_CUBE_MAP_POSITIVE_Y);
          ti2d(GL_TEXTURE_CUBE_MAP_POSITIVE_Z);
          ti2d(GL_TEXTURE_CUBE_MAP_NEGATIVE_X);
          ti2d(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y);
          ti2d(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);
          break;
        default: throw new Error("Unexpected texture target "+target+"; use GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP");
      }
    }
  }

  /* pushLevel/popLevel are used for automatic management of glActiveTexture's.
    The general concept is that you can do something like:

      tex1.bind(context, function() {
        tex2.bind(context, function() {
          // render stuff.
          // tex1 => GL_TEXTURE0, tex1.textureLevel => 0
          // tex2 => GL_TEXTURE1, tex2.textureLevel => 1

          tex3.bind(context, 5, function() {
            // tex3 => GL_TEXTURE5, tex3.textureLevel => 5
          });
        });
      });
   */
  function pushLevel(self, level, context) {
    if (level == null) level = Jax.Texture._level++;
    self.textureLevel = level;
    context.glActiveTexture(GL_TEXTURES[level]);
  }

  function popLevel(self, context) {
    Jax.Texture._level = self.textureLevel - 1;
    if (Jax.Texture._level < 0) Jax.Texture._level = 0;
    delete self.textureLevel;
  }

  return Jax.Class.create({
    /**
     * new Jax.Texture(url[, options])
     * - url (String): the URL or relative path to the image to be loaded.
     * - options (Object): a generic object optionally consisting of the following properties:
     * new Jax.Texture(urls[, options])
     * - urls (Array): an array of URLs or relative paths to the images to be loaded. This is intended
     *                 for use with cube maps. If used with a cube map, 6 paths must be provided.
     *                 If used with a standard 2D texture, only the first path in the array will be used.
     * - options (Object): a generic object optionally consisting of the following properties:
     * new Jax.Texture(options)
     * - options (Object): a generic object optionally consisting of the following properties, plus a mandatory
     *                     _width_ and _height_ in pixels:
     *
     *   * min_filter: GL_NEAREST
     *   * mag_filter: GL_NEARETS
     *   * generate_mipmap: true
     *   * mipmap_hint: GL_DONT_CARE
     *   * format: GL_RGBA
     *   * target: GL_TEXTURE_2D
     *   * data_type: GL_UNSIGNED_BYTE
     *   * wrap_s: GL_REPEAT
     *   * wrap_t: GL_REPEAT
     *   * flip_y: false
     *   * premultiply_alpha: false
     *   * colorspace_conversion: true
     *   * onload: null - a function to be called after the image has been loaded. This function
     *                    will not be called if the image fails to load.
     *
     **/
    initialize: function(path_or_array, options) {
      this.handles = {};
      this.loaded = false;
      this.valid = [];

      if (!options && typeof(path_or_array) == "object" && path_or_array.length == undefined) {
        options = path_or_array;
        path_or_array = null;
      }

      var self = this;
      this.options = Jax.Util.normalizeOptions(options, {
        min_filter: GL_NEAREST,
        mag_filter: GL_NEAREST,
        generate_mipmap: true,
        mipmap_hint: GL_DONT_CARE,
        format: GL_RGBA,
        target: GL_TEXTURE_2D,
        data_type: GL_UNSIGNED_BYTE,
        wrap_s: GL_REPEAT,
        wrap_t: GL_REPEAT,
        flip_y: false,
        premultiply_alpha: false,
        colorspace_conversion: true,
        onload: null
      });

      if (path_or_array) {
        if (typeof(path_or_array) == "string") {
          this.image = new Image();
          this.image.onload = function() { imageLoaded(self, false); };
          this.image.src = path_or_array;
        } else {
          var onload = function() { imageLoaded(self, true); };
          this.images = [];
          this.images.load_count = 0;
          for (var i = 0; i < path_or_array.length; i++) {
            this.images[i] = new Image();
            this.images[i].onload = onload;
            this.images[i].src = path_or_array[i];
          }
        }
      }
      else {
        // nothing to load
        this.options.generate_mipmap = !!(options && options.generate_mipmap);
        this.loaded = true;
      }
    },

    getTarget: function() { return this.options.target; },
    getMinFilter: function() { return this.options.min_filter; },
    getMagFilter: function() { return this.options.mag_filter; },
    getGeneratesMipmaps: function() { return this.options.generate_mipmap; },
    getMipmapHint: function() { return this.options.mipmap_hint; },
    getFormat: function() { return this.options.format; },
    getDataType: function() { return this.options.data_type; },
    getWrapS: function() { return this.options.wrap_s; },
    getWrapT: function() { return this.options.wrap_t; },
    getFlipY: function() { return this.options.flip_y; },
    getPremultipliesAlpha: function() { return this.options.premultiply_alpha; },
    getDoesColorspaceConversion: function() { return this.options.colorspace_conversion; },
    getOnloadFunc: function() { return this.options.onload; },

    refresh: function(context) {
      if (!this.ready()) return;

      context.glBindTexture(this.options.target, this.getHandle(context));
      generateTexture(context, this);
      context.glTexParameteri(this.options.target, GL_TEXTURE_MAG_FILTER, this.options.mag_filter);
      context.glTexParameteri(this.options.target, GL_TEXTURE_MIN_FILTER, this.options.min_filter);
      context.glTexParameteri(this.options.target, GL_TEXTURE_WRAP_S, this.options.wrap_s);
      context.glTexParameteri(this.options.target, GL_TEXTURE_WRAP_T, this.options.wrap_t);
      context.glPixelStorei(GL_UNPACK_FLIP_Y_WEBGL, this.options.flip_y);
      context.glPixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.options.premultiply_alpha);
      context.glPixelStorei(GL_UNPACK_COLORSPACE_CONVERSION_WEBGL, this.options.colorspace_conversion ? GL_BROWSER_DEFAULT_WEBGL : GL_NONE);

      if (this.options.generate_mipmap) {
        this.generateMipmap(context);
      }

      context.glBindTexture(this.options.target, null);
      this.valid[context.id] = true;
    },

    generateMipmap: function(context) {
      // FIXME why does this raise 1280 invalid enum?
//      context.glHint(GL_GENERATE_MIPMAP_HINT, this.options.mipmap_hint);
      context.glGenerateMipmap(this.options.target);
    },

    invalidate: function() { this.valid.clear(); },

    dispose: function(context) {
      context.glDeleteTexture(getHandle(context));
      delete this.handles[context.id];
    },

    getHandle: function(context) {
      if (!this.handles[context.id]) {
        build(this, context);
        this.refresh(context);
      }
      return this.handles[context.id];
    },

    isValid: function(context) {
      return !!this.valid[context.id];
    },

    bind: function(context, level, callback) {
      if (!this.ready()) return; // no texture to display, yet... but not worth crashing over.
      if (!this.isValid(context)) this.refresh(context);

      if (typeof(level) == "number")
        pushLevel(this, level, context);
      else if (typeof(level) == "function") { callback = level; pushLevel(this, null, context); }

      context.glBindTexture(this.options.target, this.getHandle(context));
      if (callback)
      {
        callback.call(this, this.textureLevel);
        this.unbind(context);
      }
    },

    unbind: function(context) {
      if (this.textureLevel != undefined) context.glActiveTexture(GL_TEXTURES[this.textureLevel]);
      context.glBindTexture(this.options.target, null);
      popLevel(this, context);
    },

    ready: function() {
      return this.loaded;
    }
  });
})();

Jax.Texture._level = 0;
<|endoftext|>


<commit_before>/*jslint browser:true, plusplus:true, vars:true */
/*jshint browser:true, jquery:true */
/*jshint -W040:false */ /* to don't alert on "this" in triggerLoad and triggerError */

(function ($, window, document, undefined) {
    'use strict';

    // options
    var options = {
            autoInit: true,
            selector: 'img',
            classNojs: 'lazy',
            blankImage: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
            throttle: 99,
            loadEvent: 'pageshow', // check AJAX-loaded content in jQueryMobile
            updateEvent: 'load orientationchange resize scroll touchmove', // page-modified events
            forceEvent: '', // force loading of all elements
            oninit: null, // init handler
            onshow: null, // start loading handler
            onload: null, // load success handler
            onerror: null // error handler
        },
        elementOptions = {
            srcAttr: 'data-src',
            edgeX: 0,
            edgeY: 0,
            visibleOnly: true
        },
        $window = $(window),
        $isFunction = $.isFunction,
        docElement = document.documentElement,
    //  autoload all images in Opera Mini and some mobile browsers without scroll event or getBoundingClientRect()
        autoLoad = (window.onscroll === undefined || !!window.operamini || !docElement.getBoundingClientRect),
        dataLazied = 'lazied',
        elements = [],
        $data = $.data || function (el, name) {
            return $(el).data(name);
        },
        topLazy = 0,
    /*
     waitingMode=0 : no setTimeout
     waitingMode=1 : setTimeout, no deferred events
     waitingMode=2 : setTimeout, deferred events
     */
        waitingMode = 0;

    $.lazyLoadXT = $.extend(options, elementOptions, $.lazyLoadXT);

    /**
     * Return def if value is undefined, otherwise return value
     * @param {*} value
     * @param {*} def
     * @returns *
     */
    function getOrDef(value, def) {
        return value !== undefined ? value : def;
    }

    /**
     * Add new elements to lazy-load list:
     * $(elements).lazyLoadXT() or $(window).lazyLoadXT()
     *
     * @param {object} [overrides] loading of all elements
     */
    $.fn.lazyLoadXT = function (overrides) {
        overrides = overrides || {};

        var elementOptionsOverrides = {},
            blankImage = getOrDef(overrides.blankImage, options.blankImage),
            classNojs = getOrDef(overrides.classNojs, options.classNojs),
            checkDuplicates = getOrDef(overrides.checkDuplicates, true),
            prop;

        for (prop in elementOptions) {
            elementOptionsOverrides[prop] = getOrDef(overrides[prop], options[prop]);
        }

        return this.each(function () {
            if (this === window) {
                $(options.selector).lazyLoadXT(overrides);
            } else {
                // prevent duplicates
                if (checkDuplicates && $data(this, dataLazied)) {
                    return;
                }

                var $el = $(this);

                $el
                    .data(dataLazied, 1)
                    .removeClass(classNojs);

                if (blankImage && $el[0].tagName === 'IMG' && !this.getAttribute('src')) {
                    this.setAttribute('src', blankImage);
                }

                // clone elementOptionsOverrides object
                $el.lazyLoadXT = $.extend({}, elementOptionsOverrides);

                triggerEvent('init', $el);

                elements.unshift($el); // push it in the first position as we iterate elements in reverse order
            }
        });
    };

    /**
     * Process function/object event handler
     * @param {string} event suffix
     * @param {jQuery} $el
     */
    function triggerEvent(event, $el) {
        $el.trigger('lazy' + event, [$el]);

        var handler = options['on' + event];
        if (handler) {
            if ($isFunction(handler)) {
                handler.call($el[0]);
            } else {
                $el
                    .addClass(handler.addClass)
                    .removeClass(handler.removeClass);
            }
        }

        // queue next check as images may be resized after loading of actual file
        queueCheckLazyElements();
    }

    /**
     * Trigger onload/onerror handler
     * @param {Event} e
     */
    function triggerLoadOrError(e) {
        triggerEvent(e.type, $(this).off('load error', triggerLoadOrError));
    }

    /**
     * Load visible elements
     * @param {bool} [force] loading of all elements
     */
    function checkLazyElements(force) {
        if (!elements.length) {
            return;
        }

        force = force || autoLoad;

        topLazy = Infinity;

        var viewportTop    = $window.scrollTop(),
            viewportHeight = window.innerHeight || $window.height(),
            viewportWidth  = window.innerWidth  || $window.width(),
            i;

        for (i = elements.length - 1; i >= 0; i--) {
            var $el = elements[i],
                el = $el[0],
                objData = $el.lazyLoadXT;

            // remove items that are not in DOM
            if (!$.contains(docElement, el)) {
                elements.splice(i, 1);
            } else if (force || !objData.visibleOnly || el.offsetWidth > 0 || el.offsetHeight > 0) {
                var elPos = el.getBoundingClientRect(),
                    edgeX = objData.edgeX,
                    edgeY = objData.edgeY,
                    topEdge = (elPos.top + viewportTop - edgeY) - viewportHeight;

                if (force ||
                    ((topEdge <= viewportTop) && (elPos.bottom > -edgeY) &&
                        (elPos.left <= viewportWidth + edgeX) && (elPos.right > -edgeX))) {

                    triggerEvent('show', $el);

                    var srcAttr = objData.srcAttr,
                        src = $isFunction(srcAttr) ? srcAttr($el) : el.getAttribute(srcAttr);
                    if (src) {
                        $el.on('load error', triggerLoadOrError);
                        el.setAttribute('src', src);
                    }

                    elements.splice(i, 1);
                } else {
                    if (topEdge < topLazy) {
                        topLazy = topEdge;
                    }
                }
            }
        }

        if (!elements.length) {
            $(document).trigger('lazyloadall');
        }
    }

    /**
     * Run check of lazy elements after timeout
     */
    function timeoutLazyElements() {
        if (waitingMode > 1) {
            waitingMode = 1;
            checkLazyElements();
            setTimeout(timeoutLazyElements, options.throttle);
        } else {
            waitingMode = 0;
        }
    }

    /**
     * Queue check of lazy elements because of event e
     * @param {Event} [e]
     */
    function queueCheckLazyElements(e) {
        if (!elements.length) {
            return;
        }

        // fast check for scroll event without new visible elements
        if (e && e.type === 'scroll') {
            if (topLazy >= $window.scrollTop()) {
                return;
            }
        }

        if (!waitingMode) {
            setTimeout(timeoutLazyElements, 0);
        }

        waitingMode = 2;
    }

    /**
     * Initialize list of hidden elements
     */
    function initLazyElements() {
        $window.lazyLoadXT();
        queueCheckLazyElements();
    }

    /**
     * Loading of all elements
     */
    function forceLoadAll() {
        checkLazyElements(true);
    }

    /**
     * Initialization
     */
    $(document).ready(function () {
        $window
            .on(options.loadEvent, initLazyElements)
            .on(options.updateEvent, queueCheckLazyElements)
            .on(options.forceEvent, forceLoadAll);

        if (options.autoInit) {
            initLazyElements(); // standard initialization
        }
    });

})(window.jQuery || window.Zepto, window, document);
<commit_msg>minor code reformat<commit_after>/*jslint browser:true, plusplus:true, vars:true */
/*jshint browser:true, jquery:true */
/*jshint -W040:false */ /* to don't alert on "this" in triggerLoad and triggerError */

(function ($, window, document, undefined) {
    'use strict';

    // options
    var options = {
            autoInit: true,
            selector: 'img',
            classNojs: 'lazy',
            blankImage: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
            throttle: 99,
            loadEvent: 'pageshow', // check AJAX-loaded content in jQueryMobile
            updateEvent: 'load orientationchange resize scroll touchmove', // page-modified events
            forceEvent: '', // force loading of all elements
            oninit: null, // init handler
            onshow: null, // start loading handler
            onload: null, // load success handler
            onerror: null // error handler
        },
        elementOptions = {
            srcAttr: 'data-src',
            edgeX: 0,
            edgeY: 0,
            visibleOnly: true
        },
        $window = $(window),
        $isFunction = $.isFunction,
        docElement = document.documentElement,
    //  autoload all images in Opera Mini and some mobile browsers without scroll event or getBoundingClientRect()
        autoLoad = (window.onscroll === undefined || !!window.operamini || !docElement.getBoundingClientRect),
        dataLazied = 'lazied',
        elements = [],
        $data = $.data || function (el, name) {
            return $(el).data(name);
        },
        topLazy = 0,
    /*
     waitingMode=0 : no setTimeout
     waitingMode=1 : setTimeout, no deferred events
     waitingMode=2 : setTimeout, deferred events
     */
        waitingMode = 0;

    $.lazyLoadXT = $.extend(options, elementOptions, $.lazyLoadXT);

    /**
     * Return def if value is undefined, otherwise return value
     * @param {*} value
     * @param {*} def
     * @returns *
     */
    function getOrDef(value, def) {
        return value !== undefined ? value : def;
    }

    /**
     * Add new elements to lazy-load list:
     * $(elements).lazyLoadXT() or $(window).lazyLoadXT()
     *
     * @param {object} [overrides] loading of all elements
     */
    $.fn.lazyLoadXT = function (overrides) {
        overrides = overrides || {};

        var elementOptionsOverrides = {},
            blankImage = getOrDef(overrides.blankImage, options.blankImage),
            classNojs = getOrDef(overrides.classNojs, options.classNojs),
            checkDuplicates = getOrDef(overrides.checkDuplicates, true),
            prop;

        for (prop in elementOptions) {
            elementOptionsOverrides[prop] = getOrDef(overrides[prop], options[prop]);
        }

        return this.each(function () {
            if (this === window) {
                $(options.selector).lazyLoadXT(overrides);
            } else {
                // prevent duplicates
                if (checkDuplicates && $data(this, dataLazied)) {
                    return;
                }

                var $el = $(this);

                $el
                    .data(dataLazied, 1)
                    .removeClass(classNojs);

                if (blankImage && $el[0].tagName === 'IMG' && !this.getAttribute('src')) {
                    this.setAttribute('src', blankImage);
                }

                // clone elementOptionsOverrides object
                $el.lazyLoadXT = $.extend({}, elementOptionsOverrides);

                triggerEvent('init', $el);

                elements.unshift($el); // push it in the first position as we iterate elements in reverse order
            }
        });
    };

    /**
     * Process function/object event handler
     * @param {string} event suffix
     * @param {jQuery} $el
     */
    function triggerEvent(event, $el) {
        $el.trigger('lazy' + event, [$el]);

        var handler = options['on' + event];
        if (handler) {
            if ($isFunction(handler)) {
                handler.call($el[0]);
            } else {
                $el
                    .addClass(handler.addClass)
                    .removeClass(handler.removeClass);
            }
        }

        // queue next check as images may be resized after loading of actual file
        queueCheckLazyElements();
    }

    /**
     * Trigger onload/onerror handler
     * @param {Event} e
     */
    function triggerLoadOrError(e) {
        triggerEvent(e.type, $(this).off('load error', triggerLoadOrError));
    }

    /**
     * Load visible elements
     * @param {bool} [force] loading of all elements
     */
    function checkLazyElements(force) {
        if (!elements.length) {
            return;
        }

        force = force || autoLoad;

        topLazy = Infinity;

        var viewportTop = $window.scrollTop(),
            viewportHeight = window.innerHeight || $window.height(),
            viewportWidth = window.innerWidth || $window.width(),
            i;

        for (i = elements.length - 1; i >= 0; i--) {
            var $el = elements[i],
                el = $el[0],
                objData = $el.lazyLoadXT;

            // remove items that are not in DOM
            if (!$.contains(docElement, el)) {
                elements.splice(i, 1);
            } else if (force || !objData.visibleOnly || el.offsetWidth > 0 || el.offsetHeight > 0) {
                var elPos = el.getBoundingClientRect(),
                    edgeX = objData.edgeX,
                    edgeY = objData.edgeY,
                    topEdge = (elPos.top + viewportTop - edgeY) - viewportHeight;

                if (force || (topEdge <= viewportTop && elPos.bottom > -edgeY &&
                        elPos.left <= viewportWidth + edgeX && elPos.right > -edgeX)) {

                    triggerEvent('show', $el);

                    var srcAttr = objData.srcAttr,
                        src = $isFunction(srcAttr) ? srcAttr($el) : el.getAttribute(srcAttr);
                    if (src) {
                        $el.on('load error', triggerLoadOrError);
                        el.setAttribute('src', src);
                    }

                    elements.splice(i, 1);
                } else {
                    if (topEdge < topLazy) {
                        topLazy = topEdge;
                    }
                }
            }
        }

        if (!elements.length) {
            $(document).trigger('lazyloadall');
        }
    }

    /**
     * Run check of lazy elements after timeout
     */
    function timeoutLazyElements() {
        if (waitingMode > 1) {
            waitingMode = 1;
            checkLazyElements();
            setTimeout(timeoutLazyElements, options.throttle);
        } else {
            waitingMode = 0;
        }
    }

    /**
     * Queue check of lazy elements because of event e
     * @param {Event} [e]
     */
    function queueCheckLazyElements(e) {
        if (!elements.length) {
            return;
        }

        // fast check for scroll event without new visible elements
        if (e && e.type === 'scroll') {
            if (topLazy >= $window.scrollTop()) {
                return;
            }
        }

        if (!waitingMode) {
            setTimeout(timeoutLazyElements, 0);
        }

        waitingMode = 2;
    }

    /**
     * Initialize list of hidden elements
     */
    function initLazyElements() {
        $window.lazyLoadXT();
        queueCheckLazyElements();
    }

    /**
     * Loading of all elements
     */
    function forceLoadAll() {
        checkLazyElements(true);
    }

    /**
     * Initialization
     */
    $(document).ready(function () {
        $window
            .on(options.loadEvent, initLazyElements)
            .on(options.updateEvent, queueCheckLazyElements)
            .on(options.forceEvent, forceLoadAll);

        if (options.autoInit) {
            initLazyElements(); // standard initialization
        }
    });

})(window.jQuery || window.Zepto, window, document);
<|endoftext|>


<commit_before>(function($) {

 //The timeout has to live in this scope
 var timeout;

 $.fn.livesearch = function(options) {
  options = options || {};
  return $(this).each(function() {
    var livesearch = $(this).data('livesearch');
		if (!livesearch) {
		  livesearch = new LiveSearch($(this), options);
			$(this).data('livesearch', livesearch);
		}
  });
};

function LiveSearch($elem, options) {
	this.$elem = $elem;
  this.$form = $elem.closest('form');
  this.options = $.extend({ delay: 1000, minimum_characters: 3 }, options);
  this.last_search = false;
  this.search_xhr;
  this.cache = {};
  this.active = true;
  this._attach();
}

$.extend(LiveSearch.prototype, {
  _attach: function() {
    var _this = this;
    this.$elem.bind("keypress cut paste input", function() {
      if(!_this.active) return;

      clearTimeout(timeout);
      timeout = function() { _this.search_for_value(); };
      setTimeout(timeout, _this.options.delay);
    });
    this.$form.bind("submit", function(e) {
      if(!_this.active) return;

      clearTimeout(timeout);
      _this.search_for_value();
    });
    this.$elem.bind('livesearch:suspend', function() {
      _this.active = false;
    });
    this.$elem.bind('livesearch:activate', function() {
      _this.active = true;
    });
  },

  search_for_value: function() {
    this.search(this.$elem.val());
  },

  suspend_while: function(func) {
    this.active = false;
    func();
    // TODO: this timeout is to to allow events to bubble before re-enabling, but I'm not sure
    // why bubbling doesn't occur synchronously.
    setTimeout(function() {
      this.active = true;
    }, 100);
  },

  search: function(value) {
    var _this = this;

    if(value == this.last_search) return;
    if(value.length < this.options.minimum_characters) return;

    if(this.search_xhr) this.search_xhr.abort();

    if(this.cache[value]) {
      this.$elem.trigger('livesearch:results', [this.cache[value]]);
    } else {
      this.search_xhr = $.get(this.$form.attr('action'), this.$form.serialize(), function(data) {
        _this.$elem.trigger('livesearch:results', [data]);
        _this.cache[value] = data;
      }, 'json');
    }

    this.last_search = value;
  }

});

})(jQuery);
<commit_msg>Livesearch engine: fix the delay.<commit_after>(function($) {

//The timeout has to live in this scope
var timeout;

$.fn.livesearch = function(options) {
  options = options || {};
  return $(this).each(function() {
    var livesearch = $(this).data('livesearch');
		if (!livesearch) {
		  livesearch = new LiveSearch($(this), options);
			$(this).data('livesearch', livesearch);
		}
  });
};

function LiveSearch($elem, options) {
	this.$elem = $elem;
  this.$form = $elem.closest('form');
  this.options = $.extend({ delay: 400, minimum_characters: 3 }, options);
  this.last_search = false;
  this.search_xhr;
  this.cache = {};
  this.active = true;
  this._attach();
}

$.extend(LiveSearch.prototype, {
  _attach: function() {
    var _this = this;
    this.$elem.bind("keypress cut paste input", function() {
      if(!_this.active) return;

      clearTimeout(timeout);
      timeout = setTimeout(function() {_this.search_for_value();}, _this.options.delay);
    });
    this.$form.bind("submit", function(e) {
      if(!_this.active) return;

      clearTimeout(timeout);
      _this.search_for_value();
    });
    this.$elem.bind('livesearch:suspend', function() {
      _this.active = false;
    });
    this.$elem.bind('livesearch:activate', function() {
      _this.active = true;
    });
  },

  search_for_value: function() {
    this.search(this.$elem.val());
  },

  suspend_while: function(func) {
    this.active = false;
    func();
    // TODO: this timeout is to to allow events to bubble before re-enabling, but I'm not sure
    // why bubbling doesn't occur synchronously.
    setTimeout(function() {
      this.active = true;
    }, 100);
  },

  search: function(value) {
    var _this = this;

    if(value == this.last_search) return;
    if(value.length < this.options.minimum_characters) return;

    if(this.search_xhr) this.search_xhr.abort();

    if(this.cache[value]) {
      this.$elem.trigger('livesearch:results', [this.cache[value]]);
    } else {
      this.search_xhr = $.get(this.$form.attr('action'), this.$form.serialize(), function(data) {
        _this.$elem.trigger('livesearch:results', [data]);
        _this.cache[value] = data;
      }, 'json');
    }

    this.last_search = value;
  }

});

})(jQuery);
<|endoftext|>


<commit_before>/*
 * scrollAway.js
 * https://github.com/negomi/scrollaway
 *
 * Copyright (c) 2015 Imogen Wentworth
 * Licensed under the MIT license.
 */

(function($) {
  'use strict';

  // Collection method.
  $.fn.scrollAway = function(options) {
    // Override default options with passed in options.
    options = $.extend({}, $.fn.scrollAway.defaults, options || {});

    // Limit the scroll event to firing every n milliseconds, determined by
    // the specified delay. A small delay is recommended because triggering
    // immediately can be annoying due to scroll sensitivity.
    var throttle = function(fn, delay) {
      var last, deferTimer;
      return function () {
        var context = this;
        var now = new Date().getTime(), args = arguments;
        if (last && now < last + delay) {
          clearTimeout(deferTimer);
          deferTimer = setTimeout(function () {
            last = now;
            fn.apply(context, args);
          }, delay);
        } else {
          last = now;
          fn.apply(context, args);
        }
      };
    };

    // Check type of all the values of a given object. Input must be an object
    // as the keys are used with msg to create a meaningful error message.
    var checkType = function(input, type, msg) {
      if (typeof input === 'object') {
        $.each(input, function(key, val) {
          if (typeof val !== type) {
            $.error(key + ' ' + msg + '.');
          }
        });
      }
    };

    // Check if a given value or expression is truthy.
    var isTrue = function(exp) { return !!exp; };

    // Hide or show each element on which scrollAway() is executed.
    return this.each(function() {
      var $element = $(this);
      var previousScroll = 0;
      var show, hide;

      var numberOptions = {
        duration: options.duration,
        delay: options.delay,
        topTriggerDistance: options.topTriggerDistance,
        bottomTriggerDistance: options.bottomTriggerDistance
      };

      var booleanOptions = {
        autoShowAtBottom: options.autoShowAtBottom
      };

      // Check supplied options to make sure their types make sense.
      checkType(numberOptions, 'number', 'must be a number');
      checkType(booleanOptions, 'boolean', 'must be true or false');

      // Set show/hide functions to the most appropriate jQuery function.
      switch (options.animation) {
        case 'fade':
          show = $.fn.fadeIn;
          hide = $.fn.fadeOut;
          break;
        case 'slide':
          show = $.fn.slideDown;
          hide = $.fn.slideUp;
          break;
        default:
          show = $.fn.show;
          hide = $.fn.hide;
          break;
      }

      // Define options object to pass to show/hide.
      var animationOptions = {
        easing: options.easing,
        duration: options.duration
      };

      // Determine desired element state on scroll event.
      $(window).scroll(throttle(function() {
        var currentScroll = $(this).scrollTop();
        var scrollUpEvent = currentScroll < previousScroll;
        var atTop = currentScroll <= 0 + options.topTriggerDistance;
        var atBottom;
        var showConditions = [scrollUpEvent, atTop];

        if (options.autoShowAtBottom) {
          atBottom = currentScroll + $(window).height() >=
                     $(document).height() - options.bottomTriggerDistance;
          showConditions.push(atBottom);
        }

        // Check if any of the specified show conditions are satisfied,
        // and then show/hide the element as necessary. Any errors thrown
        // will be due to an unrecognised easing value.
        try {
          if (showConditions.some(isTrue)) {
            show.call($element, animationOptions);
          } else {
            hide.call($element, animationOptions);
          }
        }
        catch(err) {
          $.error('Unknown easing value. ' +
                  'Core jQuery options are \'swing\' or \'linear\'.');
        }

        previousScroll = currentScroll;
      }, options.delay));
    });
  };

  // Set default options.
  $.fn.scrollAway.defaults = {
    animation: 'fade',
    duration: 400,
    easing: 'swing',
    delay: 350,
    topTriggerDistance: 0,
    bottomTriggerDistance: 0,
    autoShowAtBottom: true
  };

  // Throw error if called as a static method.
  $.scrollAway = function() {
    $.error('No element specified for scrollAway. ' +
            'Try: $("#your-element").scrollAway();');
  };

}(jQuery));
<commit_msg>Refactor into testable functions<commit_after>/*
 * scrollAway.js
 * https://github.com/negomi/scrollaway
 *
 * Copyright (c) 2015 Imogen Wentworth
 * Licensed under the MIT license.
 */

(function($) {
  'use strict';

  // Set element and override default options with passed in options.
  function ScrollAway(element, options) {
    this.$element = $(element);
    this.options = $.extend({}, this.defaults, options || {});
    this.init();
  }

  ScrollAway.prototype.init = function() {
    var self = this;

    this.setOptsByType(self);
    this.validateOptions();
    this.setAnimations(this.options.animation);

    this.previousScroll = 0;

    $(window).scroll(this.throttle(function() {
      self.handleScroll();
    }, this.options.delay));
  };

  // Check if any of the specified show conditions are satisfied,
  // and then show/hide the element as necessary. Any errors thrown
  // will be due to an unrecognised easing value.
  ScrollAway.prototype.handleScroll = function() {
    this.currentScroll = $(window).scrollTop();
    this.setShowConditions();

    try {
      if (this.showConditions.some(this.isTrue)) {
        this.show.call(this.$element, this.optsByType.animation);
      } else {
        this.hide.call(this.$element, this.optsByType.animation);
      }
    }
    catch(err) {
      $.error('Unknown easing value. ' +
              'Core jQuery options are \'swing\' or \'linear\'.');
    }

    this.previousScroll = this.currentScroll;
  };

  // Set the conditions on which to show the element. It will always show on
  // scroll up, or at the top of the page. Showing it at the bottom is optional
  // and determined by the autoShowAtBottom option.
  ScrollAway.prototype.setShowConditions = function() {
    var scrollUpEvent, atTop, atBottom;

    if (this.showConditions) {
      this.showConditions.length = 0;
    } else {
      this.showConditions = [];
    }

    scrollUpEvent = this.currentScroll < this.previousScroll;
    this.showConditions.push(scrollUpEvent);

    atTop = this.currentScroll <= 0 + this.options.topTriggerDistance;
    this.showConditions.push(atTop);

    if (this.options.autoShowAtBottom) {
      atBottom = this.currentScroll + $(window).height() >=
      $(document).height() - this.options.bottomTriggerDistance;
      this.showConditions.push(atBottom);
    }
  };

  // Set show/hide to the most appropriate jQuery function.
  ScrollAway.prototype.setAnimations = function(options) {
    switch (options) {
      case 'fade':
        ScrollAway.prototype.show = $.fn.fadeIn;
        ScrollAway.prototype.hide = $.fn.fadeOut;
        break;
      case 'slide':
        ScrollAway.prototype.show = $.fn.slideDown;
        ScrollAway.prototype.hide = $.fn.slideUp;
        break;
      default:
        ScrollAway.prototype.show = $.fn.show;
        ScrollAway.prototype.hide = $.fn.hide;
        break;
    }
  };

  // Store options grouped for easy access/type checking.
  ScrollAway.prototype.setOptsByType = function(self) {
    this.optsByType = {
      num: {
        duration: self.options.duration,
        delay: self.options.delay,
        topTriggerDistance: self.options.topTriggerDistance,
        bottomTriggerDistance: self.options.bottomTriggerDistance
      },
      bool: {
        autoShowAtBottom: self.options.autoShowAtBottom
      },
      animation: {
        easing: self.options.easing,
        duration: self.options.duration
      }
    };
  };

  // Check supplied options to make sure their types make sense.
  ScrollAway.prototype.validateOptions = function() {
    this.checkType(this.optsByType.num, 'number', 'must be a number');
    this.checkType(this.optsByType.bool, 'boolean', 'must be true or false');
  };

  // Check type of all the values of a given object. Input must be an object
  // as the keys are used with msg to create a meaningful error message.
  ScrollAway.prototype.checkType = function(input, type, msg) {
    if (typeof input === 'object') {
      $.each(input, function(key, val) {
        if (typeof val !== type) {
          $.error(key + ' ' + msg + '.');
        }
      });
    }
  };

  // Limit the scroll event to firing every n milliseconds, determined by
  // the specified delay. A small delay is recommended because triggering
  // immediately can be annoying due to scroll sensitivity.
  ScrollAway.prototype.throttle = function(fn, delay) {
    var last, deferTimer;
    return function () {
      var context = this;
      var now = new Date().getTime(), args = arguments;
      if (last && now < last + delay) {
        clearTimeout(deferTimer);
        deferTimer = setTimeout(function () {
          last = now;
          fn.apply(context, args);
        }, delay);
      } else {
        last = now;
        fn.apply(context, args);
      }
    };
  };

  // Check if a given value or expression is truthy.
  ScrollAway.prototype.isTrue = function(exp) {
    return !!exp;
  };

  // Collection method.
  $.fn.scrollAway = function(options) {
    // Hide or show each element to which scrollAway() is applied.
    return this.each(function() {
      $.data(this, 'scrollAway', new ScrollAway(this, options));
    });
  };

  // Throw error if called as a static method.
  $.scrollAway = function() {
    $.error('No element specified for scrollAway. ' +
            'Try: $("#your-element").scrollAway();');
  };

  // Set default options.
  ScrollAway.prototype.defaults = {
    animation: 'fade',
    duration: 400,
    easing: 'swing',
    delay: 350,
    topTriggerDistance: 0,
    bottomTriggerDistance: 0,
    autoShowAtBottom: true
  };

}(jQuery));
<|endoftext|>