Class in Sqimitive Ordered

class Sqimitive.Ordered
A mixIn that transparently makes Sqimitive reliably ordered

Defined in: sqimitive.js, line 5228

Description (skip)

A mixIn that transparently makes Sqimitive reliably ordered.

Example
obj.mixIn(Sqimitive.Ordered)    // add to an instance (must be empty!)
Class.mixIn(Sqimitive.Ordered)  // or add to a class

// Or add to a class when declaring it:
var Class = Sqimitive.Base.extend({
  mixIn: [Sqimitive.Ordered],
  // ...
})

JavaScript objects are unordered, even if it appears to work the way you expect (it’s very browser-specific). For example: Object.keys({9: 0, 1: 0}) returns [1, 9]. Since Sqimitive\Base stores children in an object (_children), sqimitives are unordered by default; this affects nested(), toArray() (util) and many other functions.

Ordered maintains order of children without disrupting the standard API but makes nesting and unnesting on such sqimitives slower (access time is unaffected).

Ordered supports any _owning mode (but _owning works faster). Like with Base, duplicate children may appear if _owning is unset – to disallow this wrap =nestEx in a check with contains() (see nestdup).

Properties

_ordered

Modifiers: protected

Holds positional information about objects contained within this instance.

Value Types
Types Notes
array of object Keys:
Members
Name Types Notes
childobject A Sqimitive in _children
keystring child’s key in _children
posany The value from nestEx’s options.pos

_ordered is a properly sorted array of _children, in order determined by _sorter(). It’s kept in sync with _children automatically.

The format of _ordered objects conveniently matches that accepted by nestEx():

// Assuming both collections are Ordered and _owning, removes
// the first child from col1 and inserts it in col2, preserving
// the same position and key:
col2.nestEx(col1.at(0))

You’re advised against accessing _ordered at all. Instead, use at().

Defined in: sqimitive.js, line 5282Show code

_ordered: [],

Methods

indexFor ( array, value, func, cx )

Modifiers: static

Part of: tag_Utilities

Determines insertion position for a new member value in array using a binary search.

indexFor() is an adaptation of Underscore’s un:sortedIndex(). It supports two sort callback styles: relative ((a, b), as for Array’s sort()) and weight-based ((a), as for un:sortBy()).

First time func is called it’s given only 1 argument (value) and expected to return value’s weight (which is stored for later calls), then it’s called repeatedly to compare value against other members (returning < 0 if b must go before a, > 0 if after, == 0 if their sort order is the same and either can go in front of another).

In Ordered, indexFor() is used to maintain the order of children (in _ordered) using _sorter() for func. Usually there’s no need to call indexFor() directly.

cx is func’s context (required).

ExampleRelative func ignores the first call, otherwise is the same as with standard sort():
Sqimitive.Ordered.indexFor(a, v, function (a, b) {
  return a > b ? +1 : (a < b ? -1 : 0)
  // This would fail without a check because b may be null:
  return b && (a.prop - b.prop)
})

Weight-based func uses all 3 arguments:

Sqimitive.Ordered.indexFor(a, v, function (a, b, posB) {
  var posA = a.someWeightProp    // assuming it's a number.
  return arguments.length == 1 ? posA : (posA - posB)
})

Defined in: sqimitive.js, lines 5603-5614 (12 lines) • Show code

  indexFor: function (array, value, func, cx) {
    var pos = array.length && func.call(cx, value)
    for (var low = 0, high = array.length, rel = 1; low < high; rel) {
      var mid = Math.floor((low + high) / 2)
      rel = -func.call(cx, array[mid], value, pos)
      // Exiting immediately if rel == 0 (found a member with the same sort
      // order) - low remains next to current index so order of ? : is
      // important.
      rel < 0 ? (high = mid) : (low = mid + 1)
    }
    return low
  },
},
_repos ( child, index )

Modifiers: protected

Called when a child’s position has changed or was assumed for the first time.

Arguments
Name Types Notes
sqimobject The child that has changed position.
indexint sqim’s current (new) index in this; can be given to at()

Typically, you’d listen to/override this method to keep positions of children on screen (in their parent’s Base.el) in sync with their “logical” order (in this).

ExampleAdapted code snippet from the sample To-Do application:
_repos: function (child, index) {
  index >= this.length - 1
    ? child.el.appendTo(this.el)
    : child.el.insertBefore(this.el.children()[index])
},

_repos() is called for newly nest’ed _children and those that have changed pos. It’s not called for removed children. The default implementation in Ordered does nothing.

resort() calls _repos() for all children (even if some didn’t change positions – this is hard to determine). Here, note Array’s forEach() implications: going in ascending order from child #0 to last, and if _repos() mutates children then _repos() is not called for some of them (so don’t mutate).

from plainstub

  • This method returns nothing.
  • It should not be called directly.

Defined in: sqimitive.js, line 5490

_sorter ( a, b, posB )

Modifiers: protected

The comparison function for determining desired child order.

Result Types
Types Notes
< 0 if a must go before b
> 0 if it must go after
0 if they can go in any order; it’s generally recommended to avoid 0 to ensure that re-sorting the same array doesn’t result in a different order of members

You want to override _sorter() if you are not happy with the default implementation. If you do, see indexFor() for the invocation format. Default implementation compares using pos (if given to nestEx() for an object) or key (if nesting using _defaultKey() then keys are _cid’s).

ExampleAdapted code snippet from the sample To-Do application that is using the value of the order option of a given child if no explicit pos was provided for it when nesting:
'=_sorter': function (sup, a, b, posB) {
  var posA = a.pos == null ? a.child.get('order') : a.pos
  return arguments.length == 2 ? posA
    : (posA > posB ? +1 : (posA < posB ? -1
        // If properties match - sort stably by unique and constant _cid's.
        : (a.child._cid - b.child._cid)))
},

a and b are objects in the _ordered format. During nestEx() one of them may be missing from _ordered but present in _children; during resort() both are present in _ordered. b is null on the first iteration (see indexFor()).

Note: if consumers of this object are using pos, make sure either they supply correct types or you normalize them in _sorter: number 10 is > 2 but string '10' is < '2'.

Defined in: sqimitive.js, lines 5436-5441 (6 lines) • Show code

_sorter: function (a, b, posB) {
  a = a.pos == null ? a.key : a.pos
  return arguments.length == 1 ? a
    // Not simply a - posB to work with non-numeric key/pos.
    : (a > posB ? +1 : (a < posB ? -1 : 0))
},
at ( index )

Returns detailed info about a child by its index in this collection.

Result Types
Types Notes
object Entry in the format of _ordered

With at() you can obtain a child’s key or pos. Use it from util functions like each() which provide you with an index in _ordered (or in the result of slice()).

Warning: at() returns the object from _ordered verbatim – do not change it, or shallow-copy before you do.

Unlike slice(), at() does not accept negative index.

Example
sqim.at(0)        //=> {child: Sqimitive, key: 'foo', pos: 3}
sqim.at(999)      //=> undefined (if length <= 999)
sqim.at(-1)       //=> always undefined

this.groupBy(function (sqim, i) { return this.at(i).pos })
  //=> {pos1value: [child, ...], value2: [child, ...]}

// If clone is a non-_owning Ordered collection, populate it from orig:
orig.each(function (child, i) {
  clone.nestEx(orig.at(i))
})

// Using nest() is possible but will discard pos and keys of children:
orig.each(function (child) {
  clone.nest(child)
})

Defined in: sqimitive.js, lines 5555-5557 (3 lines) • Show code

at: function (index) {
  return this._ordered[index]
},
nestEx ( options )

Ordered extends the inherited nestEx (typically Base.nestEx()) and handles new options keys:

Arguments
Name Types Notes
posany, optional The caller may explicitly specify new child’s position relative to other children; pos may be used by _sorter() (in the default implementation it is).
indexnumber Set on return to the actual position of the new child in _ordered; index can be given to at()
oldIndexnumber Only set on return if options.changed is unset.

If re-nesting the same child on the same key, Base’s nestEx() does nothing but Ordered’s nestEx updates the child’s position in _ordered if old and new options.pos are different (i.e. !_.isEqual()) and calls _repos(). options.changed remains false. Detect this situation by comparing index and oldIndex:

col.nestEx({child: sqim, pos: -123})   // new child, explicit pos
  //=> {..., changed: true, previous: null, index: 0}
col.nestEx({child: sqim, pos: 9000})   // existing child, changed pos
  //=> {..., changed: false, previous: sqim, index: 3, oldIndex: 0}

Defined in: sqimitive.js, line 5285

resort ( )

Re-sorts the entire collection from scratch.

Result Types
Types Notes
this

Call resort() if an option that affects the sort order (_sorter) was changed or if sorting was temporary disabled.

Calls _repos() on every member after sorting _ordered.

Example
Sqimitive.Base.extend({
  mixIns: [Sqimitive.Ordered],
  _opt: {invert: false},

  events: {
    '=_sorter': function (sup) {
      var res = sup(this, arguments)
      // Invert sort order based on the option's value:
      return this.get('invert') ? -res : res
    },

    change_invert: 'resort-',
  },
})
ExampleTemporary disabling automatic sorting is useful during mass-assignment (assignChildren()):
var hook = this.on('=_indexFor', Sqimitive.Core.stub)
try {
  this.assignChildren(data)
} finally {
  this.off(hook)
  this.resort()
}

Defined in: sqimitive.js, lines 5480-5488 (9 lines) • Show code

resort: function () {
  this._ordered.sort(function (a, b) {
    // function _sorter(a, b, posB) - obtain that posB (simulating first
    // iteration of indexFor()).
    return this._sorter(a, b, this._sorter(b))
  }.bind(this))
  this.each(this._repos, this)
  return this
},