<commit_before>/* eslint-disable indent, vars-on-top, no-warning-comments, max-len */

/* global $ */

function initBlockWidget(id) {
    /*
    Initialises the top-level element of a BlockWidget
    (i.e. the form widget for a StreamField).
    Receives the ID of a DOM element with the attributes:
        data-block: JSON-encoded block definition to be passed to telepath.unpack
            to obtain a Javascript representation of the block
            (i.e. an instance of one of the Block classes below)
        data-value: JSON-encoded value for this block
    */

    var body = document.getElementById(id);

    // unpack the block definition and value
    var blockDefData = JSON.parse(body.dataset.block);
    var blockDef = window.telepath.unpack(blockDefData);
    var blockValue = JSON.parse(body.dataset.value);

    // replace the 'body' element with the unpopulated HTML structure for the block
    var block = blockDef.render(body, id);
    // populate the block HTML with the value
    block.setState(blockValue);
}
window.initBlockWidget = initBlockWidget;

class FieldBlock {
    constructor(name, widget, meta) {
        this.name = name;
        this.widget = window.telepath.unpack(widget);
        this.meta = meta;
    }

    render(placeholder, prefix) {
        var html = $(`

        `);
        var dom = $(html);
        $(placeholder).replaceWith(dom);
        var widgetElement = dom.find('[data-streamfield-widget]').get(0);
        var boundWidget = this.widget.render(widgetElement, prefix, prefix);
        return {
            type: this.name,
            setState(state) {
                boundWidget.setState(state);
            },
            getState() {
                boundWidget.getState();
            },
            getValue() {
                boundWidget.getValue();
            },
        };
    }
}
window.telepath.register('wagtail.blocks.FieldBlock', FieldBlock);

class StructBlock {
    constructor(name, childBlocks, meta) {
        this.name = name;
        this.childBlocks = childBlocks.map((child) => window.telepath.unpack(child));
        this.meta = meta;
    }

    render(placeholder, prefix) {
        var html = $(`

        `);
        var dom = $(html);
        $(placeholder).replaceWith(dom);

        var boundBlocks = {};
        this.childBlocks.forEach(childBlock => {
            var childHtml = $(`

                    ${childBlock.meta.label}

            `);
            var childDom = $(childHtml);
            dom.append(childDom);
            var childBlockElement = childDom.find('[data-streamfield-block]').get(0);
            var boundBlock = childBlock.render(childBlockElement, prefix + '-' + childBlock.name);

            boundBlocks[childBlock.name] = boundBlock;
        });

        return {
            type: this.name,
            setState(state) {
                // eslint-disable-next-line guard-for-in, no-restricted-syntax, no-native-reassign
                for (name in state) {
                    boundBlocks[name].setState(state[name]);
                }
            },
            getState() {
                var state = {};
                // eslint-disable-next-line guard-for-in, no-restricted-syntax, no-native-reassign
                for (name in boundBlocks) {
                    state[name] = boundBlocks[name].getState();
                }
                return state;
            },
            getValue() {
                var value = {};
                // eslint-disable-next-line guard-for-in, no-restricted-syntax, no-native-reassign
                for (name in boundBlocks) {
                    value[name] = boundBlocks[name].getValue();
                }
                return value;
            },
        };
    }
}
window.telepath.register('wagtail.blocks.StructBlock', StructBlock);

class ListBlock {
    constructor(name, childBlock, meta) {
        this.name = name;
        this.childBlock = window.telepath.unpack(childBlock);
        this.meta = meta;
    }

    render(placeholder, prefix) {
        var html = $(`

                <input type="hidden" name="${prefix}-count" data-streamfield-list-count value="0">

                <button type="button" title="Add" data-streamfield-list-add class="c-sf-add-button c-sf-add-button--visible">+</button>

        `);
        var dom = $(html);
        $(placeholder).replaceWith(dom);

        var boundBlocks = [];
        var countInput = dom.find('[data-streamfield-list-count]');
        var listContainer = dom.find('[data-streamfield-list-container]');

        // eslint-disable-next-line @typescript-eslint/no-this-alias
        var self = this;

        return {
            type: this.name,
            setState(values) {
                countInput.val(values.length);
                boundBlocks = [];
                listContainer.empty();
                values.forEach((val, index) => {
                    var childPrefix = prefix + '-' + index;
                    var childHtml = $(`

                            <input type="hidden" id="${childPrefix}-deleted" name="${childPrefix}-deleted" value="">
                            <input type="hidden" id="${childPrefix}-order" name="${childPrefix}-order" value="${index}">

                                                <button type="button" id="${childPrefix}-moveup" class="c-sf-block__actions__single" title="{% trans 'Move up' %}">

                                            </button>
                                            <button type="button" id="${childPrefix}-movedown" class="c-sf-block__actions__single" title="{% trans 'Move down' %}">

                                            </button>
                                            <button type="button" id="${childPrefix}-delete" class="c-sf-block__actions__single" title="{% trans 'Delete' %}">

                                            </button>

                    `);
                    var childDom = $(childHtml);
                    listContainer.append(childDom);
                    var childBlockElement = childDom.find('[data-streamfield-block]').get(0);
                    var boundBlock = self.childBlock.render(childBlockElement, childPrefix + '-value');
                    boundBlock.setState(val);
                    boundBlocks.push(boundBlock);
                });
            },
            getState() {
                return boundBlocks.map((boundBlock) => boundBlock.getState());
            },
            getValue() {
                return boundBlocks.map((boundBlock) => boundBlock.getValue());
            },
        };
    }
}
window.telepath.register('wagtail.blocks.ListBlock', ListBlock);

class StreamBlock {
    constructor(name, childBlocks, meta) {
        this.name = name;
        this.childBlocks = childBlocks.map((child) => window.telepath.unpack(child));
        this.childBlocksByName = {};
        for (var i = 0; i < this.childBlocks.length; i++) {
            var block = this.childBlocks[i];
            this.childBlocksByName[block.name] = block;
        }
        this.meta = meta;
    }

    render(placeholder, prefix) {
        var html = $(`

                <input type="hidden" name="${prefix}-count" data-streamfield-stream-count value="0">

        `);
        var dom = $(html);
        $(placeholder).replaceWith(dom);
        var boundBlocks = [];
        var countInput = dom.find('[data-streamfield-stream-count]');
        var streamContainer = dom.find('[data-streamfield-stream-container]');

        // eslint-disable-next-line @typescript-eslint/no-this-alias
        var self = this;

        return {
            type: this.name,
            setState(values) {
                countInput.val(values.length);
                streamContainer.empty();
                boundBlocks = [];
                for (var index = 0; index < values.length; index++) {
                    var blockData = values[index];
                    var blockType = blockData.type;
                    var block = self.childBlocksByName[blockType];

                    var childPrefix = prefix + '-' + index;
                    var childHtml = `

                            <input type="hidden" name="${childPrefix}-deleted" value="">
                            <input type="hidden" name="${childPrefix}-order" value="${index}">
                            <input type="hidden" name="${childPrefix}-type" value="${blockType}">
                            <input type="hidden" name="${childPrefix}-id" value="${blockData.id || ''}">

                                                ${block.meta.label}
                                                <button type="button" class="c-sf-block__actions__single" title="{% trans 'Move up' %}">

                                                </button>
                                                <button type="button" class="c-sf-block__actions__single" title="{% trans 'Move down' %}">

                                                </button>
                                                <button type="button" class="c-sf-block__actions__single" title="{% trans 'Delete' %}">

                                                </button>

                    `;
                    var childDom = $(childHtml);
                    streamContainer.append(childDom);
                    var childBlockElement = childDom.find('[data-streamfield-block]').get(0);
                    var boundBlock = block.render(childBlockElement, childPrefix + '-value');
                    boundBlock.setState(blockData.value);
                    boundBlocks.push(boundBlock);
                }
            },
            getState() {
                return boundBlocks.map((boundBlock) => ({
                        type: boundBlock.type,
                        val: boundBlock.getState(),
                        id: null, /* TODO: add this */
                    }));
            },
            getValue() {
                return boundBlocks.map((boundBlock) => ({
                        type: boundBlock.type,
                        val: boundBlock.getValue(),
                        id: null, /* TODO: add this */
                    }));
            },
        };
    }
}
window.telepath.register('wagtail.blocks.StreamBlock', StreamBlock);
<commit_msg>lint telepath/blocks.js<commit_after>/* eslint-disable no-warning-comments */

/* global $ */

function initBlockWidget(id) {
  /*
  Initialises the top-level element of a BlockWidget
  (i.e. the form widget for a StreamField).
  Receives the ID of a DOM element with the attributes:
    data-block: JSON-encoded block definition to be passed to telepath.unpack
      to obtain a Javascript representation of the block
      (i.e. an instance of one of the Block classes below)
    data-value: JSON-encoded value for this block
  */

  const body = document.getElementById(id);

  // unpack the block definition and value
  const blockDefData = JSON.parse(body.dataset.block);
  const blockDef = window.telepath.unpack(blockDefData);
  const blockValue = JSON.parse(body.dataset.value);

  // replace the 'body' element with the unpopulated HTML structure for the block
  const block = blockDef.render(body, id);
  // populate the block HTML with the value
  block.setState(blockValue);
}
window.initBlockWidget = initBlockWidget;

class FieldBlock {
  constructor(name, widget, meta) {
    this.name = name;
    this.widget = window.telepath.unpack(widget);
    this.meta = meta;
  }

  render(placeholder, prefix) {
    const html = $(`

    `);
    const dom = $(html);
    $(placeholder).replaceWith(dom);
    const widgetElement = dom.find('[data-streamfield-widget]').get(0);
    const boundWidget = this.widget.render(widgetElement, prefix, prefix);
    return {
      type: this.name,
      setState(state) {
        boundWidget.setState(state);
      },
      getState() {
        boundWidget.getState();
      },
      getValue() {
        boundWidget.getValue();
      },
    };
  }
}
window.telepath.register('wagtail.blocks.FieldBlock', FieldBlock);

class StructBlock {
  constructor(name, childBlocks, meta) {
    this.name = name;
    this.childBlocks = childBlocks.map((child) => window.telepath.unpack(child));
    this.meta = meta;
  }

  render(placeholder, prefix) {
    const html = $(`

    `);
    const dom = $(html);
    $(placeholder).replaceWith(dom);

    const boundBlocks = {};
    this.childBlocks.forEach(childBlock => {
      const childHtml = $(`

          ${childBlock.meta.label}

      `);
      const childDom = $(childHtml);
      dom.append(childDom);
      const childBlockElement = childDom.find('[data-streamfield-block]').get(0);
      const boundBlock = childBlock.render(childBlockElement, prefix + '-' + childBlock.name);

      boundBlocks[childBlock.name] = boundBlock;
    });

    return {
      type: this.name,
      setState(state) {
        // eslint-disable-next-line guard-for-in, no-restricted-syntax
        for (const name in state) {
          boundBlocks[name].setState(state[name]);
        }
      },
      getState() {
        const state = {};
        // eslint-disable-next-line guard-for-in, no-restricted-syntax
        for (const name in boundBlocks) {
          state[name] = boundBlocks[name].getState();
        }
        return state;
      },
      getValue() {
        const value = {};
        // eslint-disable-next-line guard-for-in, no-restricted-syntax
        for (const name in boundBlocks) {
          value[name] = boundBlocks[name].getValue();
        }
        return value;
      },
    };
  }
}
window.telepath.register('wagtail.blocks.StructBlock', StructBlock);

class ListBlock {
  constructor(name, childBlock, meta) {
    this.name = name;
    this.childBlock = window.telepath.unpack(childBlock);
    this.meta = meta;
  }

  render(placeholder, prefix) {
    const html = $(`

        <input type="hidden" name="${prefix}-count" data-streamfield-list-count value="0">

        <button type="button" title="Add" data-streamfield-list-add class="c-sf-add-button c-sf-add-button--visible">
          +
        </button>

    `);
    const dom = $(html);
    $(placeholder).replaceWith(dom);

    let boundBlocks = [];
    const countInput = dom.find('[data-streamfield-list-count]');
    const listContainer = dom.find('[data-streamfield-list-container]');

    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this;

    return {
      type: this.name,
      setState(values) {
        countInput.val(values.length);
        boundBlocks = [];
        listContainer.empty();
        values.forEach((val, index) => {
          const childPrefix = prefix + '-' + index;
          const childHtml = $(`

              <input type="hidden" id="${childPrefix}-deleted" name="${childPrefix}-deleted" value="">
              <input type="hidden" id="${childPrefix}-order" name="${childPrefix}-order" value="${index}">

                        <button type="button" id="${childPrefix}-moveup" class="c-sf-block__actions__single"
                            title="{% trans 'Move up' %}">

                        </button>
                        <button type="button" id="${childPrefix}-movedown" class="c-sf-block__actions__single"
                            title="{% trans 'Move down' %}">

                        </button>
                        <button type="button" id="${childPrefix}-delete" class="c-sf-block__actions__single"
                            title="{% trans 'Delete' %}">

                        </button>

          `);
          const childDom = $(childHtml);
          listContainer.append(childDom);
          const childBlockElement = childDom.find('[data-streamfield-block]').get(0);
          const boundBlock = self.childBlock.render(childBlockElement, childPrefix + '-value');
          boundBlock.setState(val);
          boundBlocks.push(boundBlock);
        });
      },
      getState() {
        return boundBlocks.map((boundBlock) => boundBlock.getState());
      },
      getValue() {
        return boundBlocks.map((boundBlock) => boundBlock.getValue());
      },
    };
  }
}
window.telepath.register('wagtail.blocks.ListBlock', ListBlock);

class StreamBlock {
  constructor(name, childBlocks, meta) {
    this.name = name;
    this.childBlocks = childBlocks.map((child) => window.telepath.unpack(child));
    this.childBlocksByName = {};
    for (let i = 0; i < this.childBlocks.length; i++) {
      const block = this.childBlocks[i];
      this.childBlocksByName[block.name] = block;
    }
    this.meta = meta;
  }

  render(placeholder, prefix) {
    const html = $(`

        <input type="hidden" name="${prefix}-count" data-streamfield-stream-count value="0">

    `);
    const dom = $(html);
    $(placeholder).replaceWith(dom);
    let boundBlocks = [];
    const countInput = dom.find('[data-streamfield-stream-count]');
    const streamContainer = dom.find('[data-streamfield-stream-container]');

    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this;

    return {
      type: this.name,
      setState(values) {
        countInput.val(values.length);
        streamContainer.empty();
        boundBlocks = [];
        for (let index = 0; index < values.length; index++) {
          const blockData = values[index];
          const blockType = blockData.type;
          const block = self.childBlocksByName[blockType];

          const childPrefix = prefix + '-' + index;
          const childHtml = `

              <input type="hidden" name="${childPrefix}-deleted" value="">
              <input type="hidden" name="${childPrefix}-order" value="${index}">
              <input type="hidden" name="${childPrefix}-type" value="${blockType}">
              <input type="hidden" name="${childPrefix}-id" value="${blockData.id || ''}">

                        ${block.meta.label}
                        <button type="button" class="c-sf-block__actions__single" title="{% trans 'Move up' %}">

                        </button>
                        <button type="button" class="c-sf-block__actions__single" title="{% trans 'Move down' %}">

                        </button>
                        <button type="button" class="c-sf-block__actions__single" title="{% trans 'Delete' %}">

                        </button>

          `;
          const childDom = $(childHtml);
          streamContainer.append(childDom);
          const childBlockElement = childDom.find('[data-streamfield-block]').get(0);
          const boundBlock = block.render(childBlockElement, childPrefix + '-value');
          boundBlock.setState(blockData.value);
          boundBlocks.push(boundBlock);
        }
      },
      getState() {
        return boundBlocks.map((boundBlock) => ({
          type: boundBlock.type,
          val: boundBlock.getState(),
          id: null, /* TODO: add this */
        }));
      },
      getValue() {
        return boundBlocks.map((boundBlock) => ({
          type: boundBlock.type,
          val: boundBlock.getValue(),
          id: null, /* TODO: add this */
        }));
      },
    };
  }
}
window.telepath.register('wagtail.blocks.StreamBlock', StreamBlock);
<|endoftext|>


<commit_before>var url = require('url');
var rules = require('../rules');
var util = require('../../util');

module.exports = function(req, res, next) {
	var config = this.config;
	var fullUrl = req.fullUrl = util.getFullUrl(req);
	req.rules = rules.resolveRules(fullUrl);
	var rule = req.rules.rule;
	var proxy = req.rules.proxy;
	req._fromProxy = req.headers[util.PROXY_ID] == config.name;
	var matchedUrl = proxy && !req._fromProxy ? util.rule.getMatcher(proxy) :
		(util.rule.getUrl(rule) || fullUrl);
	var options = url.parse(matchedUrl);
	if (options.protocol == 'proxy:') {
		var port = options.port || config.port;
		options = url.parse(fullUrl);
		options.port = port;
		options.proxy = proxy;
		options.proxyUrl = matchedUrl = options.protocol + util.removeProtocol(matchedUrl);
	}

	options.rule = rule;
	req.options = options;
	req.matchedUrl = matchedUrl;
	next();
};<commit_msg>remove proxy id<commit_after>var url = require('url');
var rules = require('../rules');
var util = require('../../util');

module.exports = function(req, res, next) {
	var config = this.config;
	var fullUrl = req.fullUrl = util.getFullUrl(req);
	req.rules = rules.resolveRules(fullUrl);
	var rule = req.rules.rule;
	var proxy = req.rules.proxy;
	var matchedUrl = proxy ? util.rule.getMatcher(proxy) :
		(util.rule.getUrl(rule) || fullUrl);
	var options = url.parse(matchedUrl);
	if (options.protocol == 'proxy:') {
		var port = options.port || config.port;
		options = url.parse(fullUrl);
		options.port = port;
		options.proxy = proxy;
		options.proxyUrl = matchedUrl = options.protocol + util.removeProtocol(matchedUrl);
	}

	options.rule = rule;
	req.options = options;
	req.matchedUrl = matchedUrl;
	next();
};<|endoftext|>


<commit_before>  // TO DO:  confirm the segment starts with MSH.
  //         get the field delimiter from the header
  //         split the header, get the response field
  //         generate and return ack, if ACK is empty then the calling function should not respond with an ACK.
  //         user parameter to always send ACK - defaults to false.
  //        error handler if the port is already bound
  //        handle messages sent via buffer

  // Rob Holme
//
// Implements functions to receive a HL7 v2.x message via TCP using MLLP framing.
// Message received are displayed as new documents in the active editor.

// required modules
const vscode = require('vscode');
var net = require("net");

// MLLP framing codes
const VT = String.fromCharCode(0x0b);
const FS = String.fromCharCode(0x1c);
const CR = String.fromCharCode(0x0d);

var listenerStarted = false;
var server;

// generate an ack message from the message header received
// @param {string} MessageHeader - the MSH segment of the message received
function GenerateAckFromHeader(MessageHeader) {

  // regex to confirm the header starts with ^MSH
  // get the delimiter character from MSH-1
  // var MshFields = MessageHeader.split("|");

}

// Receive a HL7 v2.x message from a remote host using MLLP framing.
// @param {int} Port - the port number to listen for messages on
function StartListener(Port) {

  // return if the listener is already running
  if (listenerStarted === true) {
    console.log("The listener is already running");
    vscode.window.showWarningMessage("The HL7 TCP listener is already running. Stop the current listener before starting another.");
    return;
  }

  console.log("listening on port " + Port);

  listenerStarted = true;
  server = net.createServer(function (stream) {
    stream.setEncoding("utf8");
    stream.addListener("connect", function () {
      console.log("Client connected");
    });
    stream.addListener("data", function (receivedMessage) {
     if (receivedMessage && !receivedMessage.match(/^\s*$/)) {
 //       if (receivedMessage && !receivedMessage.match(FS+CR)) {
        //send a fake accept ack to my perl llp sender over the socket
        stream.write("...AA");
        // write the message received to a new document
        if (receivedMessage.length > 2) {

          // strip the framing characters from the received message
          receivedMessage = receivedMessage.substring(1, (receivedMessage.length - 3))
          vscode.workspace.openTextDocument({ content: receivedMessage, language: "hl7" }).then((newDocument) => {
            vscode.window.showTextDocument(newDocument, 1, false).then(e => {
            });
          }, (error) => {
            console.error(error);
          });
        }
      }
    });
    stream.addListener("end", function () {
      console.log("Client disconnected");
      stream.end();
    });
  });
  server.listen(Port);

}

// Stops all listeners
function StopListener() {
  if (listenerStarted === true) {
    server.close(function () {
      listenerStarted = false;
      console.log("Listener stopped");
      vscode.window.showInformationMessage("The HL7 TCP listener has stopped.");

    });
  }
  else {
    console.log("Listener is not running");
        vscode.window.showInformationMessage("The HL7 TCP listener is not started, nothing to stop.");

  }
}

exports.StartListener = StartListener;
exports.StopListener = StopListener;

<commit_msg>updated MLLP frame detection<commit_after>// TO DO:  confirm the segment starts with MSH.
//         get the field delimiter from the header
//         split the header, get the response field
//         generate and return ack, if ACK is empty then the calling function should not respond with an ACK.
//         user parameter to always send ACK - defaults to false.
//        error handler if the port is already bound
//        handle messages sent via buffer

// Rob Holme
//
// Implements functions to receive a HL7 v2.x message via TCP using MLLP framing.
// Message received are displayed as new documents in the active editor.

// required modules
const vscode = require('vscode');
var net = require("net");

// MLLP framing codes
const VT = String.fromCharCode(0x0b);
const FS = String.fromCharCode(0x1c);
const CR = String.fromCharCode(0x0d);

var listenerStarted = false;
var server;

// generate an ack message from the message header received
// @param {string} MessageHeader - the MSH segment of the message received
function GenerateAckFromHeader(MessageHeader) {

  // regex to confirm the header starts with ^MSH
  // get the delimiter character from MSH-1
  // var MshFields = MessageHeader.split("|");

}

// Receive a HL7 v2.x message from a remote host using MLLP framing.
// @param {int} Port - the port number to listen for messages on
function StartListener(Port) {
  var receivedData;
  // return if the listener is already running
  if (listenerStarted === true) {
    console.log("The listener is already running");
    vscode.window.showWarningMessage("The HL7 TCP listener is already running. Stop the current listener before starting another.");
    return;
  }

  console.log("listening on port " + Port);

  listenerStarted = true;
  server = net.createServer(function (stream) {
    stream.setEncoding("utf8");
    stream.addListener("connect", function () {
      console.log("Client connected");
    });
    // receive the message(s) from the remote host, split the message from the stream using MLLP framing
    stream.addListener("data", function (receivedMessage) {
      // search for the start of the MLLP frame (VT character)
      receivedData += receivedMessage;
      var start = receivedData.indexOf(VT);
      if (start >= 0) {
        // search for the end of the MLLP frame (FS char followed by a CR char)
        var end = receivedData.indexOf(FS + CR);
        if (end > start) {
          // TO DO: send valid ACK message
          stream.write("...AA");
          receivedData = receivedData.substring(1, (receivedData.length - 3))
          vscode.workspace.openTextDocument({ content: receivedData, language: "hl7" }).then((newDocument) => {
            vscode.window.showTextDocument(newDocument, 1, false).then(e => {
              receivedData = "";
            });
          }, (error) => {
            console.error(error);
          });
        }
      }
    });

    // client has disconnected from TCP socket
    stream.addListener("end", function () {
      console.log("Client disconnected");
      stream.end();
    });
  });

  // listen on all interfaces
  server.listen(Port);
}

// Stops all listeners
function StopListener() {
  if (listenerStarted === true) {
    server.close(function () {
      listenerStarted = false;
      console.log("Listener stopped");
      vscode.window.showInformationMessage("The HL7 TCP listener has stopped.");

    });
  }
  else {
    console.log("Listener is not running");
    vscode.window.showInformationMessage("The HL7 TCP listener is not started, nothing to stop.");

  }
}

exports.StartListener = StartListener;
exports.StopListener = StopListener;

<|endoftext|>


<commit_before>var generateObjectId   = require("./object_id")
  , EmbeddedCollection = require("./embedded_collection")
  , _                  = require("underscore")
  , idCounter = 0

require("./utils")

var InstanceMethods = {

  /**
   * The constructor for a new instance of LiveDocument
   *
   * The only valid opts param at the moment is validate false, if you don't
   * want the document to be validated it would be nice if we could pass in
   * saved, persisted and loaded as well
   *
   * This will create a new user which has not been persisted to the
   * database, saved, or loaded, and it is automatically assigned an id
   *
   * It is also used internally to create new documents that are received
   * from the server, except the loaded, saved, and persisted flags are
   * manually set, and it's not validated
   *
   * Examples:
   *     user = new User({ name: "Zach" })
   *
   **/

  constructor: function(document, opts) {
    var that = this
    var opts = opts || {}

    this.document = document || {}
    this.id = idCounter++

    this.deleted = false
    this.saved = false
    this.saving = false
    this.persisted = false
    this.loaded = false
    this.alreadyChanging = false

    this.unsavedChanges = []

    this.modelName = this.constructor.modelName

    if(!this.document._id && !opts.noId) {
      this.document._id = generateObjectId()
    }

    this.collectionName = this.constructor.collectionName()

    this.setupAssociations()

    if(this.get("_id")) {
      this.constructor.listenForChanges(this.get("_id"), this)
    } else {
      this.once("change:_id", function() {
        that.constructor.listenForChanges(this.get("_id"), that)
      })
    }
  }

, fetch: function() {
    var that = this
    this.constructor.sendReadOneMessage(this.document, function(document) {
      if(document) {
        that.set(document, { validate: false, initialize: true })
        that.loaded = true
        that.saved = true
        that.persisted = true
        that.emit("load", that)
      } else {
        that.emit("notExist")
      }
    })
  }

, setupManyAssociations: function() {
    var that = this
    this.manyAssociations = {}

    _(this.constructor.hasMany).each(function(obj, collectionName) {
      var collection
        , Model = obj.model
        , opts = obj.opts
      that.manyAssociations[collectionName] = function(callback) {
        var docs
          , query
        if(collection) {
          return collection
        }
        if(Model.embedded) {
          docs = that.document[collectionName] || []
          delete that.document[collectionName]
          collection = new EmbeddedCollection(docs, that, Model)
        } else {
          query = {}
          query[opts.foreignKey || _.singularize(that.collectionName) + "Id"] = that.get("_id")
          collection = Model.read(query)
        }
        callback = callback || function() {}
        collection.load(function() {
          callback(collection)
        })
        return collection
      }
    })
  }

, setupOneAssociations: function() {
    var that = this
    this.oneAssociations = {}
    _(this.constructor.hasOne).each(function(obj, collectionName) {
      var Model = obj.model
        , opts = obj.opts || {}
        , foreignKey = opts.foreignKey || _.singularize(that.collectionName) + "Id"

      that.oneAssociations[collectionName] = function(cb) {
        var model = Model.findByKey(foreignKey, that.get("_id"))
        //we couldn't find the model
        model.once("notExist", function(err) {
          model.set("_id", generateObjectId())
          model.save(function() {
            model.emit("load")
          })
        })
        model.load(function() {
          cb(model)
        })
        return model
      }
    })
  }

, setupBelongsToAssociations: function() {
    var that = this
    this.belongAssociations = {}
    _(this.constructor.belongingTo).each(function(opts, collectionName) {
      that.belongAssociations[collectionName] = function(cb) {
        var ModelMap = opts.models
          , Model
          , foreignType
          , foreignKey = that.get(collectionName + "Id")

        if(opts.polymorphic) {
          foreignType = that.get(collectionName + "Type")
          Model = ModelMap[foreignType]
        } else {
          Model = opts.model
        }

        if(!foreignKey) {
          cb()
          return
        }
        var model = Model.findOne(foreignKey)
        model.once("notExist", function(err) {
          model.save(function() {
            model.emit("load")
          })
        })
        model.load(function() {
          cb(model)
        })
        return model
      }

    })

  }

, setupAssociations: function() {
    this.setupManyAssociations()
    this.setupOneAssociations()
    this.setupBelongsToAssociations()
  }

, stopListening: function() {
    this.constructor.stopListening(this.get("_id"), this)
  }

 /*
  * listen for changes to documents with this id
  * since it's coming from the server we'll assume
  * it validates
  */
, handleChange: function(changes) {
    this.set(changes, {validate: false, initialize:true})
  }

 /*
  * listens for this document having been deleted, if it has
  * emit the delete event and set the deleted attribute to false
  */
, handleRemove: function() {
    this.deleted = true
    this.emit("delete", this)
  }

, sendUpdate: function(query, document, callback) {
    this.constructor.sendUpdateMessage(query, document, callback)
  }

, sendCreate: function(document, callback) {
    this.constructor.sendCreateMessage(document, callback)
  }

, performSave: function(cb) {
    var that = this
      , doc
      , time = (new Date()).getTime()

    this.set("updatedAt", time)

    this.saving = true
    this.emit("saving", that)

    //if the document has not been already persisted to the database before
    //then its a create event
    //
    if(!this.persisted) {
      this.set("createdAt", time)
      this.unsavedChanges = []
      this.sendCreate(this.document, function() {
        that.saving = false
        that.saved = true
        that.persisted = true
        that.emit("saved", that)
        cb(that)
      })
    } else {
      var doc = {}
        , changed = _.clone(this.unsavedChanges)

      _(changed).each(function(key) {
        if(key === "_id") {
          return
        }

        that.emit("saving:" + key, that)
        doc[key] = that.document[key]
      })

      this.unsavedChanges = []
      this.sendUpdate({ _id: that.get("_id") }, doc, function(doc) {
        that.lastPersisted = _.clone(that.document)
        that.saved = true
        that.saving = false
        cb(that)
        _(changed).each(function(key) {
          if(key === "_id") {
            return
          }
          that.emit("saved:" + key, that)
        })
        that.emit("saved", that)
      })
    }
  }

 /**
  * Save will create the document on the server if it has not yet been
  * persisted to the database, otherwise it will update the version in the
  * database, calls the callback with itself, returns itself for chaining
  */

, save: function(cb) {
    cb = cb || function() {}
    var that = this
      , doc
      , callNext
      , beforeSaves = []
      , beforeSave

    if(!this.document._id) {
      throw new Error("document doesn't have an _id, maybe it hasn't loaded yet?")
    }

    if(this.constructor.isServer) {
      //it's a create save
      if(!this.persisted) {
        beforeSaves.push.apply(beforeSaves, that.constructor.serverBeforeCreateFunctions || [])
      }
      beforeSaves.push.apply(beforeSaves, that.constructor.serverBeforeSaveFunctions || [])
    } else {
      if(!this.persisted) {
        beforeSaves.push.apply(beforeSaves, that.constructor.beforeCreateFunctions || [])
      }
      beforeSaves.push.apply(beforeSaves, that.constructor.beforeSaveFunctions || [])
    }

    beforeSaves.unshift(function(inst, cb) {
      that.validate(function(inst, errors) {
        //dont pass the errors to the callback, because
        //validate emits it's own invalid event
        if(!errors) {
          cb()
        }
      })
    })

    // Run this after all the beforeSave functions have been called in order
    // to actually save the document, unless any of them have errors
    beforeSaves.push(function() {
      that.performSave(cb)
    })

    _.asyncEach(beforeSaves, function(beforeSave, next) {
      _(beforeSave).bind(that)(that, function(err) {
        if(!err) {
          next()
        } else {
          process.nextTick(function() {
            that.emit("error", err)
          })
        }
      })
    })
    return this
  }

 /**
  * Gets the value of field, takes in an optional function as second
  * parameter, which will get called once the field is available, and every
  * time the field is changed. Great for binding
  *
  * If the callback has arity one, it passes the value, otherwise it passes
  * (key, value)
  **/

, get: function(field) {
    return this.document[field]
  }

, assoc: function(assoc, callback) {
    var callback = callback || function() {}
      , model
    if(this.oneAssociations[assoc]) {
      return this.oneAssociations[assoc](callback)
    } else if(this.belongAssociations[assoc]) {
      return this.belongAssociations[assoc](callback)
    } else if (this.manyAssociations[assoc]) {
      return this.manyAssociations[assoc](callback)
    } else {
      throw new Error("No such association " + assoc)
    }
  }

 /**
  * takes in a hash of keys and values to set, or a string as the first arg
  * and a value(anything serializable) as the second.
  * fires a change event, which happens after all of the changes have been made
  *
  * If you call set from inside of a change event, it won't call another change
  * event, phew!
  **/

, set: function(field, value, opts) {
    opts = opts || {}

    if(typeof field === "undefined") {
      throw new Error("Must at least provide a field to set")
    }

    if(typeof field === "object" && typeof value === "object") {
      opts = value
    }

    if(field === "_id" && typeof this.document._id !== "undefined" && this.document._id !== value) {
      throw new Error("Can't change _id after its been set")
    }

    var that = this
      , alreadyChanging = this.alreadyChanging
      , oldValue

    this.alreadyChanging = true
    // we're initiating the changes, so we'll store the old values
    if(alreadyChanging == false) {
      this.previousDocument = _.clone(this.document)
    }

    if(typeof field == "object") {
      _.each(field, function(v, k) {
        that.set(k, v, opts)
      })
    } else if(that.manyAssociations[field]) {
      _.each(value, function(doc) {
        that.manyAssociations[field]().add(doc)
      })
    } else {
      oldValue = this.document[field]
      if(this.document[field] != value) {
        this.document[field] = value
        this.emit("change:" + field, value, oldValue, this)
      }
      if(!opts.initialize) {
        this.unsavedChanges = _.union(this.unsavedChanges, [field])
      }
    }

    // if THIS instance of set started the changes, it should emit the change
    // event and then end the changingness
    if(alreadyChanging == false) {
      this.triggerChange(opts)
    }
    return this
  }

, triggerChange: function(opts) {
    var that = this
      , fields = _.union(_.keys(this.document), _.keys(this.previousDocument))
      , changedFields = _.filter(fields, function(key) {
          return that.document[key] != that.previousDocument[key]
        })

    // if something actually changed
    if(changedFields.length > 0) {
      this.saved = false
      this.emit("change", this, changedFields)
      _(changedFields).each(function(field) {
        if(opts.validate !== false) {
          that.validateField(field)
        }
      })
    }
    this.previousDocument = _.clone(this.document)
    this.alreadyChanging = false
  }

, remove: function(callback) {
    callback = callback || function() {}
    var that = this
    this.constructor.sendDeleteMessage({ _id: this.get("_id") }, function(document) {
      callback(that)
    })
  }

, validateField: function(field, callback, opts) {
    opts = opts || {}
    callback = callback || function() {}

    var that = this
    var validations = (this.constructor.validations || {})[field]
    var valid = []
    _.asyncEach(validations, function(value, rule, next) {
      if(rule === "min" && that.get(field) && that.get(field).length < value) {
        valid.push("short")
        next()
      } else if(rule === "isInt" && that.get(field) && (parseFloat(that.get(field)) != parseInt(that.get(field)) || isNaN(that.get(field)))) {
        valid.push("isInt")
        next()
      } else if(rule === "max" && that.get(field) && that.get(field).length >= value) {
        valid.push("long")
        next()
      } else if(rule === "required" && !that.get(field)) {
        valid.push("required")
        next()
      } else if(rule === "unique") {
        that.isUnique(field, that.get(field), function(unique) {
          if(!unique) {
            valid.push("unique")
          }
          next()
        })
      } else {
        next()
      }
    }, function() {
      if(valid.length > 0) {
        if(!opts.silent) {
          that.emit("invalid:" + field, that, valid)
        }
        callback(that, valid)
      } else {
        // emit an event when a field is valid
        // useful for removing error messages
        if(!opts.silent) {
          that.emit("valid:" + field, that)
        }
        callback(that)
      }
    })
  }

, validate: function(cb, opts) {
    cb = cb || function() {}

    opts = opts || {}
    var that = this
    _.asyncMap(this.constructor.validations, function(validations, field, next) {
      that.validateField(field, function(instance, errors) {
        if(errors) {
          next(field, errors)
        } else {
          next()
        }
      }, opts)
    }, function(valid) {
      if(_.keys(valid).length != 0) {
        cb(that, valid)
        if(!opts.silent) {
          process.nextTick(function() {
            that.emit("invalid", that, valid)
          })
        }
      } else {
        cb(that)
      }
    })
  }

, isUnique: function(property, value, done) {
    var obj = {_id: {$ne : this.get("_id")}}
    value = value || ""
    obj[property] = value
    this.constructor.find(obj, function(things) {
      done(things.length === 0)
    })
  }
  /**
   * Called as soon as the document is loaded, if the document is already
   * loaded, call it right away
   */
, load: function(callback) {
    if(this.loaded) {
      process.nextTick(callback)
    } else {
      this.once("load", callback)
    }
  }

, keys: function() {
    var keys = _.clone(this.constructor.keys)
    keys.push("_id")
    keys.push("createdAt")
    keys.push("updatedAt")
    return keys
  }

, oneAssocs: function() {
    var assocs = _.union(
      _.keys(this.constructor.hasOne || {})
    , _.keys(this.constructor.belongingTo || {})
    , _.keys(this.constructor.polymorphicBelongingTo || {})
    )
    return assocs
  }
, manyAssocs: function() {
    return _.keys(this.constructor.hasMany || {})
  }
, assocs: function() {
   return this.oneAssocs().concat(this.manyAssocs)
  }
}
module.exports = InstanceMethods
<commit_msg>assocs can be chained, FOREVER<commit_after>var generateObjectId   = require("./object_id")
  , EmbeddedCollection = require("./embedded_collection")
  , _                  = require("underscore")
  , idCounter = 0

require("./utils")

var InstanceMethods = {

  /**
   * The constructor for a new instance of LiveDocument
   *
   * The only valid opts param at the moment is validate false, if you don't
   * want the document to be validated it would be nice if we could pass in
   * saved, persisted and loaded as well
   *
   * This will create a new user which has not been persisted to the
   * database, saved, or loaded, and it is automatically assigned an id
   *
   * It is also used internally to create new documents that are received
   * from the server, except the loaded, saved, and persisted flags are
   * manually set, and it's not validated
   *
   * Examples:
   *     user = new User({ name: "Zach" })
   *
   **/

  constructor: function(document, opts) {
    var that = this
    var opts = opts || {}

    this.document = document || {}
    this.id = idCounter++

    this.deleted = false
    this.saved = false
    this.saving = false
    this.persisted = false
    this.loaded = false
    this.alreadyChanging = false

    this.unsavedChanges = []

    this.modelName = this.constructor.modelName

    if(!this.document._id && !opts.noId) {
      this.document._id = generateObjectId()
    }

    this.collectionName = this.constructor.collectionName()

    this.setupAssociations()

    if(this.get("_id")) {
      this.constructor.listenForChanges(this.get("_id"), this)
    } else {
      this.once("change:_id", function() {
        that.constructor.listenForChanges(this.get("_id"), that)
      })
    }
  }

, fetch: function() {
    var that = this
    this.constructor.sendReadOneMessage(this.document, function(document) {
      if(document) {
        that.set(document, { validate: false, initialize: true })
        that.loaded = true
        that.saved = true
        that.persisted = true
        that.emit("load", that)
      } else {
        that.emit("notExist")
      }
    })
  }

, setupManyAssociations: function() {
    var that = this
    this.manyAssociations = {}

    _(this.constructor.hasMany).each(function(obj, collectionName) {
      var collection
        , Model = obj.model
        , opts = obj.opts
      that.manyAssociations[collectionName] = function(callback) {
        var docs
          , query
        if(collection) {
          return collection
        }
        if(Model.embedded) {
          docs = that.document[collectionName] || []
          delete that.document[collectionName]
          collection = new EmbeddedCollection(docs, that, Model)
        } else {
          query = {}
          query[opts.foreignKey || _.singularize(that.collectionName) + "Id"] = that.get("_id")
          collection = Model.read(query)
        }
        callback = callback || function() {}
        collection.load(function() {
          callback(collection)
        })
        return collection
      }
    })
  }

, setupOneAssociations: function() {
    var that = this
    this.oneAssociations = {}
    _(this.constructor.hasOne).each(function(obj, collectionName) {
      var Model = obj.model
        , opts = obj.opts || {}
        , foreignKey = opts.foreignKey || _.singularize(that.collectionName) + "Id"

      that.oneAssociations[collectionName] = function(cb) {
        var model = new Model({}, {noId: true})
        that.load(function() {
          model.set(foreignKey, that.get("_id"))
          model.fetch()
        })
        //we couldn't find the model
        model.once("notExist", function(err) {
          model.set("_id", generateObjectId())
          model.save(function() {
            model.emit("load")
          })
        })
        model.load(function() {
          cb(model)
        })
        return model
      }
    })
  }

, setupBelongsToAssociations: function() {
    var that = this
    this.belongAssociations = {}
    _(this.constructor.belongingTo).each(function(opts, collectionName) {
      that.belongAssociations[collectionName] = function(cb) {
        var ModelMap = opts.models
          , Model
          , foreignType
          , foreignKey = collectionName + "Id"

        if(opts.polymorphic) {
          foreignType = that.get(collectionName + "Type")
          Model = ModelMap[foreignType]
        } else {
          Model = opts.model
        }
        var model = new Model({}, {noId: true})
        that.load(function() {
          model.set(foreignKey, that.get(foreignKey))
          model.fetch()
        })
        model.once("notExist", function(err) {
          model.save(function() {
            model.emit("load")
          })
        })
        model.load(function() {
          cb(model)
        })
        return model
      }

    })

  }

, setupAssociations: function() {
    this.setupManyAssociations()
    this.setupOneAssociations()
    this.setupBelongsToAssociations()
  }

, stopListening: function() {
    this.constructor.stopListening(this.get("_id"), this)
  }

 /*
  * listen for changes to documents with this id
  * since it's coming from the server we'll assume
  * it validates
  */
, handleChange: function(changes) {
    this.set(changes, {validate: false, initialize:true})
  }

 /*
  * listens for this document having been deleted, if it has
  * emit the delete event and set the deleted attribute to false
  */
, handleRemove: function() {
    this.deleted = true
    this.emit("delete", this)
  }

, sendUpdate: function(query, document, callback) {
    this.constructor.sendUpdateMessage(query, document, callback)
  }

, sendCreate: function(document, callback) {
    this.constructor.sendCreateMessage(document, callback)
  }

, performSave: function(cb) {
    var that = this
      , doc
      , time = (new Date()).getTime()

    this.set("updatedAt", time)

    this.saving = true
    this.emit("saving", that)

    //if the document has not been already persisted to the database before
    //then its a create event
    //
    if(!this.persisted) {
      this.set("createdAt", time)
      this.unsavedChanges = []
      this.sendCreate(this.document, function() {
        that.saving = false
        that.saved = true
        that.persisted = true
        if(!that.loaded) {
          that.emit("load", that)
        }
        that.loaded = true
        that.emit("saved", that)
        cb(that)
      })
    } else {
      var doc = {}
        , changed = _.clone(this.unsavedChanges)

      _(changed).each(function(key) {
        if(key === "_id") {
          return
        }

        that.emit("saving:" + key, that)
        doc[key] = that.document[key]
      })

      this.unsavedChanges = []
      this.sendUpdate({ _id: that.get("_id") }, doc, function(doc) {
        that.lastPersisted = _.clone(that.document)
        that.saved = true
        that.saving = false
        cb(that)
        _(changed).each(function(key) {
          if(key === "_id") {
            return
          }
          that.emit("saved:" + key, that)
        })
        that.emit("saved", that)
      })
    }
  }

 /**
  * Save will create the document on the server if it has not yet been
  * persisted to the database, otherwise it will update the version in the
  * database, calls the callback with itself, returns itself for chaining
  */

, save: function(cb) {
    cb = cb || function() {}
    var that = this
      , doc
      , callNext
      , beforeSaves = []
      , beforeSave

    if(!this.document._id) {
      throw new Error("document doesn't have an _id, maybe it hasn't loaded yet?")
    }

    if(this.constructor.isServer) {
      //it's a create save
      if(!this.persisted) {
        beforeSaves.push.apply(beforeSaves, that.constructor.serverBeforeCreateFunctions || [])
      }
      beforeSaves.push.apply(beforeSaves, that.constructor.serverBeforeSaveFunctions || [])
    } else {
      if(!this.persisted) {
        beforeSaves.push.apply(beforeSaves, that.constructor.beforeCreateFunctions || [])
      }
      beforeSaves.push.apply(beforeSaves, that.constructor.beforeSaveFunctions || [])
    }

    beforeSaves.unshift(function(inst, cb) {
      that.validate(function(inst, errors) {
        //dont pass the errors to the callback, because
        //validate emits it's own invalid event
        if(!errors) {
          cb()
        }
      })
    })

    // Run this after all the beforeSave functions have been called in order
    // to actually save the document, unless any of them have errors
    beforeSaves.push(function() {
      that.performSave(cb)
    })

    _.asyncEach(beforeSaves, function(beforeSave, next) {
      _(beforeSave).bind(that)(that, function(err) {
        if(!err) {
          next()
        } else {
          process.nextTick(function() {
            that.emit("error", err)
          })
        }
      })
    })
    return this
  }

 /**
  * Gets the value of field, takes in an optional function as second
  * parameter, which will get called once the field is available, and every
  * time the field is changed. Great for binding
  *
  * If the callback has arity one, it passes the value, otherwise it passes
  * (key, value)
  **/

, get: function(field) {
    return this.document[field]
  }

, assoc: function(assoc, callback) {
    var callback = callback || function() {}
      , model
    if(this.oneAssociations[assoc]) {
      return this.oneAssociations[assoc](callback)
    } else if(this.belongAssociations[assoc]) {
      return this.belongAssociations[assoc](callback)
    } else if (this.manyAssociations[assoc]) {
      return this.manyAssociations[assoc](callback)
    } else {
      throw new Error("No such association " + assoc)
    }
  }

 /**
  * takes in a hash of keys and values to set, or a string as the first arg
  * and a value(anything serializable) as the second.
  * fires a change event, which happens after all of the changes have been made
  *
  * If you call set from inside of a change event, it won't call another change
  * event, phew!
  **/

, set: function(field, value, opts) {
    opts = opts || {}

    if(typeof field === "undefined") {
      throw new Error("Must at least provide a field to set")
    }

    if(typeof field === "object" && typeof value === "object") {
      opts = value
    }

    if(field === "_id" && typeof this.document._id !== "undefined" && this.document._id !== value) {
      throw new Error("Can't change _id after its been set")
    }

    var that = this
      , alreadyChanging = this.alreadyChanging
      , oldValue

    this.alreadyChanging = true
    // we're initiating the changes, so we'll store the old values
    if(alreadyChanging == false) {
      this.previousDocument = _.clone(this.document)
    }

    if(typeof field == "object") {
      _.each(field, function(v, k) {
        that.set(k, v, opts)
      })
    } else if(that.manyAssociations[field]) {
      _.each(value, function(doc) {
        that.manyAssociations[field]().add(doc)
      })
    } else {
      oldValue = this.document[field]
      if(this.document[field] != value) {
        this.document[field] = value
        this.emit("change:" + field, value, oldValue, this)
      }
      if(!opts.initialize) {
        this.unsavedChanges = _.union(this.unsavedChanges, [field])
      }
    }

    // if THIS instance of set started the changes, it should emit the change
    // event and then end the changingness
    if(alreadyChanging == false) {
      this.triggerChange(opts)
    }
    return this
  }

, triggerChange: function(opts) {
    var that = this
      , fields = _.union(_.keys(this.document), _.keys(this.previousDocument))
      , changedFields = _.filter(fields, function(key) {
          return that.document[key] != that.previousDocument[key]
        })

    // if something actually changed
    if(changedFields.length > 0) {
      this.saved = false
      this.emit("change", this, changedFields)
      _(changedFields).each(function(field) {
        if(opts.validate !== false) {
          that.validateField(field)
        }
      })
    }
    this.previousDocument = _.clone(this.document)
    this.alreadyChanging = false
  }

, remove: function(callback) {
    callback = callback || function() {}
    var that = this
    this.constructor.sendDeleteMessage({ _id: this.get("_id") }, function(document) {
      callback(that)
    })
  }

, validateField: function(field, callback, opts) {
    opts = opts || {}
    callback = callback || function() {}

    var that = this
    var validations = (this.constructor.validations || {})[field]
    var valid = []
    _.asyncEach(validations, function(value, rule, next) {
      if(rule === "min" && that.get(field) && that.get(field).length < value) {
        valid.push("short")
        next()
      } else if(rule === "isInt" && that.get(field) && (parseFloat(that.get(field)) != parseInt(that.get(field)) || isNaN(that.get(field)))) {
        valid.push("isInt")
        next()
      } else if(rule === "max" && that.get(field) && that.get(field).length >= value) {
        valid.push("long")
        next()
      } else if(rule === "required" && !that.get(field)) {
        valid.push("required")
        next()
      } else if(rule === "unique") {
        that.isUnique(field, that.get(field), function(unique) {
          if(!unique) {
            valid.push("unique")
          }
          next()
        })
      } else {
        next()
      }
    }, function() {
      if(valid.length > 0) {
        if(!opts.silent) {
          that.emit("invalid:" + field, that, valid)
        }
        callback(that, valid)
      } else {
        // emit an event when a field is valid
        // useful for removing error messages
        if(!opts.silent) {
          that.emit("valid:" + field, that)
        }
        callback(that)
      }
    })
  }

, validate: function(cb, opts) {
    cb = cb || function() {}

    opts = opts || {}
    var that = this
    _.asyncMap(this.constructor.validations, function(validations, field, next) {
      that.validateField(field, function(instance, errors) {
        if(errors) {
          next(field, errors)
        } else {
          next()
        }
      }, opts)
    }, function(valid) {
      if(_.keys(valid).length != 0) {
        cb(that, valid)
        if(!opts.silent) {
          process.nextTick(function() {
            that.emit("invalid", that, valid)
          })
        }
      } else {
        cb(that)
      }
    })
  }

, isUnique: function(property, value, done) {
    var obj = {_id: {$ne : this.get("_id")}}
    value = value || ""
    obj[property] = value
    this.constructor.find(obj, function(things) {
      done(things.length === 0)
    })
  }
  /**
   * Called as soon as the document is loaded, if the document is already
   * loaded, call it right away
   */
, load: function(callback) {
    if(this.loaded) {
      process.nextTick(callback)
    } else {
      this.once("load", callback)
    }
  }

, keys: function() {
    var keys = _.clone(this.constructor.keys)
    keys.push("_id")
    keys.push("createdAt")
    keys.push("updatedAt")
    return keys
  }

, oneAssocs: function() {
    var assocs = _.union(
      _.keys(this.constructor.hasOne || {})
    , _.keys(this.constructor.belongingTo || {})
    , _.keys(this.constructor.polymorphicBelongingTo || {})
    )
    return assocs
  }
, manyAssocs: function() {
    return _.keys(this.constructor.hasMany || {})
  }
, assocs: function() {
   return this.oneAssocs().concat(this.manyAssocs)
  }
}
module.exports = InstanceMethods
<|endoftext|>


<commit_before>Template.TableHeader.onCreated(function () {
  let self = this;

  self.settings = self.data.settings;
  self.selector = self.data.selector;
  self.fields = self.data.fields;
});

Template.TableHeader.onRendered(function () {
  let self = this;

  // self.autorun(function (c) {
  //   let settings = self.settings.get();

  //   Session.set(self.settings.tableName, self.settings);
  // });
});

var timeoutId;

Template.TableHeader.events({
  'keyup #search-box': function (e, template) {
    e.preventDefault();

    let instance = Template.instance();

    clearTimeout(timeoutId);

    timeoutId = setTimeout(function () {
      let searchString = e.currentTarget.value;

      let selector = Helpers.generateSearchFilter(
        instance.selector.get(),
        instance.fields.get(),
        searchString
      );

      instance.selector.set(selector);
    }, 500);
  },
  'click li[role="presentation"] > a': function (e, template) {
    e.preventDefault();

    let entry = parseInt(e.currentTarget.dataset.entry);

    let settings = Template.instance().settings.get();
    settings.current.entry = entry;

    Template.instance().settings.set(settings);
  }
});

Template.TableHeader.helpers({
  selectedEntry: (entry) => {
    return entry === Template.instance().settings.get().current.entry ? 'active' : '';
  },
  settings: () => {
    return Template.instance().settings.get();
  }
});<commit_msg>fixed pagination on entry change<commit_after>Template.TableHeader.onCreated(function () {
  let self = this;

  self.settings = self.data.settings;
  self.selector = self.data.selector;
  self.fields = self.data.fields;
});

Template.TableHeader.onRendered(function () {
  let self = this;

  // self.autorun(function (c) {
  //   let settings = self.settings.get();

  //   Session.set(self.settings.tableName, self.settings);
  // });
});

var timeoutId;

Template.TableHeader.events({
  'keyup #search-box': function (e, template) {
    e.preventDefault();

    let instance = Template.instance();

    clearTimeout(timeoutId);

    timeoutId = setTimeout(function () {
      let searchString = e.currentTarget.value;

      let selector = Helpers.generateSearchFilter(
        instance.selector.get(),
        instance.fields.get(),
        searchString
      );

      instance.selector.set(selector);
    }, 500);
  },
  'click li[role="presentation"] > a': function (e, template) {
    e.preventDefault();

    let entry = parseInt(e.currentTarget.dataset.entry);

    let settings = Template.instance().settings.get();
    settings.current.entry = entry;
    settings.current.page = 1;

    Template.instance().settings.set(settings);
  }
});

Template.TableHeader.helpers({
  selectedEntry: (entry) => {
    return entry === Template.instance().settings.get().current.entry ? 'active' : '';
  },
  settings: () => {
    return Template.instance().settings.get();
  }
});<|endoftext|>


<commit_before>/* Copyright 2016 Paul Brewer Economic & Financial Technology Consulting LLC */
/* tests for PartialIndex */
/* Open Source License: The MIT Public License */

var assert = require('assert');
var should = require('should');
var PartialIndex = require("../index.js");

describe('PartialIndex', function(){
    var tendata =
	[[1,9,3],
	 [2,8,1],
	 [3,7,4],
	 [4,6,1],
	 [5,5,5],
	 [6,4,9],
	 [7,3,2],
	 [8,2,6],
	 [9,1,5]];
    describe('new properties', function(){
	var data = tendata.slice();
	var limit = 7;
	var x = new PartialIndex(data,limit,1);
	it('should have property data', function(){
	    x.should.have.property('data');
	});
	it('data should be correct', function(){
	    x.data.should.eql(data);
	});
	it('should have property limit', function(){
	    x.should.have.property('limit');
	});
	it('limit should be correct', function(){
	    x.limit.should.eql(limit);
	});
	it('should have idx property', function(){
	    x.should.have.property('idx');
	});
	it('idx should be empty', function(){
	    x.idx.should.eql([]);
	});
	it('should have functions datafilter, datacomp, idxcomp, idxfilter, idxdata', function(){
	    x.should.have.property('datafilter');
	    x.datafilter.should.be.type('function');
	    x.should.have.property('datacomp');
	    x.datacomp.should.be.type('function');
	    x.should.have.property('idxcomp');
	    x.idxcomp.should.be.type('function');
	    x.should.have.property('idxfilter');
	    x.idxfilter.should.be.type('function');
	    x.should.have.property('idxdata');
	    x.idxdata.should.be.type('function');
	});
	it('.idxdata() should be []', function(){
	    x.idxdata().should.be.eql([]);
	});
	it('should have property needScan set to 1', function(){
	    x.should.have.property('needScan');
	    x.needScan.should.equal(1);
	});

    });

    function ascTests(j, col){
	describe('col '+col+' limit '+j, function(){
	    var seq = [],i,l;
	    for(i=0;i<tendata.length;++i) seq[i]=i;
	    it('should have correct idx', function(){
		var x = new PartialIndex(tendata.slice(),j,col);
		x.scan();
		if (col===0)
		    x.idx.should.eql(seq.slice(0,j));
		else
		    x.idx.should.eql(seq.slice().reverse().slice(0,j));
	    });
	    it('should have correct idxdata()', function(){
		var x = new PartialIndex(tendata.slice(),j,col);
		var a;
		x.scan();
		if (col===0)
		    a = tendata.slice(0,j);
		else
		    a = tendata.slice().reverse().slice(0,j);
		x.idxdata().should.eql(a);
		assert.strictEqual(x.idxdata(-1), undefined);
		var i;
		for(i=0;i<j;++i)
		    x.idxdata(i).should.eql(a[i]);
		assert.strictEqual(x.idxdata(j), undefined);
	    });
	    it('should return correct val(i) ', function(){
		var i=0,l=j;
		var x = new PartialIndex(tendata.slice(),j,col);
		x.scan();
		for(i=0;i<l;++i)
		    assert.strictEqual(x.val(i), i+1);
	    });
	    it('should return undefined val(i) when i out of range', function(){
		var x = new PartialIndex(tendata.slice(),j,col);
		assert.strictEqual(x.val(0), undefined);
		x.scan();
		assert.strictEqual(x.val(-1), undefined, "val(-1) !== undefined");
		assert.strictEqual(x.val(j), undefined, "val(length) !== undefined");
	    });

	    it('should return correct vals()', function(){
		var x = new PartialIndex(tendata.slice(),j,col);
		var a = [1,2,3,4,5,6,7,8,9];
		x.scan();
		x.vals().should.eql((0===j)? ([]): (a.slice(0,j)));
	    });
	    it('should have correct idx when deleting [4]', function(){
		var x = new PartialIndex(tendata.slice(),j,col);
		x.scan();
		x.remove([4]);
		x.data.splice(4,1);
		if (j>4)
		    x.idx.should.eql((col===0)?(seq.slice(0,j-1)):(seq.slice().reverse().slice(1,j)));
		else
		    x.idx.should.eql((col===0)?(seq.slice(0,j)):(seq.slice().reverse().slice(1,j+1)));
		x.scan();
		var a;
		if (j===0) a=[];
		if (j>0 && j<9 && col===0) a = seq.slice(0,j);
		if (j===9 && col===0) a = seq.slice(0,j-1);
		if (j>0 && col===1) a = seq.slice().reverse().slice(1,j+1);
		x.idx.should.eql(a);
	    });
	});
    }

    describe('ascending index on copy of tendata ', function(){
	var i, j, l;
	l = 10;
	for(var col=0; col<2; ++col)
	    for(j=0;j<l;++j)
		ascTests(j,col);

    });

    describe('index cols 2 asc,0 asc ', function(){
	var answer =  [1,3,6,0,2,4,8,7,5];
	var answer_rm = [2,5,0,1,3,7,6,4];
	var i = 0;
	for(i=0;i<10;++i)
	    (function(limit){
		describe(' limit '+limit, function(){
		    it('should have correct idx after scan', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,0,1);
			x.scan();
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should also be correct using push and syncLast', function(){
			var x = new PartialIndex([],limit,2,1,0,1);
			tendata.forEach(function(item){
			    x.data.push(item);
			    x.syncLast();
			});
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should have correct idx after removing [1]', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,0,1);
			x.scan();
			x.remove([1]);
			x.data.splice(1,1);
			var a;
			if (limit===0) a=[];
			if (limit===1) a=[];
			if (limit>=2)  a=answer_rm.slice(0,limit-1);
			x.idx.should.eql(a);
			x.scan();
			if (limit>=1) a=answer_rm.slice(0,limit);
			x.idx.should.eql(a);
		    });
		});
	    })(i);
    });

    describe('index cols 2 asc, 1 asc ', function(){
	var answer = [3,1,6,0,2,8,4,7,5];
	var answer_rm = [2,5,0,1,7,3,6,4];
	var i = 0;
	for(i=0;i<10;++i)
	    (function(limit){
		describe(' limit '+limit, function(){
		    it('should have correct idx ', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,1,1);
			x.scan();
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should also be correct using push and syncLast', function(){
			var x = new PartialIndex([], limit, 2,1,1,1);
			tendata.forEach(function(item){
			    x.data.push(item);
			    x.syncLast();
			});
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should have correct idx after removing [1]', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,1,1);
			x.scan();
			x.remove([1]);
			x.data.splice(1,1);
			var a;
			if (limit===0) a=[];
			if (limit===1) a=[2];
			if (limit===2) a=[2];
			if (limit>=3) a=answer_rm.slice(0,limit-1);
			x.idx.should.eql(a);
			x.scan();
			if (limit>=0) a=answer_rm.slice(0,limit);
			x.idx.should.eql(a);
		    });
		});
	    })(i);
    });

    describe('index cols 2 asc, 0 desc ', function(){
	var answer = [3,1,6,0,2,8,4,7,5];
	var answer_rm = [2,5,0,1,7,3,6,4];
	var i = 0;
	for(i=0;i<10;++i)
	    (function(limit){
		describe(' limit '+limit, function(){
		    it('should have correct idx ', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,0,-1);
			x.scan();
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should also be correct using push and syncLast', function(){
			var x = new PartialIndex([], limit, 2,1,0,-1);
			tendata.forEach(function(item){
			    x.data.push(item);
			    x.syncLast();
			});
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should have correct idx after removing [1]', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,0,-1);
			x.scan();
			x.remove([1]);
			x.data.splice(1,1);
			var a;
			if (limit===0) a=[];
			if (limit===1) a=[2];
			if (limit===2) a=[2];
			if (limit>=3) a=answer_rm.slice(0,limit-1);
			x.idx.should.eql(a);
			x.scan();
			if (limit>=0) a=answer_rm.slice(0,limit);
			x.idx.should.eql(a);
		    });
		});
	    })(i);
    });

    describe('index cols 2 asc,1 desc ', function(){
	var answer =  [1,3,6,0,2,4,8,7,5];
	var answer_rm = [2,5,0,1,3,7,6,4];
	var i = 0;
	for(i=0;i<10;++i)
	    (function(limit){
		describe(' limit '+limit, function(){
		    it('should have correct idx after scan', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,1,-1);
			x.scan();
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should also be correct using push and syncLast', function(){
			var x = new PartialIndex([],limit,2,1,1,-1);
			tendata.forEach(function(item){
			    x.data.push(item);
			    x.syncLast();
			});
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should have correct idx after removing [1]', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,1,-1);
			x.scan();
			x.remove([1]);
			x.data.splice(1,1);
			var a;
			if (limit===0) a=[];
			if (limit===1) a=[];
			if (limit>=2)  a=answer_rm.slice(0,limit-1);
			x.idx.should.eql(a);
			x.scan();
			if (limit>=1) a=answer_rm.slice(0,limit);
			x.idx.should.eql(a);
		    });
		});
	    })(i);
    });
    describe(".remove()", function(){
	var data = [[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[7,8]];
	it('should remove ordered list [2,3]', function(){
	    var x = new PartialIndex(data,5,0,1);
	    x.remove([2,3]);
            assert.ok(true);
	});
	it('should throw if removing misordered list [3,2]', function(){
	    var x = new PartialIndex(data,5,0,1);
	    x.remove.bind(x,[3,2]).should.throw();
	});
    });
});
<commit_msg>additional .remove() testing<commit_after>/* Copyright 2016 Paul Brewer Economic & Financial Technology Consulting LLC */
/* tests for PartialIndex */
/* Open Source License: The MIT Public License */

var assert = require('assert');
var should = require('should');
var PartialIndex = require("../index.js");

describe('PartialIndex', function(){
    var tendata =
	[[1,9,3],
	 [2,8,1],
	 [3,7,4],
	 [4,6,1],
	 [5,5,5],
	 [6,4,9],
	 [7,3,2],
	 [8,2,6],
	 [9,1,5]];
    describe('new properties', function(){
	var data = tendata.slice();
	var limit = 7;
	var x = new PartialIndex(data,limit,1);
	it('should have property data', function(){
	    x.should.have.property('data');
	});
	it('data should be correct', function(){
	    x.data.should.eql(data);
	});
	it('should have property limit', function(){
	    x.should.have.property('limit');
	});
	it('limit should be correct', function(){
	    x.limit.should.eql(limit);
	});
	it('should have idx property', function(){
	    x.should.have.property('idx');
	});
	it('idx should be empty', function(){
	    x.idx.should.eql([]);
	});
	it('should have functions datafilter, datacomp, idxcomp, idxfilter, idxdata', function(){
	    x.should.have.property('datafilter');
	    x.datafilter.should.be.type('function');
	    x.should.have.property('datacomp');
	    x.datacomp.should.be.type('function');
	    x.should.have.property('idxcomp');
	    x.idxcomp.should.be.type('function');
	    x.should.have.property('idxfilter');
	    x.idxfilter.should.be.type('function');
	    x.should.have.property('idxdata');
	    x.idxdata.should.be.type('function');
	});
	it('.idxdata() should be []', function(){
	    x.idxdata().should.be.eql([]);
	});
	it('should have property needScan set to 1', function(){
	    x.should.have.property('needScan');
	    x.needScan.should.equal(1);
	});

    });

    function ascTests(j, col){
	describe('col '+col+' limit '+j, function(){
	    var seq = [],i,l;
	    for(i=0;i<tendata.length;++i) seq[i]=i;
	    it('should have correct idx', function(){
		var x = new PartialIndex(tendata.slice(),j,col);
		x.scan();
		if (col===0)
		    x.idx.should.eql(seq.slice(0,j));
		else
		    x.idx.should.eql(seq.slice().reverse().slice(0,j));
	    });
	    it('should have correct idxdata()', function(){
		var x = new PartialIndex(tendata.slice(),j,col);
		var a;
		x.scan();
		if (col===0)
		    a = tendata.slice(0,j);
		else
		    a = tendata.slice().reverse().slice(0,j);
		x.idxdata().should.eql(a);
		assert.strictEqual(x.idxdata(-1), undefined);
		var i;
		for(i=0;i<j;++i)
		    x.idxdata(i).should.eql(a[i]);
		assert.strictEqual(x.idxdata(j), undefined);
	    });
	    it('should return correct val(i) ', function(){
		var i=0,l=j;
		var x = new PartialIndex(tendata.slice(),j,col);
		x.scan();
		for(i=0;i<l;++i)
		    assert.strictEqual(x.val(i), i+1);
	    });
	    it('should return undefined val(i) when i out of range', function(){
		var x = new PartialIndex(tendata.slice(),j,col);
		assert.strictEqual(x.val(0), undefined);
		x.scan();
		assert.strictEqual(x.val(-1), undefined, "val(-1) !== undefined");
		assert.strictEqual(x.val(j), undefined, "val(length) !== undefined");
	    });

	    it('should return correct vals()', function(){
		var x = new PartialIndex(tendata.slice(),j,col);
		var a = [1,2,3,4,5,6,7,8,9];
		x.scan();
		x.vals().should.eql((0===j)? ([]): (a.slice(0,j)));
	    });
	    it('should have correct idx when removing [4]', function(){
		var x = new PartialIndex(tendata.slice(),j,col);
		x.scan();
		x.remove([4]);
		x.data.splice(4,1);
		if (j>4)
		    x.idx.should.eql((col===0)?(seq.slice(0,j-1)):(seq.slice().reverse().slice(1,j)));
		else
		    x.idx.should.eql((col===0)?(seq.slice(0,j)):(seq.slice().reverse().slice(1,j+1)));
		x.scan();
		var a;
		if (j===0) a=[];
		if (j>0 && j<9 && col===0) a = seq.slice(0,j);
		if (j===9 && col===0) a = seq.slice(0,j-1);
		if (j>0 && col===1) a = seq.slice().reverse().slice(1,j+1);
		x.idx.should.eql(a);
	    });
	});
    }

    describe('ascending index on copy of tendata ', function(){
	var i, j, l;
	l = 10;
	for(var col=0; col<2; ++col)
	    for(j=0;j<l;++j)
		ascTests(j,col);

    });

    describe('index cols 2 asc,0 asc ', function(){
	var answer =  [1,3,6,0,2,4,8,7,5];
	var answer_rm = [2,5,0,1,3,7,6,4];
	var i = 0;
	for(i=0;i<10;++i)
	    (function(limit){
		describe(' limit '+limit, function(){
		    it('should have correct idx after scan', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,0,1);
			x.scan();
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should also be correct using push and syncLast', function(){
			var x = new PartialIndex([],limit,2,1,0,1);
			tendata.forEach(function(item){
			    x.data.push(item);
			    x.syncLast();
			});
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should have correct idx after removing [1]', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,0,1);
			x.scan();
			x.remove([1]);
			x.data.splice(1,1);
			var a;
			if (limit===0) a=[];
			if (limit===1) a=[];
			if (limit>=2)  a=answer_rm.slice(0,limit-1);
			x.idx.should.eql(a);
			x.scan();
			if (limit>=1) a=answer_rm.slice(0,limit);
			x.idx.should.eql(a);
		    });
		});
	    })(i);
    });

    describe('index cols 2 asc, 1 asc ', function(){
	var answer = [3,1,6,0,2,8,4,7,5];
	var answer_rm = [2,5,0,1,7,3,6,4];
	var i = 0;
	for(i=0;i<10;++i)
	    (function(limit){
		describe(' limit '+limit, function(){
		    it('should have correct idx ', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,1,1);
			x.scan();
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should also be correct using push and syncLast', function(){
			var x = new PartialIndex([], limit, 2,1,1,1);
			tendata.forEach(function(item){
			    x.data.push(item);
			    x.syncLast();
			});
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should have correct idx after removing [1]', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,1,1);
			x.scan();
			x.remove([1]);
			x.data.splice(1,1);
			var a;
			if (limit===0) a=[];
			if (limit===1) a=[2];
			if (limit===2) a=[2];
			if (limit>=3) a=answer_rm.slice(0,limit-1);
			x.idx.should.eql(a);
			x.scan();
			if (limit>=0) a=answer_rm.slice(0,limit);
			x.idx.should.eql(a);
		    });
		});
	    })(i);
    });

    describe('index cols 2 asc, 0 desc ', function(){
	var answer = [3,1,6,0,2,8,4,7,5];
	var answer_rm = [2,5,0,1,7,3,6,4];
	var i = 0;
	for(i=0;i<10;++i)
	    (function(limit){
		describe(' limit '+limit, function(){
		    it('should have correct idx ', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,0,-1);
			x.scan();
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should also be correct using push and syncLast', function(){
			var x = new PartialIndex([], limit, 2,1,0,-1);
			tendata.forEach(function(item){
			    x.data.push(item);
			    x.syncLast();
			});
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should have correct idx after removing [1]', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,0,-1);
			x.scan();
			x.remove([1]);
			x.data.splice(1,1);
			var a;
			if (limit===0) a=[];
			if (limit===1) a=[2];
			if (limit===2) a=[2];
			if (limit>=3) a=answer_rm.slice(0,limit-1);
			x.idx.should.eql(a);
			x.scan();
			if (limit>=0) a=answer_rm.slice(0,limit);
			x.idx.should.eql(a);
		    });
		});
	    })(i);
    });

    describe('index cols 2 asc,1 desc ', function(){
	var answer =  [1,3,6,0,2,4,8,7,5];
	var answer_rm = [2,5,0,1,3,7,6,4];
	var i = 0;
	for(i=0;i<10;++i)
	    (function(limit){
		describe(' limit '+limit, function(){
		    it('should have correct idx after scan', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,1,-1);
			x.scan();
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should also be correct using push and syncLast', function(){
			var x = new PartialIndex([],limit,2,1,1,-1);
			tendata.forEach(function(item){
			    x.data.push(item);
			    x.syncLast();
			});
			x.idx.should.eql(answer.slice(0,limit));
		    });
		    it('should have correct idx after removing [1]', function(){
			var x = new PartialIndex(tendata.slice(),limit,2,1,1,-1);
			x.scan();
			x.remove([1]);
			x.data.splice(1,1);
			var a;
			if (limit===0) a=[];
			if (limit===1) a=[];
			if (limit>=2)  a=answer_rm.slice(0,limit-1);
			x.idx.should.eql(a);
			x.scan();
			if (limit>=1) a=answer_rm.slice(0,limit);
			x.idx.should.eql(a);
		    });
		});
	    })(i);
    });
    describe(".remove()", function(){
	var data = [[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[7,8]];
	it('should remove ordered list [2,3]', function(){
	    var x = new PartialIndex(data,5,0,1);
	    var y = new PartialIndex(data,5,0,-1);
	    x.scan();
	    y.scan();
	    x.remove([2,3]);
	    y.remove([2,3]);
            x.idx.should.eql([0,1,2]);
	    y.idx.should.eql([4,3,2]);
	});
	it('should throw if removing misordered list [3,2]', function(){
	    var x = new PartialIndex(data,5,0,1);
	    x.remove.bind(x,[3,2]).should.throw();
	});
    });
});
<|endoftext|>


<commit_before>import Ember from 'ember';
import loadAll from 'ember-osf/utils/load-relationship';
import Analytics from '../mixins/analytics';

export default Ember.Component.extend(Analytics, {
    elementId: 'preprint-file-view',
    endIndex: 6,
    startIndex: 0,

    scrollAnim: '',
    selectedFile: null,

    hasAdditionalFiles: function() {
        return this.get('files.length') > 1;
    }.property('files'),

    hasPrev: function() {
        return this.get('startIndex') > 0;
    }.property('files', 'endIndex', 'startIndex'),

    hasNext: function() {
        return this.get('endIndex') < this.get('files.length');
    }.property('files', 'endIndex', 'startIndex'),

    __files: function() {
        this.set('files', []);
        this.set('selectedFile', null);
        this.get('node').get('files')
            .then(providers => {
                this.set('provider', providers.findBy('name', 'osfstorage'));
                return loadAll(this.get('provider'), 'files', this.get('files'), {'page[size]': 50});
            })
            .then(() => {
                let pf = this.get('files').findBy('id', this.get('preprint.primaryFile.id'));
                if (pf) {
                    this.get('files').removeObject(pf);
                    this.set('primaryFile', pf);
                }

                this.set('selectedFile', this.get('primaryFile'));
                this.set('files', [this.get('primaryFile')].concat(this.get('files')));
            });

    }.observes('preprint'),

    init() {
        this._super(...arguments);
        this.__files();

    },
    actions: {
        next() {
            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'file browser',
                    action: 'click',
                    label: 'next'
                });

            if (this.get('endIndex') > this.get('files.length')) return;

            this.set('scrollAnim', 'toLeft');
            this.set('endIndex', this.get('endIndex') + 5);
            this.set('startIndex', this.get('startIndex') + 5);
        },
        prev() {
            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'file browser',
                    action: 'click',
                    label: 'prev'
                });

            if (this.get('startIndex') <= 0) return;

            this.set('scrollAnim', 'toRight');
            this.set('endIndex', this.get('endIndex') - 5);
            this.set('startIndex', this.get('startIndex') - 5);
        },
        changeFile(file) {
            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'file browser',
                    action: 'select',
                    label: 'file'
                });

            this.set('selectedFile', file);

            if (this.attrs.chooseFile) {
                this.sendAction('chooseFile', file);
            }
        },
    },
});
<commit_msg>Next and Prev buttons can point multiple directions, depending if horizontal or vertical scroll.<commit_after>import Ember from 'ember';
import loadAll from 'ember-osf/utils/load-relationship';
import Analytics from '../mixins/analytics';

export default Ember.Component.extend(Analytics, {
    elementId: 'preprint-file-view',
    endIndex: 6,
    startIndex: 0,

    scrollAnim: '',
    selectedFile: null,

    hasAdditionalFiles: function() {
        return this.get('files.length') > 1;
    }.property('files'),

    hasPrev: function() {
        return this.get('startIndex') > 0;
    }.property('files', 'endIndex', 'startIndex'),

    hasNext: function() {
        return this.get('endIndex') < this.get('files.length');
    }.property('files', 'endIndex', 'startIndex'),

    __files: function() {
        this.set('files', []);
        this.set('selectedFile', null);
        this.get('node').get('files')
            .then(providers => {
                this.set('provider', providers.findBy('name', 'osfstorage'));
                return loadAll(this.get('provider'), 'files', this.get('files'), {'page[size]': 50});
            })
            .then(() => {
                let pf = this.get('files').findBy('id', this.get('preprint.primaryFile.id'));
                if (pf) {
                    this.get('files').removeObject(pf);
                    this.set('primaryFile', pf);
                }

                this.set('selectedFile', this.get('primaryFile'));
                this.set('files', [this.get('primaryFile')].concat(this.get('files')));
            });

    }.observes('preprint'),

    init() {
        this._super(...arguments);
        this.__files();

    },
    actions: {
        next(direction) {
            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'file browser',
                    action: 'click',
                    label: 'next'
                });

            if (this.get('endIndex') > this.get('files.length')) return;

            this.set('scrollAnim', `to${direction}`);
            this.set('endIndex', this.get('endIndex') + 5);
            this.set('startIndex', this.get('startIndex') + 5);
        },
        prev(direction) {
            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'file browser',
                    action: 'click',
                    label: 'prev'
                });

            if (this.get('startIndex') <= 0) return;

            this.set('scrollAnim', `to${direction}`);
            this.set('endIndex', this.get('endIndex') - 5);
            this.set('startIndex', this.get('startIndex') - 5);
        },
        changeFile(file) {
            Ember.get(this, 'metrics')
                .trackEvent({
                    category: 'file browser',
                    action: 'select',
                    label: 'file'
                });

            this.set('selectedFile', file);

            if (this.attrs.chooseFile) {
                this.sendAction('chooseFile', file);
            }
        },
    },
});
<|endoftext|>