Class in Sqimitive Base

class Sqimitive.Base extends Core
Implements what makes Sqimitive the Sqimitive – options (opt), children (chld) and filtering (util) on top of Core, which provides the f

Defined in: sqimitive.js, lines 2530-5218 (2689 lines)

Description (skip)

Implements what makes Sqimitive the Sqimitive – options (opt), children (chld) and filtering (util) on top of Core, which provides the fundamental event framework (evt).

If you work with DOM then look for Sqimitive\jQuery, which adds el and elEvents. If you want ordered children – use the Sqimitive\Ordered mixIn.

Example

It’s good practice to extend this class just once in your application and use the new base class everywhere else:

  • If Sqimitive’s class hierarchy or your needs change, you’d have to change just one reference to Base.
  • You’re likely to implement some application-specific application-wise logic sooner or later (such as logging) and it’s easily done if you have just one base class (of course, you should not ever change Sqimitive’s own prototypes).

var MyApp = {
  VERSION: '0.1',
}

MyApp.Sqimitive = Sqimitive.Base.extend()
// or, if your application is DOM-based:
MyApp.Sqimitive = Sqimitive.jQuery.extend()

// Now use MyApp.Sqimitive as a base class throughout your code:
MyApp.ClassOne = MyApp.Sqimitive.extend(...)
MyApp.ClassTwo = MyApp.Sqimitive.extend(...)

Traditional OOP-style inheritance is not the only form of functionality extension supported by Sqimitive. For multi-parent inheritance, aka mix-ins, aka traits and for generics (parametrized mix-ins) see mixIn().

Properties

_childClass

Modifiers: protected

Part of: tag_Nesting

from settable

May be set upon declaration via Core::extend() or mixIn and/or read/written directly on run-time.

Ensures _children contains instances of a certain class only.

Value Types
Types Notes
Object to disable type checking Array and string are indirect declaration-time references (classref).
Sqimitive.Base the default
array
string
Example
var MyToDoItem = Sqimitive.Base.extend()
var SpecialMyToDoItem = MyToDoItem.extend()

var MyToDoList = Sqimitive.Base.extend({
  _childClass: MyToDoItem,
})

;(MyToDoList).nest(new MyToDoItem)         // works (the _childClass)
;(MyToDoList).nest({})                     // throws an exception
;(MyToDoList).nest(new Sqimitive.Base)     // throws an exception
;(MyToDoList).nest(new SpecialMyToDoItem)  // works (subclass of _childClass)
ExampleDisabling the check with Object:
var MyToDoList = Sqimitive.Base.extend({
  _childClass: Object,
})

;(MyToDoList).nest(new MyToDoItem)         // works
;(MyToDoList).nest({})                     // works
;(MyToDoList).nest(new Sqimitive.Base)     // works
;(MyToDoList).nest(new SpecialMyToDoItem)  // works

Other notes:

  • Typically, the value of _childClass is a subclass of Sqimitive.Core (Core) since non-Sqimitives are unlikely to work properly as children. This is not checked though.
  • This constraint is enforced as long as children are added via nest(), etc. without direct manipulation of _children (which is highly discouraged anyway).
  • Changing _childClass on run-time affects only new nesting attempts (existing children are not validated).
  • _childClass is listed in _shareProps by default.

Indirect referencesclassref

It’s often convenient to give as an array or string to extend() or mixIn(). In this case init() resolves the value of _childClass to the actual class (this makes it a tad slower than giving a real class):

Members
Name Types Notes
arraylike [BaseObject, 'Sub.Class.Path'] Same as evaluating BaseClass.Sub.Class.Path
stringlike 'Sub.Class.Path' Relative to static properties of this
stringempty The class of this

Errors if no class was found.

ExampleIndirect references are useful for “forward type declaration” where the child class is defined after the collection’s class or appears later on run-time:
var MyToDo = {}

MyToDo.List = Sqimitive.Base.extend({
  _childClass: [MyToDo, 'Item'],
})

MyToDo.Item = Sqimitive.Base.extend()
Or for the conventional Sqimitive hierarchy of <Collection>.<Child>:
MyToDo.List = Sqimitive.Base.extend({
  // All declarations below are equivalent:
  _childClass: [MyToDo, 'List.Item'],
  _childClass: [MyToDo.List, 'Item'],
  _childClass: 'Item',
})

MyToDo.List.Item = Sqimitive.Base.extend()

alert(MyToDo.List.prototype._childClass)                   //=> 'Item'
alert((new MyToDo.List)._childClass == MyToDo.List.Item)   //=> true

Defined in: sqimitive.js, line 2831Show code

_childClass: null,
_childEvents

Modifiers: protected

Part of: tag_Nesting

from settable

May be set upon declaration via Core::extend() or mixIn and/or read/written directly on run-time.

Lists event names for automatic _forward’ing from children to the collection object.

Value Types
Types Notes
array ['-nest_', 'change', ...]

Whenever a new child is nest()ed, listens to these events on it, firing events on this with the same name but prefixed with a dot . (e.g. render → .render) and with the child’s object pushed in front of the event’s arguments. Think of this as of the usual ../../path notation in file systems where each dot means “one [parent] above”.

Nothing special is done to stop listening when a child is removed since the default unnested() implementation calls child.off(this) (see off).

Example
var MyList = Sqimitive.Base.extend({
  _childEvents: ['-change'],

  events: {
    '.-change': function (sqim, name) {
      alert('Option ' + name + ' is about to change on ' + sqim._cid)
    },
  },
})

Warning: don’t list unnest here – _parent will off() itself before that and never receive the notification. Use unnested instead. Using -unnest is also possible but in this case if an exception occurs during unnesting your handler won’t know this and will be called anyway, while the child is possibly left nested.

Other notes:

ExampleUsing _childEvents is identical to manually calling on() but more convenient. However, since it’s on() in disguise, you can use any event prefixes (evtpf), specify methods that will be automatically turned into events (evt), etc.
var Collection = Sqimitive.Base.extend({
  _childEvents: ['=click'],

  _opt: {
    enabled: true,
  },

  events: {
    '.=click': function (child, sup) {
      if (this.get('enabled')) {
        return sup(this, arguments)
      } else {
        console.error('Clicking is disabled! Stop playing, ' + child._cid)
      }
    },

    '-unnested': function (child) {
      child.off(this)
    },
  },
})

var Item = Sqimitive.Base.extend({
  click: function () { alert('Oh... feels good!') },
})

var col = new Collection
var item = new Item
item.click()    // alerts; click is a method
col.nest(item)
  // item.click is no longer the original method but the wrapping handler
item.click()    // alerts
col.set('enabled', false)
item.click()    // no more alerts
col.unlist(item)
item.click()    // alerts again; click is again the original method
ExampleYou can forward already forwarded events with multi-level nesting (children of children of your collection) the same way. The number of dots indicates the number of child instances prepended to event’s arguments:
var MyListGroup = Sqimitive.Base.extend({
  // Indicate this object nests MyList instances from the first
  // example.
  _childClass: MyList,

  // MyList forwards '-change' on its children as '.-change' on itself
  // so we can foward that event too on this grouping instance.
  // There's no limit - '....-change' is perfectly fine and works on
  // 4th nesting level. Each forward gets originating object pushed in
  // front so '..-change' gets MyList as first argument. '...-change'
  // would get (MyListGroup, MyList).
  _childEvents: ['.-change'],
})

// Listening to '-change' that occurred on a MyList child, with MyList
// being nested into MyListGroup.
;(new MyListGroup).on('..-change', function (myListGroup, myList) { ... })

And of course you can use the usual event prefixes (evtpf) on these already-forwarded events:

// Trigger event on this before other handlers of '.-change'.
_childEvents: ['-.+normalize'],

// ...

;(new MyListGroup)
  .on('.-.+normalize_caption', function (myListGroup, myList,
                                         currentResult, newValue) {
    return newValue.trim()
  })

Defined in: sqimitive.js, line 2963Show code

_childEvents: [],
_children

Modifiers: protected

Part of: tag_Nesting

Holds references to objects contained within this instance (“collection”).

Value Types
Types Notes
object {key: Sqimitive} Keys are arbitrary strings as given to nest (_parentKey’s if this is _owning) and values are the children themselves (objects).

You’re advised against accessing _children at all. Instead, use nested() and other methods.

Other notes:

  • Both _owning sqimitives and not list their children here. For non-_owning, _children keys naturally differ from _parentKey of their children.
  • This parent-child relationship is purely formal and doesn’t dictate any DOM or other structure (children can have their el’s outside of the parent’s node). Moreover, if _owning is unset then it doesn’t imply the reverse relationship (from children to their parent).
  • See the children overview (chld) for examples.

Defined in: sqimitive.js, line 2695Show code

_children: {},
_opt

Modifiers: protected

Part of: tag_Options

May be set upon declaration via Core::extend() or mixIn, read on run-time with get() and written with set().

List of “opt’ions” (public properties) of this instance.

Value Types
Types Notes
object {name: value} Keys are option names and values are anything, of arbitrary type.

On run-time, use get()/set() to access this data (both from within this class’ methods and from the outside – it’s a public interface).

When any option’s value is changed, ifSet:

  • Fires the normalize_OPT event to allow for value normalization and validation.
  • If no error occurred, changes the value in this._opt and fires change_OPT and change to notify the interested parties.
Example
var Parent = Sqimitive.Base.extend({
  _opt: {caption: 'unnamed'},

  events: {
    '+normalize_caption': function (res, s) {
      return s.trim()
    },

    change: function (optName, newValue) {
      alert(optName + '=' + newValue)
    },
  },
})

var Child = Parent.extend({
  _opt: {body: 'unbodied'},

  events: {
    change_caption: function (newValue) {
      alert('New caption = ' + newValue)
    },
  },
})

var child = new Child
  // child._opt = {caption: 'unnamed', body: 'unbodied'}

child.set('caption', 'Foo')
  // alerts: New caption = foo   - Child's change_caption handler
  // alerts: caption=foo         - Parent's change handler

child.set('body', 'Bar')
  // alerts: body=foo            - Parent's change handler

child.set('caption', ' S P A C E ')
  // _opt.caption  = 'S P A C E'
child.set('body',    ' S P A C E ')
  // _opt.body     = ' S P A C E '

When given to extend(), the _opt key specifies initial options (their defaults) for new instances.

from inMergeProps

This property is listed in _mergeProps by default so subclasses defining it add to their parents’ objects instead of overwriting them entirely (but identical keys are still overwritten).

It’s highly advised to access _opt’s values only via get()/set() - performance benefits of direct access are questionable (especially when those methods are not events/firer’s, i.e. almost always) while lack of normalize_OPT and others often cause bugs.

from inBB

In Backbone…

In Backbone terms “options” are called bb:Model-attributes.

Defined in: sqimitive.js, line 2606Show code

_opt: {},
_owning

Modifiers: protected

Part of: tag_Nesting

from setOnDecl

May only be set upon declaration via Core::extend() or mixIn.

Specifies if this object manages its children or not (by default it does).

Value Types
Types Notes
true the default “Managing” (owning) parent. All of its children know who owns them (_parent) and under which key (_parentKey). It makes sure the children only ever have one parent – itself, and that they cannot duplicate in its _children. Essentially makes a bi-directional tree.
false Unmanaged (non-owning) parent. Acts as a simple collection. Imposes no hierarchy onto its children, who do not even know that they are listed here and may duplicate in this own _children (same child under different keys).

_owning only affects a subset of features (nest(), etc.). Most features – filtering (util), _forward’ing _childEvents, etc. can be used in both modes.

See chld children overview for more details.

Example
var Owning = Sqimitive.Base.extend()

var NonOwning = Sqimitive.Base.extend({
  _owning: false,
})

var child = new Sqimitive.Base
var owning = new Owning
var nonOwning = new NonOwning
owning.nest('key', child)
nonOwning.nest('key2', child)
alert(child._parent == owning)             //=> true
alert(child._parentKey)                    //=> 'key'
alert(owning.nested('key') == child)       //=> true
alert(nonOwning.nested('key'))             //=> undefined
alert(nonOwning.nested('key2') == child)   //=> true

var owning2 = new Owning
owning2.nest('key3', child)
  // child._parent == owning2, _parentKey == 'key3'
alert(owning.nested('key'))                //=> undefined

Defined in: sqimitive.js, line 2743Show code

_owning: true,
_parent

Modifiers: protected

Part of: tag_Nesting

from readOnly

May only be read, not changed.

Holds the reference to the Sqimitive that owns object, or null.

You can read this property from inside methods of the same class. Changing it (from any context) is highly discouraged because it’s easy to break object integrity – use nest(), unnest() and others.

parentAndKeyNon-_owning sqimitives never change _parent and _parentKey of their _children.

Example
Sqimitive.Base.extends({
  events: {
    owned: function () {
      // CORRECT: reading _parent from within this class' context:
      alert('New parent is ' + this._parent._cid)
      alert('My key under my parent is ' + this._parentKey)
      // WRONG: do not change _parent:
      this._parent = null
    },
  },
})

// WRONG: do not access _parent from the outside:
alert(sqim._parent._cid)

Defined in: sqimitive.js, line 2644Show code

_parent: null,
_parentKey

Modifiers: protected

Part of: tag_Nesting

from readOnly

May only be read, not changed.

Holds the key under which this instance is listed in its _parent’s list of _children, or null.

Value Types
Types Notes
string if owned If null then _parent is also null
null if not

from parentAndKey

Non-_owning sqimitives never change _parent and _parentKey of their _children.

Example
Sqimitive.Base.extends({
  events: {
    owned: function () {
      // CORRECT: reading _parent from within this class' context:
      alert('New parent is ' + this._parent._cid)
      alert('My key under my parent is ' + this._parentKey)
      // WRONG: do not change _parent:
      this._parent = null
    },
  },
})

// WRONG: do not access _parent from the outside:
alert(sqim._parent._cid)
ExampleThis key can be given to nested() and others:
Sqimitive.Base.extends({
  pull: function () {
    this._parent.unlist(this._parentKey)
      // this is a contrived example since this.remove() does
      // exactly the same
  },
})

Defined in: sqimitive.js, line 2669Show code

_parentKey: null,
_respToOpt

Modifiers: protected

Part of: tag_Options

from settable

May be set upon declaration via Core::extend() or mixIn and/or read/written directly on run-time.

Specifies rules for transforming an external input object (e.g. an API response) into _opt’ions for assignResp().

Value Types
Types Notes
object {respKey: optValue}.

_respToOpt’s keys are input object’s keys (except the special '') and values are one of the following (optValue):

Members
Name Types Notes
false Skip input item regardless of options.onlyDefined as given to assignResp().
true Assign input item’s value to the option named respKey (i.e. keys of the option and the input object are the same).
string Assign input item’s value to the option by this name.
function(respValue, key, resp, options) Input item transformation. This function is called in this context and must return ['optToSet', value].

respKey only determines the respValue given to this function; the latter can access the entire input object (resp). The (new) option’s name is retrieved from the returned array (optToSet), not from respKey.

The key argument equals respKey (i.e. is the key’s name under which the function is listed in _respToOpt), options is the object given to assignResp().

If optToSet is false then the input item is skipped and value is unused, otherwise it’s the option name to set value to (value can be of any type). It’s similar to calling set() but more declarative and future-proof.

The special key '' (empty string) must be a function (resp, options) returning object {optToSet: value} or null (equivalent to {}). It’s called in the beginning of assignResp() and, as other keys, if resp has an empty string key – check arguments.length if you expect it. This key is useful for unserializing fields that match multiple options or vice-versa:

Sqimitive.Base.extend({
  _respToOpt: {
    '': function (resp) {
      return {personsName: resp.firstName + ' ' + resp.secondName}
        // same as set('personsName', '<firstName> <secondName>')
    }

    // The opposite of the above - if there are two options in one
    // input key.
    '': function (resp) {
      var name = resp.personsName.split(' ')
      return {firstName: name[0], secondName: name[1]}
    }
  },
})

Example
Sqimitive.Base.extend({
  _respToOpt: {setAsIs: true, setAsFoo: 'Foo'},
})

// ...

sqim.assignResp({setAsIs: 123, setAsFoo: 'xyz'})
  // sqim._opt is {setAsIs: 123, Foo: 'xyz'}

// Equivalent to:
sqim.set('setAsIs', 123)
sqim.set('Foo', 'xyz')
ExampleUsing transformation functions:
Sqimitive.Base.extend({
  _respToOpt: {
    ignore: function () { return [false] },
    rename: function (value) { return ['foo', value] },
    date: function (value) { return ['date', new Date(value)] },
    merge: function (v, k, resp, options) {
      // v = 5, k = 'merge', resp = {unlisted: ...}, options = {}
      return ['bar', resp.a.concat(resp.b)
    },
    setUndefined: function () { return ['baz', undefined] },
  }
})

// ...

sqim.assignResp({unlisted: 1, ignore: 2, rename: 3, date: 4,
                 merge: 5, a: 6, b: 7, setUndefined: 8})
  // sqim._opt is {foo: 2, date: Date, bar: [6, 7], baz: undefined}

from inMergeProps

This property is listed in _mergeProps by default so subclasses defining it add to their parents’ objects instead of overwriting them entirely (but identical keys are still overwritten).

Other notes:

  • Options are assigned with set() so normalization and change events take place as usual.
  • Missing keys may or may not become options by the same name – this depends on the options.onlyDefined flag of assignResp().

Defined in: sqimitive.js, line 3068Show code

_respToOpt: {},
el

Part of: tag_Lifecycle

from settable

May be set upon declaration via Core::extend() or mixIn and/or read/written directly on run-time.

“Element” – an external “form” of this Sqimitive (such as a DOM node).

Even though this is technically a public read/write property, it’s usually best not to change it on run-time (after initialization) or at least not from the outside context.

elstubSqimitive\Base doesn’t define any element-related functionality but only provides stubs for several common fields (el, render(), remove(), etc.) for standardized extension. See Sqimitive\jQuery.el for one such subclass.

Defined in: sqimitive.js, line 3086Show code

el: false,
elEvents

Part of: tag_Lifecycle

from setOnDecl

May only be set upon declaration via Core::extend() or mixIn.

Declares event listeners for el, bound automatically by attach().

Value Types
Types Notes
object {event: func}

elEvents’ format is consistent among Base’s subclasses and follows Backbone’s bb:Model-events: keys are event references of form event[.ns][ .sel #ector] and values are strings (expandFunc()) or functions, called as function (nativeEventObject) (see argDanger).

from inMergeProps

This property is listed in _mergeProps by default so subclasses defining it add to their parents’ objects instead of overwriting them entirely (but identical keys are still overwritten).

The .ns part is ignored but can be used to create unique keys for the purpose of inheritance. By convention, the class’ own handlers don’t have ns while mixIn’s do.

from es6thiswarn

Warning: avoid using ES6 arrow functions as handlers due to their fixed this (es6this).

This property is not advised to change on run-time since it’s usually unpredictable when it will take effect. For example, Sqimitive\jQuery.attach() only binds events when nesting el into a new parent node:

sqim.attach()
sqim.elEvents['click'] = function () { alert('foo') }
sqim.attach()    // will do nothing and 'click' will not be bound
sqim.el.remove()
sqim.attach()    // now events are bound

from elstub

Sqimitive\Base doesn’t define any element-related functionality but only provides stubs for several common fields (el, render(), remove(), etc.) for standardized extension. See Sqimitive\jQuery.el for one such subclass.

Defined in: sqimitive.js, line 3124Show code

elEvents: {},
length

from readOnly

May only be read, not changed.

The number of _children nest()ed into this object.

Just like $('p').length or bb:Collection-length.

See also slice().

Defined in: sqimitive.js, line 3133Show code

length: 0,

Methods

_ ( )

Modifiers: protected

Part of: tag_Utilities

Returns wrapped _children array for chaining with your utility library.

Chaining is useful for transforming the value (array of children) several times. Call value() to obtain the final value. If you’re only doing a single transformation – just call it directly on this since most of them are exposed as methods (see util).

Note: Underscore (see un:chain()) and LoDash support chaining but NoDash doesn’t and _() will error.

The reference to the utility library itself (the global _ object) is accessible via Sqimitive._.

Example
col._().filter(...).sort(...).value()    //=> array of children

// Same as:
var _ = Sqimitive._
_.chain(col.toArray()).filter(...).sort(...).value()

// Same as:
_.sort(col.filter(...), ...)

Defined in: sqimitive.js, lines 4542-4544 (3 lines) • Show code

_: function () {
  return _.chain(this.slice())
},
_defaultKey ( sqim )

Modifiers: protected

Part of: tag_Nesting

Return an automatic (implied) key for the given to-be child.

Result Types
Types Notes
string
number

_defaultKey() is called by nest() and assignChildren() to generate a key for the new child (sqim) that is about to be nested into this instance.

Default Base’s implementation returns sqim’s _cid.

Make sure the returned value is constant for a given child so that if nesting the same child with no explicit key several times it isn’t constantly re-nested due to a different key (see nestEx()).

Warning: if you want to index children by some “ID” attribute (like Backbone’s bb:Collection does) note that _parentKey will not be automatically updated if that ID attribute changes. You should track it and update the collection. For example (see _childEvents):

var MyCollection = Sqimitive.Base.extend({
  _childEvents: ['.change_id'],

  events: {
    // '.change_id' will only occur for "models" which _parent is this.
    // They will be re-nested with the key expanding to
    // this._defaultKey(sqim), basically doing nest(newID, sqim) - and
    // since this is an _owning collection, nest() will cause sqim to
    // be unnest()'ed and then nested with the new key.
    '.change_id': function (sqim) { this.nest(sqim) },

    // Same but shorter using a masked function reference:
    '.change_id': 'nest.',
  },

  _defaultKey: function (sqim) {
    return sqim.get('id')
  },
})

from unordered

Attention: JavaScript objects are unordered. See the description of Ordered.

If you want to not just keep some attribute synced with _parentKey but also maintain a specific order based on it – use the Sqimitive\Ordered mixIn.

from inBB

In Backbone…

In Backbone, bb:Model-idAttribute names the property used for a similar purpose but in Sqimitive it’s a function allowing flexible automatic naming.

Defined in: sqimitive.js, lines 4112-4114 (3 lines) • Show code

_defaultKey: function (sqim) {
  return sqim._cid
},
_forward ( prefix, events, sqim )

Modifiers: protected

Part of: tag_Events

Forwards events occurring on sqim to this.

Forwarding is done by firing “prefix + event_name” on this (the object on which _forward() is called) with sqim pushed in front of original event’s arguments. event_name is a complete reference string as given to parseEvent(), e.g. =foo_.

_forward() is used to set up _childEvents, with the prefix of

..

Example
d._forward('.', ['render'], o)
d.on('.render', function (o) { alert(o._cid) })
 // now whenever 'render' is fired on o, '.render' is fired on d where
 // it shows the _cid of the object where the original 'render'
 // occurred
Example
destination._forward('dlg-', ['change', '-render'], origin)

This example fires dlg-change and dlg--render events on destination (a Sqimitive) whenever change and render are fired on origin. -render simply means the forwarded events occur on destination before other handlers of origin are executed, as per evtpf.

Defined in: sqimitive.js, lines 4187-4197 (11 lines) • Show code

_forward: function (prefix, events, sqim) {
  _.each(events.concat(), function (event) {
    var name = prefix + event
    sqim.on(event, function () {
      Array.prototype.unshift.call(arguments, sqim)
      return this.fire(name, arguments)
    }, this)
  }, this)

  return sqim
},
assignChildren ( resp [, options] )

Part of: tag_Nesting

Unserializes children: updates own _children based on data of arbitrary format.

Merges external “response” resp into _children by updating existing nested sqimitives and adding new and/or removing unlisted ones. resp is a list of data objects, one object per every child:

resp = [ {caption: 'foo', body: 'bar'}, {caption: 'bazz'}, ... ]
         ^^^ the first child's data ^^  ^ the second child

options keys used by this method:

Arguments
Name Types Notes
classFuncfunction returning constructor (class) for new children; receives response data object
null/omitted use _childClass of this
eqFuncnull/omitted if keepMissing then do nothing, else remove all children prior to assignment
string option name for get()
function (child, opt) returning true when the given child is the “same” as one of resp data objects and should be kept and have its _opt updated without creating a new child for that opt
keepMissingtrue to keep children “not present” in resp (as reported by eqFunc) unchanged, false to ensure all _children left after assignChildren() were present in resp and that others were unlist()ed from self
keyFuncnull/omitted use _defaultKey() of this (returns _cid by default)
function returning key (string or number); receives a constructed Sqimitive before it’s nest’ed
Result Types
Types Notes
array [nested, unlisted] nested is an array of nestEx() return values, unlisted is an object of child Sqimitives (by their keys in this) that were not found in resp.

If options.eqFunc was unset then unlisted is either always {} (options.keepMissing set) or the copy of _children before assignChildren().

assignchreassignChildren() “unserializes” own _children objects while assignResp() “unserializes” own _opt’ions (“attributes”).

Example
var MyList = Sqimitive.Base.extend()

var MyItem = Sqimitive.Base.extend({
  _opt: {foo: ''},
})

var list = new MyList
var item1 = new MyItem({foo: 'existing'})

list.nest(item1)
var resp = [{foo: 'incoming#1'}, {foo: 'existing'}]
list.assignChildren(resp)
  //=> [ [item with 'foo' of 'incoming#1', item with 'foo' of 'existing'], {item1} ]
  // item1 was removed from list and the latter got two new children
  // with 'foo' of 'incoming#1' and 'existing' (the latter has
  // identical _opt with the removed item but a new object was
  // nevertheless created and nested)

// Let's restore the list.
list.invoke('remove')
list.nest(item1)

list.assignChildren(resp, {eqFunc: function (sqim, opt) {
  return sqim.get('foo') == opt.foo
}})
  //=> [ [item with 'foo' of 'incoming#1'], {} ]
  // Now item1 wasn't removed because the function we've passed
  // compared its foo option with resp's foo value and returned true
  // to indicate that existing child represents the same entity as the
  // incoming data object. In addition to item1, list got one new item
  // with foo of 'incoming#1'.

// We can use this short form of eqFunc if we're simply matching some
// option with a field by the same name in resp's objects:
list.assignChildren(resp, {eqFunc: 'foo'})
Example
list.assignChildren([], {keepMissing: true})
  //=> [ [], {} ]
  // nothing done as there were no items in resp

list.assignChildren([])
  //=> [ [], {old list._children} ]
  // the list was cleared as keepMissing defaults to false
Example
assignChildren([ {key: 'foo'}, {key: 'bar'} ], {eqFunc: 'key'})
  // adds/updates 2 children with _opt = {key: 'foo'} and {key: 'bar'};
  // "same" sqimitives are those having the same value for the 'key'
  // option and data object's 'key' field

assignChildren([ {key: 'foo'}, {key: 'bar'} ],
               {eqFunc: function (sqim, opt) { return sqim.get('key') == opt.key }})
  // identical to the above

assignChildren({ foosh: {key: 'foo'}, barrsh: {key: 'bar'} }, {eqFunc: 'key'})
  // identical to the above (resp is an object, keys ignored)

assignChildren({ foosh: {key: 'foo'}, barrsh: {key: 'bar'} },
               {eqFunc: 'key', keepMissing: true})
  // identical to the above but keeps old children

assignChildren([ {key: 'foo'}, {key: 'bar'} ])
  // similar but always removes all nested sqimitives and adds 2 new
  // ones

assignChildren(..., {onlyDefined: true, forceFire: true})
  // passes onlyDefined to assignResp() and forceFire to set()

The operation

First, assignChildren() determines which resp objects are new and which are “serialized” forms of one of the existing _children by calling options.eqFunc with combinations of (child, obj) for every child and data object. eqFunc should return true exactly for none or one such combination. If options.eqFunc is a string then it’s assumed that children are identified by an _opt’ion by this name (like “id”), mapping to data objects with the same value of this property:

var resp = [ {id: 1, caption: 'foo'}, {id: 2, caption: 'bar'} ]
var child = new Sqimitive.Base({id: 1, caption: 'quux'})
col.nest(child)
col.assignChildren(resp)
  // col now has 2 children: id=1 'foo', id=2 'bar'
  // but the first is a new object, not our child object even though
  // it's the same entity (given the same ID) - we should preserve it
  // since it may have event listeners set up by other objects

// But if we'd have done this:
col.assignChildren(resp, {eqFunc: 'id'})
  // col would have 2 children with the same _opt values
  // but the first would be the same child as nested in the beginning
  // so its event listeners would be preserved, just 'caption' updated

// String eqFunc is the same as:
col.assignChildren(resp, {eqFunc: function (child, obj) {
  return child.get('id') == obj.id
}})

To recap: children keys (_parentKey) are not used when linking _children and resp objects unless you code this in your options.eqFunc.

If options.eqFunc is unset then all existing children are unlist()ed unless options.keepMissing is set – in this case they are preserved and for each item in resp a new child is nested. However, on duplicate keys (options.keyFunc) only the last child is kept and others are removed as per the usual behaviour of nest() (assignChildren() doesn’t handle this specially and such “removed-by-nesting” children are not reflected in the returned array).

Then, for data objects mapped to an existing child assignChildren() calls assignResp() on that child giving it the mapped data object to essentially update it. For unmapped, it creates a new Sqimitive as options.classFunc, calls assignResp() and nest’s it under the key of options.keyFunc.

Finally, if options.keepMissing is unset assignChildren() removes all children which were neither updated not created during this call. If it’s set then they are just left unchanged as _children of this.

Propagation of options

assignopropassignChildren() and assignResp() call/fire several methods/events (evt), all of which receive the same options object. This allows changing those methods’ options as well as propagating data back from them to the original caller (see optpropag).

In particular, assignChildren() calls assignResp(), set()/ifSet(), nestEx() firing normalize_OPT, change_OPT and change, as well as nestEx-produced events (however, for nestEx() a shallow-copy of options is given).

sqim.assignChildren({a: 1, b: 2}, {schema: api3, forceFire: true})
  // passes schema to assignResp() instead of _respToOpt and
  // passes forceFire to set() to always fire change events

Additionally, this method sets options.assignChildren to true so you can determine when an option is being set as a result of this method call:

Sqimitive.Base.extend({
  events: {
    change_caption: function (value, options) {
      // Don't refresh the view when unserializing.
      if (!options.assignChildren) { this.render() }
    },
  },
})

sqim.assignChildren([{caption: 'foo'}, ...])
sqim.render()    // call it once after finishing the update.

Format of resp

resp is either an array of objects or an “object of objects” – then it’s converted using un:values() ignoring the keys.

Sqimitive prior to v1.2 allowed resp to be an object with the data key to integrate with Python’s Flask framework but it’s no longer supported – see flask.palletsprojects.com/en/1.1.x/security/.

from unordered

Attention: JavaScript objects are unordered. See the description of Ordered.

assignChildren() may process resp and nest/update children in any order. If you need specific order then pass resp as an array instead of object.

Other notes

  • There’s no assignChildren() version that takes ready-made objects (like Backbone’s bb:Collection-set()) because many questions arise: do you need to keep old duplicate children or replace them with supplied objects and what to do with event listeners on the old children if you do replace them, or with listeners on the new if you don’t; what to consider “duplicate” (some ID attribute? exact same object?), do you want to keep old options, etc. etc. Instead of making Sqimitive figure or enforce them on you it just lets you implement exactly what you need.
  • assignnameThis method is named “assign” to emphasize the fact that data may undergo transformations before being assumed by the sqimitive.

Defined in: sqimitive.js, lines 4830-4878 (49 lines) • Show code

assignChildren: function (resp, options) {
  options || (options = {})
  options.assignChildren = true
  var eqFunc = options.eqFunc
  var keyFunc = options.keyFunc || this._defaultKey

  if (eqFunc == null) {
    var retUnlisted = options.keepMissing ? {} : this.nested()
    options.keepMissing || this.each(this.unlist, this)
  } else if (typeof eqFunc != 'function') {
    var field = eqFunc
    eqFunc = function (sqim, opt) { return opt && sqim.get(field) == opt[field] }
  }

  _.isArray(resp) || (resp = _.values(resp))
  var toRemove = eqFunc ? this.nested() : {}
  var nested = []

  for (var i = 0; i < resp.length; i++) {
    var found = false

    for (var key in toRemove) {
      if (eqFunc.call(this, toRemove[key], resp[i])) {
        toRemove[key].assignResp(resp[i], options)
        delete toRemove[key]
        found = true
        break
      }
    }

    if (!found) {
      // It's hard/impossible to tell apart a regular function and a
      // constructor so if you want to specify a class, create a function
      // that just returns that class. In ES6 that's simply () => MyClass.
      var cls = options.classFunc
        ? options.classFunc.call(this, resp[i]) : this._childClass
      var child = (new cls).assignResp(resp[i], options)
      var res = this.nestEx(_.extend({}, options, {
        key:    keyFunc.call(this, child),
        child:  child,
      }))
      // Can't use _parentKey if not _owning.
      nested.push(res)
    }
  }

  options.keepMissing || _.each(toRemove, this.unlist, this)
  return [nested, retUnlisted /* when !eqFunc */ || toRemove]
},
assignResp ( resp [, options] )

Part of: tag_Options

Unserializes options: updates own _opt based on a data object of arbitrary format.

assignResp() calls options.schema’s empty key and then set()s own _opt based on transformation rules (options.schema) for the external input resp (e.g. an API response). resp is an object where one member usually represents one option (but this is not a requirement).

options keys used by this method:

Arguments
Name Types Notes
schemanull/omitted to use _respToOpt
object in _respToOpt format
onlyDefinednull/omitted to call set(key) for every key in resp not listed in schema
true to ignore such keys
Result Types
Types Notes
this

from assignchre

assignChildren() “unserializes” own _children objects while assignResp() “unserializes” own _opt’ions (“attributes”).

ExampleIn the simplest case when _respToOpt is not overridden (i.e. is {}) and onlyDefined is unset assignResp() acts as a “multi-set()”:
sqim.assignResp({date: '2000-01-01', id_key: '123'})
  // without _respToOpt, schema and onlyDefined is equivalent to:
  // sqim.set('date', '2000-01-01').set('id_key', '123')

// Works the same regardless of sqim's _respToOpt.
sqim.assignResp({date: '2000-01-01', id_key: '123'}, {schema: {}})
ExampleassignrespvsUsing assignResp() in conjunction with _respToOpt to “blend” into this object a typical JSON response from a backend:
var MyModel = Sqimitive.Base.extend({
  _opt: {
    date: new Date(0),
    id_key: 0,
  },

  _respToOpt: {
    // Create a Date object from the incoming value:
    date: function (value, key) {
      return [key, new Date(value)]
    },

    // Pass the value through to _opt as is.
    id_key: true,
  },

  events: {
    change_id_key: 'render',

    '=normalize_id_key': function (sup, value) {
      value = parseInt(value)
      if (isNaN(value)) { throw new Error('What kind of ID is that?') }
      return value
    },
  },
})

var sqim = new MyModel

// Since it's using regular set() to assign new values all the usual
// normalize/change events are fired. In particular, MyModel's
// normalize_id_key and then change_id_key are called. As a result
// _opt setting happens exactly the same as if it was done with the
// usual set() from the outside.
sqim.assignResp({date: '2000-01-01', id_key: '123', bazzz: 'Ouch!'})

// Now sqim.get() is {date: new Date('2000-01-01'), id_key: 123, bazzz: 'Ouch!'}.
// date was turned into a Date object thanks to the transformation
// function in _respToOpt.
// id_key was turned into a number thanks to normalize_id_key.
// bazzz was assigned too because we didn't pass {onlyDefined: true}.
ExampleIf you have several API routes with different formats but logically the same data (so they unserialize to the same object), pass options.schema without changing _respToOpt on run-time:
sqim.assignResp(apiResp1, {schema: {id: 'id_key'}})
  // set('id_key', apiResp1.id) and set other keys as is

var schema = _.extend({extra: s => ['info', JSON.parse(s)]}, sqim._respToOpt)
sqim.assignResp(apiResp2, {schema})
  // set('extra', JSON.parse(apiResp2.info)) and
  // handle other keys according to sqim._respToOpt

schema is not meant for changing resp’s shape, e.g. from array [['opt', 'name'], ...] to object {opt: 'name', ...} – declare a specialized public method instead of making your consumers pass schema or know how to treat the data:

assignBillingResponse: function (resp) {
  this.assignResp(_.object(resp), {
    schema: this._billingRespToOpt,
    onlyDefined: true,
  })
}

Having options.onlyDefined unset is similar to having all keys in resp missing from _respToOpt listed there with the value of true:

Sqimitive.Base.extend({
  _respToOpt: {a: false, b: true},
})

sqim.assignResp({a: 1, b: 2, c: 3}, {onlyDefined: true})
  // _opt is {b: 2}

sqim.assignResp({a: 1, b: 2, c: 3})
  // _opt is {b: 2, c: 3}
  // as if _respToOpt had also {c: true}

ExampleOverride assignResp() to force skipping resp keys not defined in _respToOpt – handy if this method is used directly by API consumers, i.e. there’s no specialized “unserialize()”:
var MySqimitive = Sqimitive.Base.extend({
  events: {
    '=assignResp': function (sup, resp, options) {
      return sup(this, [resp, _.extend(options || {}, {onlyDefined: true})])
    },
  },
})

Propagation of options

from assignoprop

assignChildren() and assignResp() call/fire several methods/events (evt), all of which receive the same options object. This allows changing those methods’ options as well as propagating data back from them to the original caller (see optpropag).

In particular, assignResp() calls set()/ifSet() firing normalize_OPT, change_OPT and change.

sqim.assignResp({a: 1, b: 2}, {forceFire: true})
  // passes forceFire to set() causing change events of a and/or b
  // to fire even if they had the same values before assignResp()

Additionally, this method sets options.assignResp to true so you can determine when an option is being set as a result of this method call (see assignoprop for an example). If called by assignChildren(), options has both assignChildren and assignResp set.

normalize/change vs _respToOpt

You may notice that =normalize_id_key and _respToOpt’s date in example assignrespvs fulfill a similar purpose. That example could have been written like this:

var MyModel = Sqimitive.Base.extend({
  _respToOpt: {
    date: function (value, key) {
      return [key, new Date(value)]
    },

    id_key: function (value, key) {
      value = parseInt(value)
      if (isNaN(value)) { throw new Error('What kind of ID is that?') }
      return [key, value]
    },
  },

  events: {
    change_id_key: 'render',
    // No '=normalize_id_key', moved to _respToOpt.
  },
})
Or like this:
var MyModel = Sqimitive.Base.extend({
  _respToOpt: {
    // No date, became normalize_date.
  },

  events: {
    change_id_key: 'render',

    '=normalize_date': function (sup, value) {
      return new Date(value)
    },

    '=normalize_id_key': function (sup, value) {
      value = parseInt(value)
      if (isNaN(value)) { throw new Error('What kind of ID is that?') }
      return value
    },
  },
})

Indeed, normalize_OPT and others are fired by ifSet() and they occur during assignResp() because the latter is calling set() internally. However, _respToOpt is only used by assignResp() so both of the above examples have issues:

  • If there is no =normalize_id_key then assignResp() works as expected while set('id_key', 'zabzab') doesn’t trigger an error.
  • If there are no _respToOpt rules then assignResp() again works as expected but set('date', new Date) results in _opt.date becoming new Date(new Date).

Additionally, assignResp() allows different “unserialization profiles” by passing rules in options.schema rather than defining them in _respToOpt.

from unordered

Attention: JavaScript objects are unordered. See the description of Ordered.

assignResp() may process resp and call set() in any order.

Other notes

from assignname

  • This method is named “assign” to emphasize the fact that data may undergo transformations before being assumed by the sqimitive.

Defined in: sqimitive.js, lines 5100-5124 (25 lines) • Show code

assignResp: function (resp, options) {
  options || (options = {})
  options.assignResp = true

  var schema = options.schema || this._respToOpt
  var set = schema[''] && schema[''].call(this, resp, options)
  _.each(set || {}, function (v, k) { this.set(k, v, options) }, this)

  for (var key in resp) {
    var value = resp[key]
    var opt = schema[key]
    opt === undefined && (opt = !options.onlyDefined)
    opt === true && (opt = key)

    if (typeof opt == 'function') {
      opt = opt.call(this, value, key, resp, options) || []
      value = opt[1]
      opt = opt[0]
    }

    opt == false || this.set(opt, value, options)
  }

  return this
},
attach ( parent )

Part of: tag_Lifecycle

Attach the object to its parent and bind “external” event handlers (elEvents).

from renderAttach

Result Types
Types Notes
baseAttachobject this

Here’s a typical Sqimitive object lifecycle:

  • construct with new: new Class({opt...})
  • attach() (to DOM, etc.), for members – when nested to a collection
  • render() when required for user to see it first time
  • render() again when something changes that affects the visual presentation (usually in response to a change of some _opt’ion)
  • finally, remove()

Complete re-rendering on change is simple and adequate for many simple sqimitives but if it’s heavy, it’s customary to perform a partial update using method(s) named update(). There are no such methods by default but see vw for an example.

Example
var Label = Sqimitive.jQuery.extend({
  _opt: {caption: 'untitled'},

  events: {
    render: function () {
      this.el.text(this.get('caption'))
    },

    change_caption: 'render',
  },
})
                                    // Lifecycle:
;(new Label)                        // 1) construct
  .attach('body')                   // 2) attach
  .render()                         // 3) render
  .set('caption', 'render again!')  // 4) update

from elstub

Sqimitive\Base doesn’t define any element-related functionality but only provides stubs for several common fields (el, render(), remove(), etc.) for standardized extension. See Sqimitive\jQuery.el for one such subclass.

Base’s attach() does nothing. You may be looking for Sqimitive\jQuery.attach().

Defined in: sqimitive.js, lines 3346-3348 (3 lines) • Show code

attach: function (parent) {
  return this
},
bubble ( event, args, fireSelf )

Part of: tag_Events

Triggers an event on every parent, recursively.

Sends event with arguments args upstream – to this._parent, to that parent’s parent and so on. Does nothing if _parent is unset.

Result Types
Types Notes
bubsinthis

If fireSelf is true then first fires event on self (by default it doesn’t).

bubble() sends the event upstream while sink() sends it downstream. However, the first walks _owning sqimitives (since it’s using _parent) while the second works for any mode (since it’s using _children).

Since both call methods, not necessary events, you can “recursively” call methods as well (which may or may not be events, see evt).

bubble() is very much like DOM’s event bubbling except that it happens on sqimitives, not on their el’s.

While it should not be abused because it makes the execution flow less obvious (much like goto or longjmp()), it’s indispensible for propagating generic signals like errors and logs to whoever is on top.

Example
// Causes self and all parent sqimitives to be rendered:
sqim.bubble('render', [], true)

// Recursively calls invoke('render') on all parents which results
// in them calling attach() on children (given the default behaviour
// of attach()):
sqim.bubble('invoke', ['attach'])

// We can use it to obtain data from "some" (unspecified) _owning
// object:
var out = {}
sqim.bubble('giveMeData', [out])
alert(out.data)
// The above will work if any parent has a handler like this:
parent.on('giveMeData', function (out) { out.data = 'here goes' })

Defined in: sqimitive.js, lines 5175-5179 (5 lines) • Show code

bubble: function (event, args, fireSelf) {
  fireSelf && this[event] && this[event].apply(this, args)
  this._parent && this._parent.bubble(event, args, true)
  return this
},
change ( opt, value, old, options )

Part of: tag_Options

Called to notify that the value of some _opt’ion has changed.

from changeAndOpt

ifSet normalizes (normalize_OPT) new option’s value (value) and, if it’s different from the current one (old) fires an event named “change_” + the option’s name (change_OPT), then fires change.

New value is already written to this._opt.OPT by the time change events occur.

from normopt

options is the object originally given to set() or ifSet().

Example
var MyClass = Sqimitive.Base.extend({
  _opt: {
    caption: '',
  },

  events: {
    // When _opt.caption is changed - call render() to update the
    // looks.
    change_caption: 'render', //¹
  },
})

¹ Be aware of argDanger as your handler might care for extra arguments. If it does then use a masked reference (see masker()):

events: {
  // Pass no arguments thanks to '-':
  change_caption: 'updateCaption-',
},

Example
var MyClass = Sqimitive.jQuery.extend({
  el: {tag: 'form'},

  _opt: {
    caption: '',
    body: '',
  },

  events: {
    // When any option is changed - update the corresponding <input>.
    change: function (name, value) {
      // change_OPT doesn't receive option's name as the first
      // argument.
      //
      // value is used as is - if clean-up is required then
      //  normalize_OPT events must be handled.
      this.$('[name="' + name + '"]').val(value)
    },
  },
})
ExamplesetoptpropagPropagation of options (optpropag):
sqim.on('change_foo', function (value, old, options) {
  if (!options.noSync) {
    $.ajax({
      url: 'update',
      type: 'POST',
      data: this.get(),
    })
  }
})

// The handler above performs an AJAX request:
sqim.ifSet('foo', 123)

// But not now (set() passes options through to ifSet()):
sqim.set('foo', 123, {noSync: true})

// assignResp() passes options to set() so no request is performed too:
sqim.assignResp({foo: 123}, {noSync: true})

Defined in: sqimitive.js, line 3710

change_OPT ( value, old, options )

Part of: tag_Options

Called to notify that the value of _opt’ion named OPT has changed.

changeAndOptifSet normalizes (normalize_OPT) new option’s value (value) and, if it’s different from the current one (old) fires an event named “change_” + the option’s name (change_OPT), then fires change.

New value is already written to this._opt.OPT by the time change events occur.

from normopt

options is the object originally given to set() or ifSet().

Example
var MyClass = Sqimitive.Base.extend({
  _opt: {
    caption: '',
  },

  events: {
    // When _opt.caption is changed - call render() to update the
    // looks.
    change_caption: 'render', //¹
  },
})

¹ Be aware of argDanger as your handler might care for extra arguments. If it does then use a masked reference (see masker()):

events: {
  // Pass no arguments thanks to '-':
  change_caption: 'updateCaption-',
},

Example
var MyClass = Sqimitive.jQuery.extend({
  el: {tag: 'form'},

  _opt: {
    caption: '',
    body: '',
  },

  events: {
    // When any option is changed - update the corresponding <input>.
    change: function (name, value) {
      // change_OPT doesn't receive option's name as the first
      // argument.
      //
      // value is used as is - if clean-up is required then
      //  normalize_OPT events must be handled.
      this.$('[name="' + name + '"]').val(value)
    },
  },
})
ExamplesetoptpropagPropagation of options (optpropag):
sqim.on('change_foo', function (value, old, options) {
  if (!options.noSync) {
    $.ajax({
      url: 'update',
      type: 'POST',
      data: this.get(),
    })
  }
})

// The handler above performs an AJAX request:
sqim.ifSet('foo', 123)

// But not now (set() passes options through to ifSet()):
sqim.set('foo', 123, {noSync: true})

// assignResp() passes options to set() so no request is performed too:
sqim.assignResp({foo: 123}, {noSync: true})

Defined in: sqimitive.js, line 3622

constructor ( [opt] )

Calls Core.constructor() and fire()s init and postInit, passing opt to all.

opt is the first argument optionally given to new: new Sqimitive.Base({opt...}).

ExampleEnsures opt is always an object before passing it on so that there is no need for checks like (opt && opt.foo). Additionally, init/postInit handlers may propagate changes in user-given opt to other handlers or even to the caller of new (optpropag).
var MyClass = Sqimitive.Base.extend({
  events: {
    init: function (opt) { opt.changed = 123 },
  },
})

var opt = {}
;(new MyClass(opt))
  // opt.changed == 123

Constructors are reminiscents of the traditional JavaScript OOP (if it can be called so). They are hard to work with in Sqimitive (you can’t override them using events) so you want to leave them alone, instead working with init() and postInit() which are “regular” Sqimitive methods.

Defined in: sqimitive.js, lines 3164-3175 (12 lines) • Show code

constructor: function Sqimitive_Base(opt) {
  // ^^ Giving this function a name so that it's visible in the debugger.

  // Ensuring the argument is always an object.
  // Mere arguments[0] = {} won't work because if arguments.length == 0,
  // this won't update length and so apply() will assume arguments is still
  // empty (0) even though index 0 has been set.
  opt || Array.prototype.splice.call(arguments, 0, 1, {})
  Sqimitive.Base.__super__.constructor.apply(this, arguments)
  this.init.apply(this, arguments)
  this.postInit.apply(this, arguments)
},
findKey ( sqim | func [, cx] )

Part of: tag_Nesting, tag_Utilities

Returns the string key of the given child (in _children of self) or of the first child matched by the callback, or undefined.

findKey() has two call forms:

  • function (sqim) – If sqim is part of _children, returns its key.
  • function (func [, cx]) – Call func in the cx context (this if null or omitted) giving it the usual iterator’s set of arguments: childObject, childKey, childrenObject (as in Underscore, etc.). Returns childKey as soon as func returns a truthy value.

    Warning: do not modify childrenObject as it’s the _children itself.

In any case, findKey() returns undefined if no match was found.

Example
col.findKey(col.first())   // get key of the first child
col.findKey(ch => ch.get('enabled'))   // get key of the first "enabled" child

Other notes:

from parentkeyowning

Note: key only matches the child’s _parentKey if this is _owning, else it may differ.

from unordered

Attention: JavaScript objects are unordered. See the description of Ordered.

Either func() should match exactly 0 or 1 children or the caller should not care which of the matched ones findKey() returns (since it may return a different matching child every time).

Defined in: sqimitive.js, lines 4504-4513 (10 lines) • Show code

findKey: function (func, cx) {
  var eq = func instanceof Object
  for (var key in this._children) {
    if (eq
          ? (this._children[key] == func)
          : func.call(cx || this, this._children[key], key, this._children)) {
      return key
    }
  }
},
get ( [opt] )

Part of: tag_Options

Returns the value of one _opt or values of all of them (if no argument).

Example
sqim.get('opt1')   //=> 'value'
sqim.get()         //=> {opt1: 'value', opt2: 123, ...}

All options’ values are returned in an object shallow-copied from _opt meaning it’s safe to change the object itself (add/remove properties) but changing non-scalar values will indirectly change options inside the object:

var MyClass = Sqimitive.Base.extend({
  _opt: {array: [1, 2]},
})

var obj = new MyClass
var opts = obj.get()   //=> {array: [1, 2]}
opts.new = 3
obj.get()              //=> {array: [1, 2]} - same
opts.array.push(4)
obj.get()              //=> {array: [1, 2, 4]} - changed

Override this method to read non-existing options or transform them like this:

var MySetter = Sqimitive.Base.extend({
  _opt: {
    foo: 'foo!',
  },

  events: {
    // Now any option can be read as option:up to turn its value into
    // upper case.
    '+get': function (res, name) {
      if (name = name.match(/^([^:]+):up$/)) {
        return this.get(name[1]).toUpperCase()
      }
    },
  },
})

alert((new MySetter).get('foo'))       //=> foo!
alert((new MySetter).get('foo:up'))    //=> FOO!

from unordered

Attention: JavaScript objects are unordered. See the description of Ordered.

There are no guarantees in which order options would be iterated over.

Defined in: sqimitive.js, lines 3492-3494 (3 lines) • Show code

get: function (opt) {
  return arguments.length ? this._opt[opt] : _.extend({}, this._opt)
},
getSet ( toGet [, toSet [, toReturn]] [, func[, cx]] )

Part of: tag_Options

Performs batched get() and/or set() on multiple options.

Arguments
Name Types Notes
toGetstring _opt name
array of names
toSetstring _opt name
array of names
null/omitted assume toGet
toReturnstring _opt name
array of names
null/omitted assume toSet
func Given G arguments (G = length of toGet), returns an array or a single value (converted to [array]) – option value(s) to set. Returned array’s length must be ≤ length of toSet. Missing members of toSet are not set.

If func is missing sets every toSet[N] to toGet[N]. The length of toSet must be ≤ length of toGet.

cxobject The context for func
Result Types
Types Notes
array of values if toReturn is an array
mixed single value if not

Errors if called without arguments (without toGet).

Example
// Multi-get():
sqim.getSet(['opt1', 'opt2', 'opt3'])
  //=> [value of opt1, value of opt2, value of opt3]
  // even though the above not only get()s but also set()s them,
  // unless a normalize_OPT returns a different value then no change
  // events are fired since old values are the same

sqim.getSet('opt1', 'opt2')
// Equivalent to:
sqim.set('opt2', sqim.get('opt1'))
// Similar but result of getSet() is value of opt3, not opt1
sqim.getSet('opt1', 'opt2', 'opt3')

sqim.getSet(['left', 'right'], ['right', 'left'])
// Equivalent to:
var temp = sqim.get('left')
sqim.set('left', sqim.get('right'))
sqim.set('right', temp)
ExampleUpdating an option based on another option:
Sqimitive.Base.extend({
  _opt: {money: 0, income: 10},
})

var newMoney = sqim.getSet(['money', 'income'], 'money',
                           (money, income) => money + income)
alert(newMoney)    //=> 10

// Equivalent to:
var newMoney = sqim.get('money') + sqim.get('income')
sqim.set('money', newMoney)
alert(newMoney)

Defined in: sqimitive.js, lines 3413-3434 (22 lines) • Show code

getSet: function (toGet, toSet, toReturn, func, cx) {
  var args = Core.toArray(arguments)
  for (var i = 0; i <= 2; i++) {
    if (typeof args[i] == 'function') {
      args.splice(i, 0, args[i - 1])
    } else if (args[i] == null) {
      args[i] = args[i - 1]
    }
    args['a' + i] = _.isArray(args[i]) ? args[i] : [args[i]]
  }
  var got = _.map(args.a0, this.get, this)
  if (args[3]) {
    got = args[3].apply(args[4], got)
    if (!_.isArray(args[1])) { got = [got] }
  }
  _.each(got, function (value, index) {
    this.set(args.a1[index], got[index])
  }, this)
  return _.isArray(args[2])
    ? _.map(args[2], this.get, this)
    : this.get(args[2])
},
ifSet ( opt, value[, options] )

Part of: tag_Options

Changes the value of one _opt’ion and returns true if it was different from current.

Result Types
Types Notes
true if events were fired (value different or options.forceFire set)
false otherwise
Example
sqim.ifSet('key', 'foo')   //=> true (changed)
sqim.ifSet('key', 'foo')   //=> false (unchanged)
sqim.ifSet('key', 'foo', {forceFire: true})    //=> true (forced change)

ifSetSetCalls normalize_OPT giving it value; if the result is different from this._opt.opt (as reported by un:isEqual()) or if options.forceFire was set – fires change_OPT event, then change.

ExampleYou can take advantage of ifSet()’s return value to perform interlocking operations saving a call to get() (although of course it’s not truly atomic):
if (sqim.ifSet('eventsBound', true)) {
  // eventsBound was previously !_.isEqual(true) and it was now
  // changed to true so we can do what we need, once until it changes
  // to non-true again.
}
…As a short form of writing:
if (!sqim.get('eventsBound')) {
  sqim.set('eventsBound', true)
  // ...
}

Other notes:

  • Use set() which returns this if you don’t care for the return value but want method chaining.
  • Use getSet() if you want to update options based on other options (e.g. increment a value).
  • There’s no set() version that writes multiple options at once as you would do in some kind of “sync” operation. You might be looking for assignResp() (useful when assigning an API response) or just do plain simple $.each(opts, model.set.bind(model)).
  • It is safe to change _opt from within normalize_OPT or change handlers – they are written to this._opt immediately but subsequent change_OPT and change events are deferred in FIFO fashion (first set – first fired). This preserves “incremental” updates order.
  • See also Options overview (opt).
ExampleOverriding ifSet() is possible but most of the time normalize_OPT is what you need:
var MySetter = Sqimitive.Base.extend({
  _opt: {
    readOnly: 'foo',
  },

  events: {
    // Our handler will be called before the inherited ifSet() and
    // will prevent modification of this._opt.readOnly when they are
    // done via set/ifSet (i.e. the recommended way).
    //
    // Make sure not to hook '-set' because set() calls ifSet() and it
    // would be still possible to change 'readOnly' with
    // ifSet('readOnly', 'bar').
    '-ifSet': function (opt) {
      if (opt == 'readOnly') {
        throw new Error('You shouldn\'t really change what is called readOnly, you know?')
      }
    },

    // normalize_OPT would be better though:
    normalize_readOnly: function () {
      throw new Error('You shouldn\'t really change what is called readOnly, you know?')
    },
  },
})

optpropagoptions can be used to propagate custom data to the handlers of normalize_OPT(), change_OPT() and change(), and even back from them (options is always an object, possibly empty):

Sqimitive.Base.extend({
  change_foo: function (value, options) {
    options.foo = 123
  },
})

var options = {}
sqim.set('foo', options)
alert(options.foo)       //=> 123

Defined in: sqimitive.js, lines 3821-3832 (12 lines) • Show code

ifSet: function (opt, value, options) {
  options || (options = {})
  var old = this._opt[opt]
  var func = 'normalize_' + opt
  this[func] && (value = this[func](value, options))
  this._opt[opt] = value

  if (options.forceFire || !_.isEqual(value, old)) {
    this._fireSet([opt, [value, old, options]])
    return true
  }
},
init ( [opt] )

Part of: tag_Lifecycle

Resolves _childClass and sets _opt’ions from opt.

Arguments of init() and postInit() match those given to the constructor, which in turn gets them from new. Usually only the first one (opt) is used but you can use others:

var MyClass = Sqimitive.Base.extend({
  events: {
    init: function (opt, extra, fooArray) { ... },
  }
})

new MyClass({opt...}, 'extra', ['foo'])

Options are set by calling set() for every member of opt (if given). Other _opt remain with the declaration-time default values. opt.el is ignored, if present (see jQuery.el for the reason).

from plainstub

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

init() is followed by postInit().

Example
var MyClass = Sqimitive.Base.extend({
  _opt: {a: 1, b: 2},
})

new MyClass({b: 3, c: 4})
  // _opt is {a: 1, b: 3, c: 4}

Defined in: sqimitive.js, lines 3219-3236 (18 lines) • Show code

init: function (opt) {
  if (_.isArray(this._childClass)) {
    var path = this._childClass[1].split(/\./g)
    this._childClass = this._childClass[0]
    while (path[0]) {
      this._childClass = this._childClass[path.shift()]
    }
    if (!this._childClass) {
      throw new ReferenceError('init: _childClass by path not found')
    }
  }

  for (var name in opt) {
    // By convention, el is given as options to replace the default value
    // of this class, but el isn't a real option.
    name == 'el' || this.set(name, opt[name])
  }
},
nest ( [key, ] sqim [, options] )

Part of: tag_Nesting

Adds a new child using a shorter syntax than nestEx().

Result Types
Types Notes
sqim The added child.
Arguments
Name Types Notes
keystring If omitted, _defaultKey() is used which by default returns sqim’s _cid (unique instance identifier).
number
omitted
sqimobject New child to nest.
optionsobject Keys key and child are set by nest()
null/omitted

If you are overriding the “nesting” behaviour you should override nestEx instead of nest which calls the former.

from nestExDesc

Example
sqim.nest(new Sqimitive.Base)          // _parentKey = 'p123' (the _cid)
sqim.nest('key', new Sqimitive.Base)   // _parentKey = 'key'
// Same:
sqim.nestEx({key: 'key', child: new Sqimitive.Base})

sqim.unlist('key')
  // if sqim._owning is false - removes the child under 'key' if any,
  // else calls sqim.remove(); returns the found child or undefined
ExampleWhen hooking nestEx() to listen for changes (newly added sqimitives), check options.changed to avoid triggering your update logic if child was already nested:
Sqimitive.Base.extend({
  events: {
    // WRONG: will re-render even if the child was already there:
    nestEx: 'render',

    // CORRECT:
    '+nestEx': function (options) {
      if (options.changed) { this.render() }
    },

    // CORRECT since nestEx() returns the same options object as
    // given as its first argument:
    nestEx: function (options) {
      if (options.changed) { this.render() }
    },
  },
})

var child = new Sqimitive.Base
col.nest(child)   // calls render()
col.nest(child)   // doesn't call
  // child is already under the same key (_defaultKey() returns
  // _cid which is constant for the lifetime of an object)

Other notes:

  • There’s no nest() version that adds multiple children at once as you would do in some kind of “sync” operation. You might be looking for assignChildren() (useful when assigning an API response).
  • As with other options-accepting methods (e.g. ifSet()), the options object may be used to propagate custom data to event listeners (optpropag); options are also passed through by assignChildren().

Internal operation

nestEx() checks and errors if options.key is an object of any type (including null or undefined), or if options.child is of a wrong class (not _childClass). Then it converts and sets options.key to string, sets options.previous to the child currently occupying key (possibly undefined) and sets options.changed to false if previous is exactly child.

If options.changed is false then exits, otherwise:

Finally, if the child was nested (options.changed set), nestEx() _forward’s its _childEvents and, if _owning, calls new child’s owned() to notify it of the parent change.

Observe that nestEx():

  • Does nothing if child is already contained in this instance under the same key, i.e. when options.previous == child (changed is false).
  • unnest’s and nests child again if key differs.

Defined in: sqimitive.js, lines 3903-3912 (10 lines) • Show code

nest: function (key, sqim, options) {
  if (key instanceof Object) {   // function ( sqim [, options] )
    Array.prototype.splice.call(arguments, 0, 0, this._defaultKey(key))
  }
  options = _.extend({}, arguments[2] || {}, {
    key:    arguments[0],
    child:  arguments[1],
  })
  return this.nestEx(options).child
},
nestEx ( options )

Part of: tag_Nesting

Adds a new child to self (_children), unnest’ing the old one at the same key (if any).

Result Types
Types Notes
object options with added details about the operation.

The caller must set these keys in options:

Arguments
Name Types Notes
childobject A sqimitive to nest.
keystring New options.child’s key in _children
number

nestEx() mutates and returns options with updated keys:

Arguments
Name Types Notes
keystring always
previousobject
undefined
changedbool Whether any changes were done to the collection.

Subclasses and mixIn’s can use other options keys – for example, Sqimitive\Ordered receives insertion order in options.pos and sets options.index on return.

After the call, old length of self could be determined as follows:

this.length - (options.previous && options.changed)

In most cases nest() is more convenient to use as it allows omitting options.key and avoiding object notation for options.

ExamplenestExDesc
sqim.nest(new Sqimitive.Base)          // _parentKey = 'p123' (the _cid)
sqim.nest('key', new Sqimitive.Base)   // _parentKey = 'key'
// Same:
sqim.nestEx({key: 'key', child: new Sqimitive.Base})

sqim.unlist('key')
  // if sqim._owning is false - removes the child under 'key' if any,
  // else calls sqim.remove(); returns the found child or undefined
ExampleWhen hooking nestEx() to listen for changes (newly added sqimitives), check options.changed to avoid triggering your update logic if child was already nested:
Sqimitive.Base.extend({
  events: {
    // WRONG: will re-render even if the child was already there:
    nestEx: 'render',

    // CORRECT:
    '+nestEx': function (options) {
      if (options.changed) { this.render() }
    },

    // CORRECT since nestEx() returns the same options object as
    // given as its first argument:
    nestEx: function (options) {
      if (options.changed) { this.render() }
    },
  },
})

var child = new Sqimitive.Base
col.nest(child)   // calls render()
col.nest(child)   // doesn't call
  // child is already under the same key (_defaultKey() returns
  // _cid which is constant for the lifetime of an object)

Other notes:

  • There’s no nest() version that adds multiple children at once as you would do in some kind of “sync” operation. You might be looking for assignChildren() (useful when assigning an API response).
  • As with other options-accepting methods (e.g. ifSet()), the options object may be used to propagate custom data to event listeners (optpropag); options are also passed through by assignChildren().

Internal operation

nestEx() checks and errors if options.key is an object of any type (including null or undefined), or if options.child is of a wrong class (not _childClass). Then it converts and sets options.key to string, sets options.previous to the child currently occupying key (possibly undefined) and sets options.changed to false if previous is exactly child.

If options.changed is false then exits, otherwise:

Finally, if the child was nested (options.changed set), nestEx() _forward’s its _childEvents and, if _owning, calls new child’s owned() to notify it of the parent change.

Observe that nestEx():

  • Does nothing if child is already contained in this instance under the same key, i.e. when options.previous == child (changed is false).
  • unnest’s and nests child again if key differs.
  • nestdupAlways adds child if this is non-_owning so it may duplicate in _children. To avoid this hook =nestEx and call sup (evtpf) only if !this.contains(options.child) (util).

Defined in: sqimitive.js, lines 4024-4057 (34 lines) • Show code

nestEx: function (options) {
  var sqim = options.child

  if (!(sqim instanceof this._childClass)) {
    throw new TypeError('nestEx: Nesting Sqimitive of wrong class')
  } else if (typeof options.key == 'object' ||    // object or null.
             options.key === undefined) {
    throw new TypeError('nestEx: Bad key given')
  }

  // Object keys are always strings; _parentKey mismatching actual key will
  // break indexOf() if it's used on an array like _.keys(this._children).
  var key = options.key += ''
  var prev = options.previous = this._children[key]

  if (options.changed = prev !== sqim) {
    if (this._owning) {
      prev && prev.unnest()   // --this.length
      sqim.unnest()
      this._children[key] = sqim
      sqim._parent = this
      sqim._parentKey = key
      ++this.length
    } else {
      this._children[key] = sqim
      prev ? this.unnested(prev) : ++this.length
    }

    this._forward('.', this._childEvents, sqim)
    this._owning && sqim.owned()
  }

  return options
},
nested ( [key] )

Part of: tag_Nesting

Returns a single child by key or instance or all _children (if no argument).

Result Types
Types Notes
object all children if key not given
object the found child
undefined if given key/child isn’t nested in this
Arguments
Name Types Notes
keyomitted return a shallow copy of _children
string or number return the object at that key (case-sensitive) or null
object return the argument itself if this object is nested as per findKey()
Example
sqim.nested()   //=> {childKey1: Sqimitive, ...}
sqim.nested('childKey1')    //=> Sqimitive by its key
sqim.nested('foobarbaz!')   //=> undefined - key not found in sqim._children

var child = sqim.nested('childKey1')
sqim.nested(child)          //=> child by its object
sqim.nested(new Sqimitive)  //=> undefined - object not listed in sqim._children
ExampleThe object key form allows using nested() as un:includes() (util) to check if a child is part of the collection:
if (col.nested(sqim)) {
  // ...
}

parentkeyowningNote: key only matches the child’s _parentKey if this is _owning, else it may differ.

from unordered

Attention: JavaScript objects are unordered. See the description of Ordered.

Order of keys in the returned _children’s copy may be unpredictable.

nestedslicenested() without arguments returns an object – children with keys. slice() returns an array – children only, dropping their keys.

Defined in: sqimitive.js, lines 4389-4399 (11 lines) • Show code

nested: function (key) {
  if (!arguments.length) {
    return _.extend({}, this._children)
  } else if (key == null) {
    // Return undefined - neither keys nor children can be null.
  } else if (!(key instanceof Object)) {
    return this._children[key + '']
  } else if (this.findKey(key) != null) {
    return key
  }
},
normalize_OPT ( value, options )

Part of: tag_Options

Called to normalize and/or validate new _opt’ion value before it’s set.

ifSet() fires an event named “normalize_” + the option’s name before the value is actually set to _opt. Here you should clean it up (e.g. trim whitespace) or throw an error if it has a wrong format (e.g. not YYYY-MM-DD for dates).

normoptoptions is the object originally given to set() or ifSet().

ExampleRemoving spaces and converting to lower case:
var MyNorm = Sqimitive.Base.extend({
  _opt: {
    stringie: '',
  },

  // Now 'stringie' is guaranteed to have no surrounding whitespace
  // and be lower case - as long as it's not written directly as
  // this._opt.stringie, which is a bad idea in general.
  normalize_stringie: function (value) {
    return _.trim(value).toLowerCase()
  },
})

var str = (new MyNorm)
  .set('stringie', '  Foo\n')
  .get('stringie')
    //=> 'Foo'
ExampleValidating new value (see the sample To-Do application for a complete primer):
var MyValid = Sqimitive.Base.extend({
  _opt: {
    date: null,    //= Date or 'YYYY-MM-DD'¹
  },

  normalize_date: function (value) {
    if (!(value instanceof Date) &&
        (value = value.trim().match(/^(\d{4})-(\d\d)-(\d\d)$/))) {
      value = new Date(value[1], value[2] - 1, value[3])
    }
    if (!(value instanceof Date)) {
      throw new TypeError('Bad date format')
    }
    return value
  },
})

;(new MyValid).set('date', new Date)       // works
;(new MyValid).set('date', '2020-02-20')   // works
;(new MyValid).set('date', 'whatchadoin')  // TypeError

¹ It’s customary in Sqimitive to leave a comment explaining non-trivial options’ types or formats.

Unlike with change/change_OPT, there is no global normalization function (since every option usually needs a unique approach) but you can override ifSet() if you need this.

Remember: when defined in events, function’s return value is ignored unless = or + prefixes are used (evtpf). Also, see evtconc and es6this.

Sqimitive.Base.extend({
  events: {
    // WRONG: return value is ignored:
    normalize_stringie:    function (value) { return _.trim(value) },
    // CORRECT: adding handler after others:
    '+normalize_stringie': function (res, value) { return _.trim(value) },
    // CORRECT: adding handler instead of others:
    '=normalize_stringie': function (sup, value) { return _.trim(value) },
  },

  // CORRECT, but only if no normalize_string is defined in any base
  // class (see #evtconc):
  normalize_stringie: function (value) { return _.trim(value) },
})

from setoptpropag

ExamplePropagation of options (optpropag):
sqim.on('change_foo', function (value, old, options) {
  if (!options.noSync) {
    $.ajax({
      url: 'update',
      type: 'POST',
      data: this.get(),
    })
  }
})

// The handler above performs an AJAX request:
sqim.ifSet('foo', 123)

// But not now (set() passes options through to ifSet()):
sqim.set('foo', 123, {noSync: true})

// assignResp() passes options to set() so no request is performed too:
sqim.assignResp({foo: 123}, {noSync: true})

Defined in: sqimitive.js, line 3529

owned ( )

Part of: tag_Nesting

Called after this instance has been nest’ed into an _owning sqimitive (changed parents or got a first _parent).

By the time owned is called _parent and _parentKey of self are already set to new values.

See also unnest() that gets called before _parent is changed/removed.

Example
var MyChild = Sqimitive.Base.extend({
  events: {
    // Will append this.el to parent's .some-point node as soon as
    // this instance gets a new parent. If you want to do this in
    // production though, look for the attachPath _opt'ion of
    // Sqimitive\jQuery.
    owned: function () {
      this.attach(this._parent.$('.some-point'))
    },
  },
})

Other notes:

  • plainstubThis method returns nothing.
  • It should not be called directly.
  • It’s defined as stub in Base which lets Sqimitive remove this function instead of calling it as a handler doing nothing when a new handler is registered for this event (e.g. in a subclass via events as in the example above).

Defined in: sqimitive.js, line 4116

postInit ( opt )

Part of: tag_Lifecycle

Called after init() to bootstrap the new instance after construction.

Logically, init is part of the object construction while postInit is part of its lifecycle; while init is inseparable from new, postInit could be called at a later time (in theory) so it should not make the object inconsistent. Thinking about it that way helps to decide which of the two events to hook.

Usually init() creates and configures related objects (DOM nodes, collections, etc.) while postInit() starts timers, resource preloading, etc. This way you don’t depend on the order of init() handlers (it may so happen that the init handler of a subclass is executed before the inherited init of its base class, when internal objects are not yet initialized).

from initonce

Base does nothing in postInit().

Example
var MySqimitive = Sqimitive.Base.extend({
  _button: null,

  events: {
    init: function () {
      this._button = this.nest(new MyButton)
    },

    postInit: function () {
      this._button.startAnimation()
    },
  },
})

Defined in: sqimitive.js, line 3238

remove ( )

Part of: tag_Nesting, tag_Lifecycle

Removes el and calls unnest().

Result Types
Types Notes
baseRemoveobject this

rmvsunnUse unnest() when an object is temporary elided from the hierarchy (e.g. because it’s changing parents). Use remove() when it’s completely destroyed and its “view” (DOM node, etc.) is no longer needed. By convention, remove() is also used on el-less objects (where it’s identical to unnest() in effect) to convey the intention of destroying them.

Example
var child = new Sqimitive.Base({el: '<p>Some text.</p>'})
var parent = new Sqimitive.Base({el: '<article>'})
parent.nest(child).attach(parent.el)
  // now we have <article><p>Some text.</p></article>

child.unnest()
  // we still have <article><p>Some text.</p></article> even though
  // child is no more listed under parent._children

// But if we would have done this:
child.remove()
  // we would have <article></article> with the child removed from
  // both the parent's _children list and from its the parent's el

from elstub

Sqimitive\Base doesn’t define any element-related functionality but only provides stubs for several common fields (el, render(), remove(), etc.) for standardized extension. See Sqimitive\jQuery.el for one such subclass.

Base’s remove() simply calls unnest().

Defined in: sqimitive.js, lines 4584-4586 (3 lines) • Show code

remove: function () {
  return this.unnest()
},
render ( )

Part of: tag_Lifecycle

Populate from scratch or update the object’s “view” – el.

Result Types
Types Notes
renderAttachbaseAttachobject this

Here’s a typical Sqimitive object lifecycle:

  • construct with new: new Class({opt...})
  • attach() (to DOM, etc.), for members – when nested to a collection
  • render() when required for user to see it first time
  • render() again when something changes that affects the visual presentation (usually in response to a change of some _opt’ion)
  • finally, remove()

Complete re-rendering on change is simple and adequate for many simple sqimitives but if it’s heavy, it’s customary to perform a partial update using method(s) named update(). There are no such methods by default but see vw for an example.

Example
var Label = Sqimitive.jQuery.extend({
  _opt: {caption: 'untitled'},

  events: {
    render: function () {
      this.el.text(this.get('caption'))
    },

    change_caption: 'render',
  },
})
                                    // Lifecycle:
;(new Label)                        // 1) construct
  .attach('body')                   // 2) attach
  .render()                         // 3) render
  .set('caption', 'render again!')  // 4) update

from elstub

Sqimitive\Base doesn’t define any element-related functionality but only provides stubs for several common fields (el, render(), remove(), etc.) for standardized extension. See Sqimitive\jQuery.el for one such subclass.

Base’s render() calls attach() on all children of self (but not on self).

Defined in: sqimitive.js, lines 3330-3333 (4 lines) • Show code

render: function () {
  this.invoke('attach')
  return this
},
set ( opt, value[, options] )

Part of: tag_Options

Changes the value of one _opt’ion and returns this.

set() is identical to ifSet() except the latter returns true/false indicating if the new value was different from the old one or options.forceFire was set.

If you are overriding the “setter” behaviour you should override ifSet instead of set which calls the former.

Example
sqim.set('key', 'foo')     //=> this

// Fires normalize/change_key/change despite the fact that old key's
// value is the same:
sqim.set('key', 'foo', {forceFire: true})

The description of ifSet() follows.

from ifSetSet

Calls normalize_OPT giving it value; if the result is different from this._opt.opt (as reported by un:isEqual()) or if options.forceFire was set – fires change_OPT event, then change.

ExampleYou can take advantage of ifSet()’s return value to perform interlocking operations saving a call to get() (although of course it’s not truly atomic):
if (sqim.ifSet('eventsBound', true)) {
  // eventsBound was previously !_.isEqual(true) and it was now
  // changed to true so we can do what we need, once until it changes
  // to non-true again.
}
…As a short form of writing:
if (!sqim.get('eventsBound')) {
  sqim.set('eventsBound', true)
  // ...
}

Other notes:

  • Use set() which returns this if you don’t care for the return value but want method chaining.
  • Use getSet() if you want to update options based on other options (e.g. increment a value).
  • There’s no set() version that writes multiple options at once as you would do in some kind of “sync” operation. You might be looking for assignResp() (useful when assigning an API response) or just do plain simple $.each(opts, model.set.bind(model)).
  • It is safe to change _opt from within normalize_OPT or change handlers – they are written to this._opt immediately but subsequent change_OPT and change events are deferred in FIFO fashion (first set – first fired). This preserves “incremental” updates order.
  • See also Options overview (opt).
ExampleOverriding ifSet() is possible but most of the time normalize_OPT is what you need:
var MySetter = Sqimitive.Base.extend({
  _opt: {
    readOnly: 'foo',
  },

  events: {
    // Our handler will be called before the inherited ifSet() and
    // will prevent modification of this._opt.readOnly when they are
    // done via set/ifSet (i.e. the recommended way).
    //
    // Make sure not to hook '-set' because set() calls ifSet() and it
    // would be still possible to change 'readOnly' with
    // ifSet('readOnly', 'bar').
    '-ifSet': function (opt) {
      if (opt == 'readOnly') {
        throw new Error('You shouldn\'t really change what is called readOnly, you know?')
      }
    },

    // normalize_OPT would be better though:
    normalize_readOnly: function () {
      throw new Error('You shouldn\'t really change what is called readOnly, you know?')
    },
  },
})

Defined in: sqimitive.js, lines 3522-3525 (4 lines) • Show code

set: function (opt, value, options) {
  this.ifSet(opt, value, options)
  return this
},
sink ( event, args, fireSelf )

Part of: tag_Events

Triggers an event on every child, recursively.

Sends event with arguments args to all nested _children, to nested children of those children and so on.

from bubsin

Result Types
Types Notes
this

If fireSelf is true then first fires event on self (by default it doesn’t).

bubble() sends the event upstream while sink() sends it downstream. However, the first walks _owning sqimitives (since it’s using _parent) while the second works for any mode (since it’s using _children).

Since both call methods, not necessary events, you can “recursively” call methods as well (which may or may not be events, see evt).

Note that it might get quite intense with heavy nesting.

Example
// Recursively causes all nested views and self to be rendered:
sqim.sink('render', []. true)

// Recursively calls remove() on self, all children and their
// children, removing every single sqimitive from its parent (useful
// when removing a parent el doesn't automatically free resources of
// its children like it does in DOM):
sqim.sink('invoke', ['remove'], true)

// We can use it to serialize the entire tree:
var serialized = []
sqim.sink('saveTo', [serialized])
localStorage.setItem('saved', JSON.stringify(serialized))
// Now if children implement something like this then serialized will
// contain a portable representation of the current hierarchy (without
// self):
child.on('saveTo', function (ser) { ser.push(this.get()) })

Defined in: sqimitive.js, lines 5213-5217 (5 lines) • Show code

  sink: function (event, args, fireSelf) {
    fireSelf && this[event] && this[event].apply(this, args)
    this.invoke('sink', event, args, true)
    return this
  },
})
slice ( [start [, end]] )

Part of: tag_Utilities

Treats this instance’s _children as an ordered array and returns a portion of it.

start defaults to 0, end – to length + 1.

Attention: the end index is not included into result. If start == end then an empty array is always returned.

Example
slice()       // get all children; toArray() does the same
slice(1, 2)   // get 2nd child as an array
slice(-1)     // get last child as an array; last() is more convenient
slice(5, 8)   // get 6th and 7th children as an array; no 8th!
slice(0, 0)   // start == end - always an empty array
slice(1, -1)  // get all children except the first and last

Along with length, slice() makes Sqimitives look like an array and so slice()’s interface is the same as Array’s slice(): developer.mozilla.org/en-US/docs/Web/Java…

from nestedslice

nested() without arguments returns an object – children with keys. slice() returns an array – children only, dropping their keys.

Unordered objects warning

from unordered

Attention: JavaScript objects are unordered. See the description of Ordered.

slice() may return entries in arbitrary order.

It should not be used unless special measures were taken to make this instance properly ordered or if the caller doesn’t care for child order (but then it should never use a positive start as there are no guarantees which child will be at that index).

Use either the Ordered mixIn (and see at()) or override slice() to sort _children based on some criteria before taking a portion of it (this may be slower than Ordered that maintains sort order as children come and go without resorting on every call to slice()):

events: {
  '=slice': function (sup, start, end) {
    var sorter = function (a, b) { return a.get('pos') - b.get('pos') }
    return sup(this).sort(sorter).slice(start, end)
    // WRONG: do not use any #util functions to avoid recursion:
    return this.toArray()...
  }
}

Making slice() “ordered” is enough to make the Sqimitive ordered because slice is used by all other functions that treat _children as an array (each(), find() and others, see util).

Defined in: sqimitive.js, lines 4457-4464 (8 lines) • Show code

slice: function (start, end) {
  // _.values(), _.toArray(), Object.keys(), etc. return values in
  // arbitrary order. The Base Sqimitive is unordered.
  //
  // Warning: do not replace _.values() with this.toArray() since the
  // latter calls this.slice().
  return _.values(this._children).slice(start, end)
},
unlist ( key )

Part of: tag_Nesting

Removes a child by its key or instance from own _children.

Result Types
Types Notes
object removed sqimitive
undefined nothing found
Arguments
Name Types Notes
keystring or number key which to clear
object instance to remove as per findKey()
Example
collection.unlist('foo')    //=> Sqimitive or undefined
collection.unlist(collection.nested('foo'))   // identical to above
collection.unlist(child)    //=> Sqimitive (child) or undefined

If _owning is set (as it is by default) calls remove(), else deletes the key in _children and calls unnested() on self.

Does nothing if this key/child is not contained (returns undefined).

See also unnest() which is called on the child (not on the parent object). However, it’s only usable in _owning collections since there’s no reverse child → parent relationship in non-_owning mode.

Defined in: sqimitive.js, lines 4224-4242 (19 lines) • Show code

unlist: function (key) {
  if (key instanceof Object) {
    key = this.findKey(key)
  }

  var sqim = this._children[key += '']

  if (sqim) {
    if (this._owning) {
      sqim.remove()
    } else {
      delete this._children[key]
      --this.length
      this.unnested(sqim)
    }
  }

  return sqim
},
unnest ( )

Part of: tag_Nesting

Removes this instance from its _parent object, if any.

Result Types
Types Notes
this

unnest() does nothing if _parent of this is already null (i.e. not owned).

Note: it doesn’t remove own el from its parent node – use remove() for this.

from rmvsunn

Use unnest() when an object is temporary elided from the hierarchy (e.g. because it’s changing parents). Use remove() when it’s completely destroyed and its “view” (DOM node, etc.) is no longer needed. By convention, remove() is also used on el-less objects (where it’s identical to unnest() in effect) to convey the intention of destroying them.

Example
var child = new Sqimitive.Base({el: '<p>Some text.</p>'})
var parent = new Sqimitive.Base({el: '<article>'})
parent.nest(child).attach(parent.el)
  // now we have <article><p>Some text.</p></article>

child.unnest()
  // we still have <article><p>Some text.</p></article> even though
  // child is no more listed under parent._children

// But if we would have done this:
child.remove()
  // we would have <article></article> with the child removed from
  // both the parent's _children list and from its the parent's el
ExampleA child reacting to its unnesting:
var MyChild = Sqimitive.Base.extend({
  events: {
    '-unnest': function () {
      // this._parent and _parentKey are still set if this instance
      // was attached to any parent, otherwise they are null.
      this._parent && alert("I am about to be... unnested! :'(")
    },

    unnest: function () {
      // At this point _parent and _parentKey are certainly null but
      // there's no telling if they will remain so - or if this
      // instance had any parent before unnest() was called.
      alert('I am now as free as the wind!')
    },

    // In contrast to the above, here we can reliably determine if
    // this sqimitive was previously nested and if it was - do
    // something after it was unnested by calling the inherited
    // implementation via sup.
    '=unnest': function (sup) {
      var hadParent = this._parent != null
      sup(this, arguments)
      hadParent && alert('I was abducted but you saved me!')
      return res
    },
  },
})

Other notes:

  • unnest() clears _parent and _parentKey and calls unnested(this) on the former parent.
  • This effectively creates a new detached tree (if this has nested _children) or a leaf (if not) – more in Children concept (chld).

Defined in: sqimitive.js, lines 4296-4305 (10 lines) • Show code

unnest: function () {
  var parent = this._parent
  if (parent) {
    delete parent._children[this._parentKey]
    this._parent = this._parentKey = null
    --parent.length
    parent.unnested(this)
  }
  return this
},
unnested ( sqim )

Part of: tag_Nesting

Called right after a child of self (sqim) was detached from its parent (this).

Base’s implementation calls sqim.off(this) to unregister all event handlers that might have been previously attached to the removed child by this instance (provided they were not hardwired with fuse() and used cx === this).

By the time unnested is called sqim’s _parent and _parentKey are already null.

Example
var MyParent = Sqimitive.Base.extend({
  events: {
    unnested: function (sqim) {
      alert('Whence thou goeth, my ' + sqim._cid + '...')
    },
  },
})

Other notes:

from plainstub

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

Defined in: sqimitive.js, lines 4337-4343 (7 lines) • Show code

unnested: function (sqim) {
  sqim.off(this)

  if (this.length < 0 && console && console.error) {
    console.error('Broken nesting: sqimitive.length below zero')
  }
},