Source: lib/order.js

/*! order.js */

/**
 * This sorts an array of items, or generates a sort function compiled from order-by-clause sort parameters object.
 * Since version 0.0.7, dot notation (e.g. foo.bar.baz) is supported to handle child nodes.
 *
 * @method obop.prototype.order
 * @param {Array} [array] - source array to sort
 * @param {Object|Array|Function} sort - sort parameters or function
 * @returns {Array} Sorted array of items (if source array given)
 * @returns {Function} Sort function compiled for Array.prototype.sort() method (if source array not given)
 * @returns {Error} Error instance (if source array not given but invalid sort parameters given)
 *
 * @example
 * var list = [
 *    { name: "apple", price: 50 },
 *    { name: "orange", price: 10 },
 *    { name: "pineapple", price: 70 },
 *    { name: "grape", price: 30 }
 * ];
 *
 * // without obop
 * var out1 = list.sort(function(a, b) {
 *     return a.price - b.price;
 * });
 * console.log(out1);
 *
 * // with obop
 * var order = { price: 1 };
 * var out2 = obop.order(list, order);
 * console.log(out2);
 *
 * // compile a sort function
 * var order = { price: 1 };
 * var sorter = obop.order(order);
 * if (sorter instanceof Error) throw sorter;
 * var out3 = list.sort(sorter);
 * console.log(out3);
 */

exports.order = function(array, param) {
  var func;
  var len = arguments.length;
  if (len == 1) {
    func = order.call(this, array);
    if (func instanceof Error) throw func;
    return func;
  } else if (len == 2) {
    if (array instanceof Array) {
      func = order.call(this, param);
      if (func instanceof Error) throw func;
      return array.sort(func);
    } else {
      throw new Error('Invalid argument type: ' + array);
    }
  } else {
    throw new Error('Invalid arguments length: ' + len);
  }
};

var cnt = 0;

function order(param) {
  var obop = this;
  var _nop = null; // no operation

  // function type
  if ('function' == typeof param) {
    return param;
  }

  // default parameters
  param = param || {};

  // other types than object
  if ('object' != typeof param) {
    return new Error('Invalid order operator type: ' + param);
  }

  if (param instanceof Array) {
    // Array format: [ ["foo", 1], ["bar", -1] ]
    var err;
    param.forEach(function(pair) {
      if (err) return;
      // each pair must have a couple of elements
      if ((pair instanceof Array) &&
        (pair.length === 2) &&
        ('undefined' !== typeof pair[0]) &&
        (pair[1] - pair[1] === 0)) {
        // ok
      } else {
        err = new Error('Invalid order pair: ' + pair);
      }
    });
    if (err) return err;
  } else {
    // Object format: { "foo": 1, "bar": -1 }
    var array = [];
    for (var key in param) {
      var pair = [key, param[key]];
      array.push(pair);
    }

    param = array;
  }

  var len = param.length;

  // no parameters
  if (len === 0) {
    return _nop;
  }

  // one or more parameters
  var func;
  for (var i = len - 1; i >= 0; i--) {
    func = gen(param[i][0], param[i][1], func);
  }
  return func;

  function gen(key, ret, next) {
    var pos = key.indexOf('.');
    var pre, post, pair, test;

    if (pos > -1) {
      pre = key.substr(0, pos);
      post = key.substr(pos + 1);
      pair = [post, ret];
      test = obop.order([pair]);
      return sort_dot;
    } else {
      return sort;
    }

    var undef;

    // function sort(a, b) {
    //   return (a[key] < b[key]) ? -ret : (a[key] > b[key]) ? ret : next ? next(a, b) : 0;
    // }

    function sort(a, b) {
      var aa = a[key];
      var bb = b[key];
      if (aa > bb) return ret;
      if (aa < bb) return -ret;
      if (aa !== undef && bb === undef) return ret;  // aa > bb == undef
      if (aa === undef && bb !== undef) return -ret; // bb > aa == undef
      if (next) return next(a, b);
      return 0;
    }

    function sort_dot(a, b) {
      var aa = a[pre];
      var bb = b[pre];
      var ao = (aa && 'object' === typeof aa);
      var bo = (bb && 'object' === typeof bb);
      if (ao || bo) {
        if (!ao) aa = {};
        if (!bo) bb = {};
        var re = test(aa, bb);
        if (re) return re;
      }
      if (next) return next(a, b);
      return 0;
    }
  }
}