class Sqimitive.Base
extends Core
Defined in: main.js, lines 4300-7345 (3046 lines)
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.
Types | Notes |
---|---|
Object to disable type checking | Array and string are indirect declaration-time references (classref). |
Sqimitive.Base the default | |
array | |
string |
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)
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:
Sqimitive.Core
(Core) since non-sqimitives are unlikely to work properly as children.
This is not checked though.It’s often convenient to provide _childClass as an array or string to extend() or mixIn(). In this case init() resolves the value of _childClass to the actual class, once per instantiation of every class (this makes it tad slower than providing an object value):
Name | Types | Notes |
---|---|---|
array | like [BaseObject, 'Sub.Class.Path'] | Same as evaluating
BaseClass.Sub.Class.Path |
string | like 'Sub.Class.Path' | Relative to static properties of
this |
string | empty '' | The class of this |
init() errors if no class was found.
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: main.js, line 4594 • Show code
_childClass: null,
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.
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).
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.
from inMergePropsA
This property is listed in _mergeProps by default so subclass defining it adds to its parent’s values instead of overwriting the array entirely.
Other notes:
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
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: main.js, line 4727 • Show code
_childEvents: [],
Modifiers: protected
Part of: tag_Nesting
Holds references to objects contained within this instance (“collection”).
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:
Defined in: main.js, line 4457 • Show code
_children: {},
Modifiers: protected
Part of: tag_Options
from setOnDecl
May only be set upon declaration via Core::extend() or mixIn.
Specifies _respToOpt rules for transforming opt
given to init()
(new
) into this
._opt.
_initToOpt is the assignResp()’s schema used by init(). Base’s
value directly merges the opt
object into own _opt, ignoring el
.
el
by convention is meant to replace the declared class value, possibly
mutated (see jQuery, for example).
var My = Sqimitive.Base.extend({
_initToOpt: {context: false},
_context: null,
events: {
init: function (opt) {
this._context = opt.context
},
},
})
// Or, shorter:
var My = Sqimitive.Base.extend({
_initToOpt: {context: '._context'},
_context: null,
})
var my = new My({context: cx})
//=> my._opt = {}
//=> my._context = cx
var My = Sqimitive.Base.extend({
_opt: {
quux: 'initial',
},
// Thanks to _mergeProps, _initToOpt keys are merged with Base's.
_initToOpt: {
foo: true, // set foo under _opt.foo
bar: 'newBar', // set bar under _opt.newBar
// By default, assignResp() copies unlisted keys as is.
'': function (opt, options) { options.onlyDefined = true },
},
})
new My({quux: 0, foo: 1, bar: 2, baz: 3, el: 4})
//=> _opt = {quux: 'initial', foo: 1, newBar: 2}
//
// 1. Keeps class' value for quux (because of onlyDefined)
// 2. Takes foo
// 3. Renames bar to newBar
// 4. Ignores baz (because of onlyDefined)
// 5. And el (because of {el: false} inherited from Base)
from rtobase
Types | Notes |
---|---|
object {respKey: optValue}. |
_respToOpt’s keys are input object’s keys (except the special ''
)
and values are one of the following (optValue
):
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. If
begins with . , assign directly to a property on this (if sole .
then assign to property named respKey ) – typically used for public
read-only properties. | |
function | (respValue, key, resp, options) | Input item transformation.
This function is called in this context and must return
[false|'optToSet|.[prop]', value] or falsy (equals [false] ),
with the first member treated as above.
The If |
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]}
}
},
})
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')
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 unordered
Attention: JavaScript objects are unordered, as explained in the description of Ordered.
Keys are restored in arbitrary order except ''
is always called first.
The below example is buggy because object
may be called before
objects
, when this.objects
is not yet unserialized:
Sqimitive.Base.extend({
objects: null, // some kind of collection
object: null, // a member of objects
_respToOpt: {
objects: function (list) {
this.objects.assignChildren(list)
},
object: function (key) {
this.object = this.objects.nested(key)
},
},
})
objects
must be restored from within ''
:
_respToOpt: {
'': function (resp) {
this.objects.assignChildren(resp.objects)
},
object: function (key) {
// As before.
},
},
This has to be done even if objects
assignment is simple:
// WRONG:
_respToOpt: {
objects: true,
object: function (key) {
this.object = this.objects[key]
},
},
// WRONG:
_respToOpt: {
// No objects key = true if onlyDefined is false (as it is by default).
object: function (key) {
this.object = this.objects[key]
},
},
assignResp({objects: {k: ...}, object: 'k'}, {onlyDefined: false})
from inMergeProps
This property is listed in _mergeProps by default so subclass defining it adds to its parent’s object instead of overwriting it entirely (but identical keys are still overwritten).
Other notes:
options.onlyDefined
flag of assignResp()..
has no effect on respKey
-s missing from schema
: they are
always either ignored (!onlyDefined
) or set(): sqim.assignResp({'.x': 1, '.y': 2}, {schema: {'.x': true}})
//=> sqim.x is 1, sqim._opt['.y'] is 2
sqim.assignResp({'.x': 1, '.y': 2}, {schema: {'.x': true}, onlyDefined: false})
//=> sqim.x is 1, sqim._opt['.y'] is unchanged
Defined in: main.js, line 4964 • Show code
_initToOpt: {el: false},
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.
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:
this._opt
and fires
change_OPT and change to notify the interested parties.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 subclass defining it adds to its parent’s object instead of overwriting it 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 terms “options” are called bb:Model-attributes.
Defined in: main.js, line 4375 • Show code
_opt: {},
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).
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.
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: main.js, line 4506 • Show code
_owning: true,
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.
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: main.js, line 4408 • Show code
_parent: null,
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
.
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.
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)
Sqimitive.Base.extends({
pull: function () {
this._parent.unlist(this._parentKey)
// this is a contrived example since this.remove() does
// exactly the same
},
})
Defined in: main.js, line 4432 • Show code
_parentKey: null,
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 used by assignResp().
See also _initToOpt that is used to assign opt
given to init()
(new
) to this
._opt.
Types | Notes |
---|---|
rtobaseobject {respKey: optValue}. |
_respToOpt’s keys are input object’s keys (except the special ''
)
and values are one of the following (optValue
):
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. If
begins with . , assign directly to a property on this (if sole .
then assign to property named respKey ) – typically used for public
read-only properties. | |
function | (respValue, key, resp, options) | Input item transformation.
This function is called in this context and must return
[false|'optToSet|.[prop]', value] or falsy (equals [false] ),
with the first member treated as above.
The If |
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]}
}
},
})
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')
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 unordered
Attention: JavaScript objects are unordered, as explained in the description of Ordered.
Keys are restored in arbitrary order except ''
is always called first.
The below example is buggy because object
may be called before
objects
, when this.objects
is not yet unserialized:
Sqimitive.Base.extend({
objects: null, // some kind of collection
object: null, // a member of objects
_respToOpt: {
objects: function (list) {
this.objects.assignChildren(list)
},
object: function (key) {
this.object = this.objects.nested(key)
},
},
})
objects
must be restored from within ''
:
_respToOpt: {
'': function (resp) {
this.objects.assignChildren(resp.objects)
},
object: function (key) {
// As before.
},
},
This has to be done even if objects
assignment is simple:
// WRONG:
_respToOpt: {
objects: true,
object: function (key) {
this.object = this.objects[key]
},
},
// WRONG:
_respToOpt: {
// No objects key = true if onlyDefined is false (as it is by default).
object: function (key) {
this.object = this.objects[key]
},
},
assignResp({objects: {k: ...}, object: 'k'}, {onlyDefined: false})
from inMergeProps
This property is listed in _mergeProps by default so subclass defining it adds to its parent’s object instead of overwriting it entirely (but identical keys are still overwritten).
Other notes:
options.onlyDefined
flag of assignResp()..
has no effect on respKey
-s missing from schema
: they are
always either ignored (!onlyDefined
) or set(): sqim.assignResp({'.x': 1, '.y': 2}, {schema: {'.x': true}})
//=> sqim.x is 1, sqim._opt['.y'] is 2
sqim.assignResp({'.x': 1, '.y': 2}, {schema: {'.x': true}, onlyDefined: false})
//=> sqim.x is 1, sqim._opt['.y'] is unchanged
Defined in: main.js, line 4899 • Show code
_respToOpt: {},
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: main.js, line 4983 • Show code
el: false,
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().
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 subclass defining it adds to its parent’s object instead of overwriting it 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 handlers of 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: main.js, line 5021 • Show code
elEvents: {},
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: main.js, line 5030 • Show code
length: 0,
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 (no:COMPATIBILITY) and _() will error.
The reference to the utility library itself (the global _
object) is
accessible via Sqimitive._
.
col._().filter(...).sort(...).value() //=> array of children
// Same as:
var _ = Sqimitive._
_.chain(col.toArray()).filter(...).sort(...).value()
// Same as:
_.sort(col.filter(...), ...)
Defined in: main.js, lines 6613-6615 (3 lines) • Show code
_: function () {
return _.chain(this.slice())
},
Modifiers: protected
Part of: tag_Nesting
Return an automatic (implied) key for the given to-be child.
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. Usage within base Sqimitive allows it to have side effects
(such as to produce sequential keys).
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 (sup, sqim) {
return sqim.get('id')
},
},
})
from unordered
Attention: JavaScript objects are unordered, as explained in 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, bb:Model-idAttribute names the property used for a similar purpose but in Sqimitive it’s a function allowing flexible automatic naming.
Defined in: main.js, lines 6177-6179 (3 lines) • Show code
_defaultKey: function (sqim) {
return sqim._cid
},
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 (context for all ...Func
is this
):
Name | Types | Notes |
---|---|---|
newFunc | function returning a constructed child; receives response data object (assignResp() is still called so it’s usually unneeded) | |
null /omitted create using _childClass of this | ||
eqFunc | null /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 | ||
keepMissing | true 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 | ||
keyFunc | null /omitted use _defaultKey() of this (returns _cid by
default) | |
function returning key (string or number); receives a
constructed Sqimitive (before it’s nest’ed) and resp data object | ||
posFunc | null /omitted provide no explicit pos | |
function returning
child’s pos ’ition in Ordered.nestEx; same arguments as keyFunc | ||
unFunc | null /omitted use unlist | |
function receiving a nested child
plus some junk arguments; the child must become unnested when unFunc
returns (no async operation) |
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 .Some members of If |
assignchreassignChildren() “unserializes” own _children objects while assignResp() “unserializes” own _opt’ions (“attributes”).
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'})
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
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()
opt
(in new
/init) and gets
them via a later assignResp(). Use newFunc
to supply some opt
’ions
early but remember that for existing children (eqFunc
) only
assignResp() is called:
var data = [{foo: 1, bar: 2}, {foo: 3, bar: 4}]
assignChildren(data, {
newFunc: resp => new ChildClass(_.pick(resp, 'foo', _.forceObject)),
// Optionally, if we don't want foo to be considered by assignResp():
schema: {foo: false},
// Or, if a suitable schema is declared on ChildClass:
schema: 'schemaProp',
})
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 by
options.newFunc
, 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
.
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 (now, 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
resp
resp
is either an array of objects or an “object of objects” – then
it’s converted using no:values() ignoring the keys. Use keyFunc
if
you want to have the object’s keys (or any other keys) assigned to
children:
var resp = {"foo": {opt: 123}, "bar": {opt: 456}}
// Since assignChildren() discards resp's keys, copy them to each object:
_.each(resp, (opt, key) => opt._key = key)
col.assignChildren(resp, {keyFunc: (child, opt) => opt._key})
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
https://flask.palletsprojects.com/en/1.1.x/security/#json-security.
from unordered
Attention: JavaScript objects are unordered, as explained in 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.
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.Defined in: main.js, lines 6929-6980 (52 lines) • Show code
assignChildren: function (resp, options) {
options || (options = {})
options.assignChildren = true
var eqFunc = options.eqFunc
var keyFunc = options.keyFunc || this._defaultKey
var unFunc = options.unFunc || this.unlist
if (eqFunc == null) {
var retUnlisted = options.keepMissing ? {} : this.nested()
options.keepMissing || this.forEach(unFunc, 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 child = options.newFunc
? options.newFunc.call(this, resp[i]) : new this._childClass
child.assignResp(resp[i], options)
var res = this.nestEx(_.assign({}, options, {
key: keyFunc.call(this, child, resp[i]),
child: child,
pos: options.posFunc
? options.posFunc.call(this, child, resp[i]) : null,
}))
// Can't use _parentKey if not _owning.
nested.push(res)
}
}
options.keepMissing || _.forEach(toRemove, unFunc, this)
return [nested, retUnlisted /* when !eqFunc */ || toRemove]
},
Part of: tag_Options
Unserializes options: updates own _opt in batch based on a resp
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) – all of this in abatch().
resp
is an object where one member usually represents one option (but
this is not a requirement).
options
keys used by this method:
Name | Types | Notes |
---|---|---|
schema | null/omitted to use _respToOpt | |
object in _respToOpt format
including empty {} | ||
string own property name | ||
onlyDefined | null/omitted to call set(key) for every key in resp not
listed in schema | |
true to ignore such keys |
Types | Notes |
---|---|
array of changed option names (as reported by ifSet() called by
assignResp(), not by schema ’s functions; doesn’t list .props ) |
from assignchre
assignChildren() “unserializes” own _children objects while assignResp() “unserializes” own _opt’ions (“attributes”).
{}
) 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 - useful as mass-set().
sqim.assignResp({date: '2000-01-01', id_key: '123'}, {schema: {}})
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}.
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
If storing schema(s) within the object itself, set options.schema
to
the property name:
var MyModel = Sqimitive.Base.extend({
apiSchema: {foo: 'bar'}
})
var obj = new MyModel
obj.assignResp(apiResp, {schema: 'apiSchema'})
// Equivalent to:
obj.assignResp(apiResp, {schema: obj.apiSchema})
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}
schema
. If
overriding this method, don’t change options
for such calls. For
example, 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) {
if (!options || !options.schema) {
options = _.extend({}, options, {onlyDefined: true})
}
return sup(this, [resp, options])
},
},
})
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 _respToOptYou 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:
=normalize_id_key
then assignResp() works as expected
while set('id_key', 'zabzab')
doesn’t trigger an error._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, as explained in the description of Ordered.
assignResp() may process resp
and call set() in any order.
from assignname
Defined in: main.js, lines 7220-7256 (37 lines) • Show code
assignResp: function (resp, options) {
return this.batch(null, function assignResp_() {
options || (options = {})
options.assignResp = true
var changed = []
var schema = this[options.schema] || options.schema || this._respToOpt
var set = schema[''] && schema[''].call(this, resp, options) || {}
_.forEach(set, function (v, k) {
this.ifSet(k, v, options) && changed.push(k)
}, 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) || [false]
value = opt[1]
opt = opt[0]
}
if (opt !== false) {
if (opt[0] == '.' && key[0] != '.') {
this[opt.substr(1) || key] = value
} else if (this.ifSet(opt, value, options)) {
changed.push(opt)
}
}
}
return changed
})
},
Part of: tag_Lifecycle
Attach the object to its parent and bind “external” event handlers (elEvents).
from renderAttach
Types | Notes |
---|---|
baseAttachobject | this |
Here’s a typical Sqimitive object lifecycle:
new
: new Class({opt...})
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.
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: main.js, lines 5250-5252 (3 lines) • Show code
attach: function (parent) {
return this
},
Part of: tag_Events
Calls the method func
with arguments args
on every _parent
(recursively, if any) and optionally onSelf
.
Types | Notes |
---|---|
bubsinthis |
If onSelf
is true
then first calls func
on this
, if defined.
bubble() proceeds upstream while sink() descends downstream. However, the first walks only _owning sqimitives (since it’s using _parent) while the second works for non-_owning too (since it’s using _children).
You can “recursively” fire events as well since events typically create
methods (evt; see _wrapHandler()
’s source code for details).
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.
// 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: main.js, lines 7304-7308 (5 lines) • Show code
bubble: function (func, args, onSelf) {
onSelf && this[func] && this[func].apply(this, args)
this._parent && this._parent.bubble(func, args, true)
return this
},
Part of: tag_Options
Called to notify that the value of some _opt’ion (opt
) has changed
from old
to now
.
from changeAndOpt
ifSet normalizes (normalize_OPT) new option’s value (now
) 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().
With new
being a reserved word, it’s customary to name the first
argument “now
” rather than “value
” to clearly indicate which argument
is the new value (“now” and “old”).
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-',
},
var MyClass = Sqimitive.jQuery.extend({
el: {tag: 'form'},
_opt: {
caption: '',
body: '',
},
events: {
// When any option is changed - update the corresponding <input>.
change: function (name, now) {
// change_OPT doesn't receive option's name as the first argument.
//
// now is used as is - if clean-up is required then normalize_OPT
// events must be handled.
this.$('[name="' + name + '"]').val(now)
},
},
})
options
(optpropag):
sqim.on('change_foo', function (now, 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: main.js, line 5704
Part of: tag_Options
Called to notify that the value of _opt’ion named OPT
has changed
from old
to now
.
changeAndOptifSet normalizes (normalize_OPT) new option’s value (now
) 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().
With new
being a reserved word, it’s customary to name the first
argument “now
” rather than “value
” to clearly indicate which argument
is the new value (“now” and “old”).
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-',
},
var MyClass = Sqimitive.jQuery.extend({
el: {tag: 'form'},
_opt: {
caption: '',
body: '',
},
events: {
// When any option is changed - update the corresponding <input>.
change: function (name, now) {
// change_OPT doesn't receive option's name as the first argument.
//
// now is used as is - if clean-up is required then normalize_OPT
// events must be handled.
this.$('[name="' + name + '"]').val(now)
},
},
})
options
(optpropag):
sqim.on('change_foo', function (now, 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: main.js, line 5613
Modifiers: constructor
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...})
.
This method ensures opt
is always an object before passing it on so
there is no need for checks like (opt && opt.foo)
.
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: main.js, lines 5062-5073 (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 || ap.unshift.call(arguments, {})
Sqimitive.Base.__super__.constructor.apply(this, arguments)
this.init.apply(this, arguments)
this.postInit.apply(this, arguments)
},
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.
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:
findIndex()
util.this
has duplicate children
(non-_owning), even if Ordered.sqim
, findKey() does a quick check on
_parent without iterating over children.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, as explained in 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: main.js, lines 6541-6553 (13 lines) • Show code
findKey: function (func, cx) {
var eq = func instanceof Object
if (eq && this._owning) {
return func._parent === this ? func._parentKey : undefined
}
for (var key in this._children) {
if (eq
? this._children[key] === func
: func.call(cx || this, this._children[key], key, this._children)) {
return key
}
}
},
Part of: tag_Options
Returns the value of one _opt or values of all of them (if no argument).
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!
Other notes:
new MySqim(orig.get())
sans _initToOpt considerations. Don’t use clone orig
(new object
will have wrong state, e.g. references to the original _parent).from getnset
title: function () { return this.get('title') }
from getmul
(['foo', 'bar'])
to retrieve multiple properties as an
array. Use _.pick(this.get(), 'foo', 'bar', _.forceObject)
to
retrieve them as an object.from unordered
Attention: JavaScript objects are unordered, as explained in the description of Ordered.
There are no guarantees in which order options would be iterated over.
Defined in: main.js, lines 5459-5461 (3 lines) • Show code
get: function (opt) {
return arguments.length ? this._opt[opt] : _.assign({}, this._opt)
},
Part of: tag_Options
Performs batch’ed get() and/or set() on multiple options.
Name | Types | Notes |
---|---|---|
toGet | string _opt name | |
array of names | ||
toSet | string _opt name | |
array of names | ||
null/omitted assume
toGet | ||
toReturn | string _opt name | Values for names prefixed with - are obtained before
setting toSet (- is ignored by toGet /toSet ). |
array of names | ||
null/omitted assume
toSet | ||
func | Given G arguments (G = length of toGet ), returns an array
or a single value (if toSet is a string) – option value(s) to set.
Returned array’s length must be ≤ length of toSet . Missing members of
toSet are not set.If | |
cx | object | The context for func |
null/omitted use this |
Types | Notes |
---|---|
array of values if toReturn is an array | |
mixed single value if not |
Other notes:
toGet
).(['foo', 'bar'])
to retrieve multiple properties as an
array. Use _.pick(this.get(), 'foo', 'bar', _.forceObject)
to
retrieve them as an object.// Multi-get() - but ensure given arrays have no properties beginning with '-':
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 each option is set to its current value.
// Returns new value of opt2 (same as value of opt1):
sqim.getSet('opt1', 'opt2')
// Similar but returns this:
sqim.set('opt2', sqim.get('opt1'))
// Similar but returns value of opt3, not opt2 (opt2 == opt1):
sqim.getSet('opt1', 'opt2', 'opt3')
// Value exchange:
sqim.getSet(['left', 'right'], ['right', 'left'])
// Equivalent to:
var temp = sqim.get('left')
sqim.set('left', sqim.get('right'))
sqim.set('right', temp)
// Temporary change:
var old = sqim.getSet(['foo', 'bar'])
try {
// ...
} finally {
sqim.getSet(['foo', 'bar'], () => old)
}
-
:
// toReturn defaults to toSet which defaults to toGet. Returns old + 1:
var newRound = sqim.getSet('round', old => old + 1)
// Dash prefix in toReturn causes pre-toSet values to be returned (i.e. old):
var oldRound = sqim.getSet('round', 'round', '-round', old => old + 1)
// Given dashes in toGet/toSet are ignored, this is an equivalent:
var oldRound = sqim.getSet('-round', old => old + 1)
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)
Sqimitive.Base.extend({
_opt: {position: 0, max: 0},
events: {
change_max: function (now) {
this.getSet('position')
},
'+normalize_position': function (res, now) {
return Math.min(now, this.get('max'))
},
},
})
sqim.set('position', 10)
sqim.set('max', 5)
sqim.get('position') //=> 5
Defined in: main.js, lines 5368-5393 (26 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]
}
// Array version of 1st/2nd/3rd argument, 0/1 with removed '-' prefix.
var a = args['a' + i] = _.isArray(args[i]) ? args[i] : [args[i]]
i < 2 && a.forEach(function (s, i) { a[i] = s.substr(s[0] == '-') })
}
var got = _.map(args.a0, this.get, this)
var preGot = {}
args.a2.forEach(function (opt, i) {
opt[0] == '-' && (preGot[i] = this.get(opt.substr(1)))
}, this)
if (args[3]) {
got = args[3].apply(args[4] || this, got)
if (!_.isArray(args[1])) { got = [got] }
}
this.assignResp(_.object(args.a1, got), {schema: {}})
return _.isArray(args[2])
? _.assign(_.map(args[2], this.get, this), preGot)
: 0 in preGot ? preGot[0] : this.get(args[2])
},
Part of: tag_Options
Changes the value of one _opt’ion and returns true
if it was
different from current.
Types | Notes |
---|---|
true if events were fired (value different or options.forceFire
set) | |
false otherwise |
sqim.ifSet('key', 'foo') //=> true (changed)
sqim.ifSet('key', 'foo') //=> false (unchanged)
sqim.ifSet('key', 'foo', {forceFire: true}) //=> true (forced change)
ifSetSetFires normalize_OPT giving it value
; if the result is not isEqual
to this._opt[opt]
or if options.forceFire
is set – changes _opt
and fires batch’ed change_OPT event, then change.
if (sqim.ifSet('eventsBound', true)) {
// eventsBound was previously not isEqual() 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:
forceFire
because it calls not only your object’s handlers
(which you expect) but also other instances’ (which you usually don’t).this
if you don’t care for the return value
but want method chaining.set()
version that writes multiple options at once. You
might be looking for assignResp(), possibly with schema
(useful when
assigning an API response): assignResp(opts, {schema: {}})
-
equivalent to batch’ed bunch of set()-s. Generally, consider
assignResp() if you find yourself writing a series of set()
.this._opt
immediately but subsequent
change_OPT and change events are batch’ed in FIFO fashion (first
set – first fired). This preserves “incremental” update order but also
means that set()
is not always immediately followed by a call to these
handlers.OPT
receive the same options.batchID
even if you don’t
wrap the ifSet() call into a batch().title: function () { return this.get('title') }
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 change what is called readOnly, you know!')
},
},
})
Hooking ifSet() specifically allows immediate, not batch’ed reaction
(but consider using priority^
– see evtref). options
will lack
some fields at this point though (_batchOptions()).
'-ifSet': function (opt, value, options) {
console.log('Option ', opt, ' is about to change to ', value)
},
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 (now, options) {
options.foo = 123
},
})
var options = {}
sqim.set('foo', options)
alert(options.foo) //=> 123
Defined in: main.js, lines 5841-5876 (36 lines) • Show code
ifSet: function (opt, value, options) {
options || (options = {})
var old = this._opt[opt]
// This should be an event but for sloppy code check and call directly if
// it's a method.
var func = 'normalize_' + opt
if (func in this) {
this[func] && (norm = this[func](value, options))
} else {
// Hooks may exist with no func if _wrapUndeclared is false.
var norm = this.fire(func, [value, options])
}
// undefined result can be made special since normalize_OPT is usually a
// firer() and there's (almost) no way to return undefined from a hook
// (see Core.fire()). If we did get undefined it means no hooks returned
// any value, i.e. the original value is good.
//
// Consider this example where the only normalize_foo hook is used to
// validate the value, not normalize it:
//
// events: {
// normalize_foo: function (now) {
// if (!now) { throw new Error(...) }
// },
norm === undefined || (value = norm)
if (options.forceFire || !this.isEqual(value, old)) {
this._opt[opt] = value
this.batch(null, function (id) {
options = this._batchOptions(id, options)
this._batch.push(['change_' + opt, value, old, options])
this._batch.push(['change', opt, value, old, options])
})
return true
}
},
Part of: tag_Lifecycle
Resolves _childClass and sets _opt’ions from opt
.
Arguments of init() and postInit() that follows it 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 in batch by giving assignResp() opt
(or {}
) and
schema
of _initToOpt. By default, _opt keys missing from opt
remain with the declaration-time values while opt.el
is ignored even
if present.
Other notes:
this
so subsequent constructors of this
’ class no longer need to do
it.from plainstub
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: main.js, lines 5121-5138 (18 lines) • Show code
init: function (opt) {
var childClass = this._childClass
// Can't be string - converted to array by Core::extend().
if (_.isArray(childClass)) {
var path = childClass[1].split('.')
childClass = childClass[0]
while (childClass && path[0]) {
childClass = childClass[path.shift()]
}
if (!childClass) {
throw new ReferenceError('init: _childClass by path not found')
}
this.constructor.prototype._childClass = childClass
}
this.assignResp(opt, {schema: '_initToOpt'})
},
Part of: tag_Utilities
Returns true
if both arguments are “same enough” and ifSet() should
not change _opt and fire change_OPT.
Default implementation compares arguments using strict equality operator
(===
).
Remember that in JavaScript, NaN !== NaN
so sqim.set('num', NaN)
will fire change_OPT even if num
is already NaN
.
==
if you’re ready for flaky
behaviour (see no:isEqual()). un:isEqual() is another option.
var MyBase = Sqimitive.Base.extend({
events: {
'=isEqual': function (sup, a, b) {
return _.isEqual(a, b)
},
},
})
from normiseq
For example, if foo
is an unordered array of numbers then
normalize_OPT can prevent change_OPT if new value has the same
numbers by returning current value which by default isEqual (===
)
to itself:
'+normalize_foo': function (res, value) {
return (value = _.unique(value)) + '' == this.get('foo') ? this.get('foo') : value
},
On the other hand, isEqual() may prevent NaN
from being inequal to
itself, for all _opt’ions:
'+isEqual': function (res, a, b) {
return res || (isNaN(a) && isNaN(b))
},
Defined in: main.js, lines 5904-5906 (3 lines) • Show code
isEqual: function (a, b) {
return a === b
},
Part of: tag_Nesting
Adds a new child using a shorter syntax than nestEx().
Types | Notes |
---|---|
sqim | The added child. |
Name | Types | Notes |
---|---|---|
key | string | If omitted, _defaultKey() is used
which by default returns sqim ’s _cid (unique instance identifier). |
number | ||
omitted | ||
sqim | object | New child to nest. |
opt | object | In place of sqim , only when _childClass is not
Object – a plain {} object of opt ’ions for _childClass’
constructor. |
options | object | Keys key and child are set by
nest() |
null/omitted |
sqim
vs opt
:
var Parent = Sqimitive.Base.extend({
// Inherited _childClass is Sqimitive.Base.
})
var parent = new Parent
parent.nest(new Sqimitive.Base({foo: 123}))
// Equivalent to:
parent.nest(new parent._childClass({foo: 123}))
// Equivalent to:
parent.nest({foo: 123})
// Constructing with default (no) opt:
parent.nest(new Sqimitive.Base())
// Equivalent to:
parent.nest({})
If you are overriding the “nesting” behaviour you should override nestEx instead of nest which calls the former.
from nestExDesc
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
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:
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).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().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:
true
(as it is by default), every child of this
can
have exactly one _parent thus forming a bi-directional tree (see
chld). nestEx() calls unnest() on the old and new children (which
call unnested() in turn) and updates _parent and _parentKey of the
new child.false
, children don’t know they are part of this
.
nestEx() simply calls unnested() on self if there was any previous
child at key
.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():
child
is already contained in this instance under the
same key
, i.e. when options.previous == child
(changed
is
false
).child
again if key
differs.Defined in: main.js, lines 5950-5965 (16 lines) • Show code
nest: function (key, sqim, options) {
var sqimIndex = +!(key instanceof Object)
if (arguments[sqimIndex].constructor === Object &&
this._childClass !== Object) {
// function (..., opt, ...)
arguments[sqimIndex] = new this._childClass(arguments[sqimIndex])
}
if (!sqimIndex) { // function ( sqim|opt [, options] )
ap.unshift.call(arguments, this._defaultKey(arguments[0]))
}
options = _.assign({}, arguments[2] || {}, {
key: arguments[0],
child: arguments[1],
})
return this.nestEx(options).child
},
Part of: tag_Nesting
Adds a new child to self (_children), unnest’ing the old one at the same key (if any).
Types | Notes |
---|---|
object | options with added details about the operation. |
The caller must set these keys in options
:
Name | Types | Notes |
---|---|---|
child | object | A sqimitive to nest. |
key | string | New options.child ’s key in _children |
number |
nestEx() mutates and returns options
with updated keys:
Name | Types | Notes |
---|---|---|
key | string always | |
previous | object | |
undefined | ||
changed | bool | 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
.
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
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:
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).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().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:
true
(as it is by default), every child of this
can
have exactly one _parent thus forming a bi-directional tree (see
chld). nestEx() calls unnest() on the old and new children (which
call unnested() in turn) and updates _parent and _parentKey of the
new child.false
, children don’t know they are part of this
.
nestEx() simply calls unnested() on self if there was any previous
child at key
.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():
child
is already contained in this instance under the
same key
, i.e. when options.previous == child
(changed
is
false
).child
again if key
differs.child
if this
is non-_owning so it may duplicate in
_children. To avoid this, hook =nestEx
and call sup
(evtpf) only
if !this.includes(options.child)
(util).Defined in: main.js, lines 6077-6111 (35 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, key) : this.length++
}
this._forward('.', this._childEvents, sqim)
this._owning && sqim.owned()
}
this._nested(options)
return options
},
Part of: tag_Nesting
Returns a single child by key or instance or all _children (if no argument).
Types | Notes |
---|---|
object all children if key not given | |
object the found child | |
undefined if given key /child isn’t nested in this |
Name | Types | Notes |
---|---|---|
key | omitted 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() |
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
key
form allows using nested() as no: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, as explained in 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: main.js, lines 6424-6434 (11 lines) • Show code
nested: function (key) {
if (!arguments.length) {
return _.assign({}, 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
}
},
Part of: tag_Options
Called to normalize and/or validate new _opt’ion value
before it’s
set. Returning undefined
keeps it unchanged.
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().
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'
For example, if foo
is an unordered array of numbers then
normalize_OPT can prevent change_OPT if new value has the same
numbers by returning current value which by default isEqual (===
)
to itself:
'+normalize_foo': function (res, value) {
return (value = _.unique(value)) + '' == this.get('foo') ? this.get('foo') : value
},
On the other hand, isEqual() may prevent NaN
from being inequal to
itself, for all _opt’ions:
'+isEqual': function (res, a, b) {
return res || (isNaN(a) && isNaN(b))
},
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
options
(optpropag):
sqim.on('change_foo', function (now, 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: main.js, line 5496
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.
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:
Defined in: main.js, line 6181
Part of: tag_Lifecycle
Called after init() to bootstrap the new instance after constructor’ing.
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).
Other notes:
from initonce
var MySqimitive = Sqimitive.Base.extend({
_button: null,
events: {
init: function () {
this._button = this.nest(new MyButton)
},
postInit: function () {
this._button.startAnimation()
},
},
})
Defined in: main.js, line 5140
Part of: tag_Nesting, tag_Lifecycle
Removes el and calls unnest().
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.
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.
Defined in: main.js, lines 6655-6657 (3 lines) • Show code
remove: function () {
return this.unnest()
},
Part of: tag_Lifecycle
Populate from scratch or update the object’s “view” – el.
Types | Notes |
---|---|
renderAttachbaseAttachobject | this |
Here’s a typical Sqimitive object lifecycle:
new
: new Class({opt...})
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.
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: main.js, lines 5234-5237 (4 lines) • Show code
render: function () {
this.invoke('attach')
return this
},
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.
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
Fires normalize_OPT giving it value
; if the result is not isEqual
to this._opt[opt]
or if options.forceFire
is set – changes _opt
and fires batch’ed change_OPT event, then change.
if (sqim.ifSet('eventsBound', true)) {
// eventsBound was previously not isEqual() 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:
forceFire
because it calls not only your object’s handlers
(which you expect) but also other instances’ (which you usually don’t).this
if you don’t care for the return value
but want method chaining.set()
version that writes multiple options at once. You
might be looking for assignResp(), possibly with schema
(useful when
assigning an API response): assignResp(opts, {schema: {}})
-
equivalent to batch’ed bunch of set()-s. Generally, consider
assignResp() if you find yourself writing a series of set()
.this._opt
immediately but subsequent
change_OPT and change events are batch’ed in FIFO fashion (first
set – first fired). This preserves “incremental” update order but also
means that set()
is not always immediately followed by a call to these
handlers.OPT
receive the same options.batchID
even if you don’t
wrap the ifSet() call into a batch().title: function () { return this.get('title') }
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 change what is called readOnly, you know!')
},
},
})
Hooking ifSet() specifically allows immediate, not batch’ed reaction
(but consider using priority^
– see evtref). options
will lack
some fields at this point though (_batchOptions()).
'-ifSet': function (opt, value, options) {
console.log('Option ', opt, ' is about to change to ', value)
},
Defined in: main.js, lines 5489-5492 (4 lines) • Show code
set: function (opt, value, options) {
this.ifSet(opt, value, options)
return this
},
Part of: tag_Events
Calls the method func
with arguments args
on all nested _children
(recursively) and optionally onSelf
.
from bubsin
Types | Notes |
---|---|
this |
If onSelf
is true
then first calls func
on this
, if defined.
bubble() proceeds upstream while sink() descends downstream. However, the first walks only _owning sqimitives (since it’s using _parent) while the second works for non-_owning too (since it’s using _children).
You can “recursively” fire events as well since events typically create
methods (evt; see _wrapHandler()
’s source code for details).
Note that it might get quite intense with heavy nesting.
// 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: main.js, lines 7340-7344 (5 lines) • Show code
sink: function (func, args, onSelf) {
onSelf && this[func] && this[func].apply(this, args)
this.invoke('sink', func, args, true)
return this
},
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.
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()
:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
from nestedslice
nested() without arguments returns an object – children with keys. slice() returns an array – children only, dropping their keys.
from unordered
Attention: JavaScript objects are unordered, as explained in 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: main.js, lines 6492-6499 (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)
},
Part of: tag_Utilities
Returns _children as an array in order specified by comparison func
.
Name | Types | Notes |
---|---|---|
func | function | Receives two members, must return -1 /+1 if first
member should go before/after second or 0 if order doesn’t matter
(may cause non-deterministic result). |
cx | object | The context for func |
null/omitted use this |
This differs from standard Array
’s sort()
that works in-place and
doesn’t accept cx
.
Use sortBy()
util to compare based on weight values returned by
func
.
nest({foo: 5})
nest({foo: 1})
sort(function (a, b) { return a.get('foo') - b.get('foo') })
//=> [ {foo: 1}, {foo: 5} ]
sortBy(function (a) { return a.get('foo') })
//=> same order
Defined in: main.js, lines 6582-6584 (3 lines) • Show code
sort: function (func, cx) {
return this.slice().sort(func.bind(cx || this))
},
Part of: tag_Nesting
Removes a child by its key or instance from own _children.
Types | Notes |
---|---|
object removed sqimitive | |
undefined nothing found |
Name | Types | Notes |
---|---|---|
key | string or number key which to clear | |
null to do nothing | ||
object instance to remove as per findKey() |
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: main.js, lines 6246-6266 (21 lines) • Show code
unlist: function (key) {
if (key instanceof Object) {
key = this.findKey(key)
}
if (key != null) {
var sqim = this._children[key += '']
}
if (sqim) {
if (this._owning) {
sqim.remove()
} else {
delete this._children[key]
--this.length
this.unnested(sqim, key)
}
}
return sqim
},
Part of: tag_Nesting
Removes this instance from its _parent object, if any.
Types | Notes |
---|---|
this |
unnest() does nothing if _parent of this
is already null
(i.e.
not owned).
Note: it normally 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.
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
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:
null
; one such
example is nestEx() that unconditionally calls unnest() on new
child.(this)
on the former parent.this
has nested
_children) or a leaf (if not) – more in Children concept (chld).Defined in: main.js, lines 6323-6333 (11 lines) • Show code
unnest: function () {
var parent = this._parent
if (parent) {
var key = this._parentKey
delete parent._children[key]
this._parent = this._parentKey = null
--parent.length
parent.unnested(this, key)
}
return this
},
Part of: tag_Nesting
Called right after a child (sqim
) was detached from its parent (this
)
where it was listed under key
.
unnestedoffBase’s implementation of unnested() 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
.
Former key
is useful for non-_owning collections, especially when it
may have duplicates (same child object under different keys).
var MyParent = Sqimitive.Base.extend({
events: {
unnested: function (sqim, key) {
alert('Whence thou goeth, my ' + key + '...')
},
},
})
Other notes:
sqim
.unnest().this
.unlist() and by
this
.nestEx() when nesting a new child under an occupied key.from plainstub
Defined in: main.js, lines 6371-6378 (8 lines) • Show code
unnested: function (sqim, key) {
sqim.off(this)
this._unnested(sqim, key)
if (this.length < 0 && console && console.error) {
console.error('Broken nesting: sqimitive.length below zero')
}
},