Fulfilled jquery.my instance, like $('#someSelector').my(manifest,data)
, establishes reactive binding between set of named UI controls and JSON-able data
object of known structure.
A manifest
is a JS object mapping which controls to bind with what data
branch, with additional props defining how to init all stuff, and how it should be styled.
Below doc is a copy of jquerymy.com/api.html.
jquerymy ($.my) is more a plugin than a framework. $.my works in a browser and runs browser side of a webapp. $.my does not include router, net functions, auth and security functions and so on. CloudWall is an example of a complete framework built around $.my and CouchDB.
One app — one object. Any $.my app, its behavior and facade, are defined using single standard javascript object named manifest. An app is by nature a successfully instantiated manifest. Standard JS object as an app description means you need not learn new syntax rules or list of directives. All plugins are applied using their standard APIs, all code is good old-fashion javascript.
Extension-friendly. jQuery.my understands a lot of rich-ui plugins out of the box, and can be extended to understand new solutions.
JSON-friendly. $.my manifests are JSONable and though are very well suited for modern noSQL DBs. One doc — one app, no additional files or resources. Since apps are JSONs, they can be manipulated as JSONs.
Recursive. Any instance of $.my form can act as a single control for another form, it allows complex nested composite apps. Children manifests are just properties of a parent manifest.
Robust. $.my is IE12+ compatible and fault-tolerant: when child form or any control fails, its parent form stays more or less operational.
jQuery.my requires Sugar.js 1.4~ (not 1.5 or 2.x) and jQuery.js 2.0+ preloaded. You can use CDNJS repo for all required components:
$.my forms are initialized and managed during runtime according to jQuery standard recommendations for plugins.
General syntax is $(DOM_node).my("command", param1, param2)
. If the first argument isn’t a string, then $.my assumes "init"
command requested.
Above code generates below app (manifest instance bound to a variable reflecting state):
Properties of person
variable reactively reflect states of two HTML controls. When a control receive input, person
is mutated.
String bindings like 'metrics.age'
are just syntax sugar. Strings 'arrayName.3'
to bind with an array rows are also ok.
Variable manifest
, the first argument for $.my invocation, defines form facade and bindings. Second argument, variable person
, is data object form is bound to. Good to think data
property of an app instance as the app state.
$('#app').my('data')
returns a reference to dynamically updated JS object bound to UI controls. When data is updated change
event is fired on form’s DOM node. Generally, object returned is ‘classic’ javascript object and is any-time serializable.
$('#app').my('data', { /* Data */ })
updates app data object, and recalculates UI. New data obj is merged deep with current app data obj— so update can be partial. For previous code example $('#app').my('data', {name:'John'})
would change only name
prop of person
variable.
If app data is an external object (like person
variable passed as data object in the above example), external mutation of the var would not redraw the app automatically. You must call $('#app').my('redraw')
explicitly to make the app know that data was updated.
Manifest is a standard javascript object, that has some reserved properties and can have unlimited number of custom keys. Reserved property names are (in AZ order):
data
Default data object, at runtime reflects state of a particular app instancedie
Function called on app disband, arguments are same to init callexpose
Obj, array or string, list of nodes allowed to be inherited by app childrenfiles
Inlined b64-encoded binary resources, each gets session URL on app startid
String, unique identifier of the manifest; autogenerated if omittedinherit
Object, array or string, the list of methods to inherit from parent appinit
Function, called just before building bindings; might return Promise for async initlang
Localization dictsmy
Non-enumerable dict of an app runtime methods, bound to the app instance.params
Object, defines form’s settingsradio
Obj, array or string — radio relay objectrequire
Array, list of resources required to start the formstyle
Object, defines CSS rules local for the formui
Required property, defines bindings between DOM and data. Object, each key is a valid jQuery selector of an app control. Value under each key defines control section for selected DOM element.Custom properties of a manifest, if any, better have Capitalized or camelCase keys to ensure no conflict with future versions of $.my.
The .ui
dict of a manifest is a simple js key/value. Each key is jQuery selector, and each value is a control section for the selected DOM node.
Property bind
of a control section should be a string reference or a function.
bind
assumed to be a dot-notation reference, pointing to a property inside app .data
object, or, if defined as 'this.SomeKey'
, the branch of the app runtime..data
obj.bind
is called any time control receives input or when $.my forced the control to update. The bind function receives three arguments: (dataObj, newValue, $control)
. dataObj
is a reference to form’s data
object. It‘s important to keep data
object anytime serializable to JSON.
If newValue
is null
, function must return value to put into the control. If not null
— binder function must store newValue
into appropriate branch of dataObj
and may return new/updated value for the control.
So bind
function acts like both getter and setter. The above example can be rewritten:
This example makes impossible to put anything but number into #age
input. Any non-num key makes no visible output— non-nums are stripped immediately.
Third argument, $control
, is a jQuery object of DOM control being processed. It has several extension properties and can be useful for navigation over form’s DOM tree. $control.my("find", "#name")
returns #name
control from inside the form, as jQuery object.
Also bind function receives this
which points to runtime version of app manifest.
Dependencies are defined using watch
and/or recalc
properties.
Both watch
and recalc
are shown. Syntax watch: "#num2, #num3"
means ‘When #num2 or #num3 changed, update me’. Syntax recalc: "#product"
means ‘When I was changed, update #product’.
If both external recalc
and own watch
are defined for a control, recalc
run first.
Controls can be cross-dependent. They even can be looped in dependency rings of 3+ more controls:
To keep recalc graph manageable, default dependency resolution depth is 2. Value can be increased for deep dependency trees or long loops using recalcDepth
property, that can be defined for any particular control.
By default, $.my tracks control’s events, that might indicate control’s update most frequently. But we may need less frequent updates. For example we may need data to be updated when textarea is blurred, not on every key press.
Events that trigger control’s update, are defined using events
property. It’s a string or an array of strings.
To ensure event listeners unbind when form is removed use .my
namespace postfix for event types.
Some controls raise update events very frequently. Binding on every single update can take excessive amounts of CPU and RAM. The delay
property defines minimum gap between subsequent bind
calls. If there is a series of events all raised in gaps smaller then delay
, only last event invokes bind
, other are suppressed.
When control is rebuilt during dependency recalc, its bind
is executed without any delay, in sync.
Control can be updated on pub/sub event. For example we received new items and need list of them to be redrawn. Pub/sub events reaction is defined in listen
property:
This control #filter
is redrawn when /list
channel receives any message or when /item
channel receives appropriate doctype. We assume that #filter
rebuilds list of items, and then #list
is redrawn.
Messages can be transmitted using $.my.radio(channel, msg)
for global broadcast or using $ctrl.trigger("radio", {channel:"channelName", message:msg})
for realm-controllable broadcast.
Validators are located in .check
properties of controls’ ui sections. For a simple control its validator can be a regexp, a function, or a list of acceptable control’s values. For nested lists the .check
prop can only be true
to propagate children errors to a parent form, or not present, to disable propagation.
Regexp validation can only indicate the fact of mismatch, message is predefined in the manifest (error
property of control‘s ui section). Same for array of allowed values.
Validator function can return custom error messages.
Validation takes place just before bind
call. When validation fails, closest DOM container of the control receives class .my-error
. Then $.my looks for an element with class .my-error-tip
within this container, makes it visible and puts error message inside. If no element found, title
attribute of the control receives the message.
For non-interactive controls (<div>
bound to data, for example) .my-error
class is applied to element itself, not to its container.
If the #name
input element has less then 3 chars its container receives class .my-error
.
.my-error-tip
receives validator message, so to make message visible there must be an element with my-error-tip
class inside control’s container.
Validator check
receives same arguments as bind
, but is executed before bind. Unlike bind
, check
is never called with value === null
.
$("#formObj").my("valid")
returns true
if all form’s controls are valid, and false
otherwise.
$("#formObj").my("errors")
returns an object. Keys are controls’ selector, and values are error messages. Form is valid if the object has no keys. Children forms’ error objects are attached under selectors the child was bind to parent form if and only if a child’s subform or list has .check:true
.
Object returned with this.my.errors()
from inside bind
is nearly the same, but may contain empty string values (which means control under key selector is valid).
Allows different CSS rules to be applied over control’s container when conditions are met. Condition is a regexp or a function. There exists special key :disabled
, that disables control when its condition is met.
Conditional formatting is applied after check
and bind
. To apply rule to control itself, not container, prefix self:
must be added to a rule key. Rule "self:red":function(){...}
is applied to the control itself, not to its container.
CSS classes .Red
and .Green
of above example are defined locally using style
property of the manifest. Those classes are translated into runtime CSS rules, which are applicable only to form’s inside — so different forms don’t interfere. One form can have class .Green
that is yellowish, and other — that is olive. They both are local.
When form starts, $.my creates unique <style>
tag in current document, and puts compiled CSS rules inside. To avoid <style>
dupes for every instance of a form, form’s manifest must have id
property.
Note leading spaces in keys. Spaces needed when key hierarchy is concatenated to produce rule. " .Red"
+ " input"
become something like
.my-manifest-1ftwlphd .Red input {background-color: #FCC; color: #C02;}
.
Rules can be defined as functions:
When a rule is defined as a function, it is calculated on start, just before init
function runs.
Also function-defined custom rules can be recalculated each time window
obj receives resize
event. The property restyle
of params
section of manifest defines delay between resize event and style recalc. By default (value is -1
) no restyle wired on resize — it can be costly.
The above example evaluates form width and returns different CSS rules if the form is wider than 500px.
New <style>
element is created for every single instance of a manifest with function-defined styles.
Every control’s bind section and entire manifest can have init
property, that is function.
All those functions run once when form starts, can be async and are useful for remote data retrieval, rendering HTML, applying rich ui plugins to controls and so on.
Any init function can return promise. Init assumed completed when promise is resolved. If returned value is not a promise, init
assumed completed immediately.
Code below builds HTML skeleton, loads external data and builds <select>
control, applies plugin to it and then fades the form in.
Note how code that fades the form in is subscribed to form init
successful finish.
Init function is invoked after manifest properties inherit
, require
, files
and style
are successfully processed (if any). So when init
starts, all resources are loaded, files have session URLs and styles are calculated and applied to form.
Note that radio
section and though pub/sub events are not processed unil entire form starts.
Every manifest‘s function sees own parent manifest via this
. Custom form functions of the first level of manifest‘s object, if any, are also bound to manifest as this
during form start.
Actually, runtime version of a manifest is, in general, slightly different from original, passed for app init. Some properties, written in shorthanded form, are unfolded, some string references are resolved and so on. These differences are insignificant in most cases although.
Manifest runtime object is not sealed or frozen. It means internal app functions can use this
to store intermediate results, that are not intended to live in main data
section.
Any form control can be also bound not only to childs of the data
section, but to this
-hosted data — just use bind:"this.MyProperty"
syntax. Data sections, that are childs of this
, are not restricted to be anytime-serializable, as the main data
section. They can have circular references, DOM objects and so on.
Since 1.2.0 any form‘s manifest receives additional non-enumerable property my
, that proxies several useful runtime methods. They are visible from inside manifest functions as:
Returns jQuery object of form‘s root DOM node.
Returns true
if form‘r root is in document DOM (form was not removed).
Returns runtime manifest object of the parent form, if any. ‘Parent’ here means the first DOM ancestor with .my-form
class.
Ajax call, by default uses standard jQuery syntax, returns jQuery promise.
Triggers commit
or cancel
event on form‘s root DOM node.
For children forms in lists returns zero-based index of the form in a parent list.
Inserts element in a list of forms under jQuery selector. If selector is omitted, insertion is performed on the first ancestor of the form‘s root (allows insert call from inside list elements).
The where
argument is a number or a string, position to insert. Strings "before"
and "after"
are allowed for insert calls from inside of other list elements.
Argument obj
is an object to insert.
Triggers check or recalc events on a control for a given jQuery selector. Looks up a control only inside the form.
Triggers given event on form‘s child.
Opens modal dialog, pivotted to "#node"
DOM object. Returns dialog‘s promise. All child dialogs are automatically closed on parent form‘s disband.
Other this.my
methods available are listed at External commands section, since they are mirroring appropriate commands.
Any $.my form can act as a control for other embracing $.my form — forms can be nested. Set of repeated child form bound to an array of data are also supported. Moreover, array can consist of different-typed items — and list can render different child forms for them.
When nested form is bound as a control, bind
property must be a string. If bind
points to an object, single child form is applied. If bind
points to an array, list of repeated forms is built.
Child forms’ init
functions can be async if they return a promise. In this case list start (and parent form binding) assumed successful when all promises are resolved.
If bind
property of a selector points to an array and there is manifest
property, $.my builds repeated list of child forms. Each form by default lives in container <div></div>
. To change container type or attributes, define container HTML in list
property.
Insertion/deletion are event-driven. To delete row any control from inside the row must trigger remove
event or this event can be fired over child form’s container.
Event insert
must be triggered to insert row. Insertion supports additional argument, that defines what to insert and where. $control.trigger ("insert", param)
. Param is a string or an object:
"before"
inserts empty row before caller row"after"
inserts row after{where: "before" | "after" | index, what: ObjToInsert}
— inserts ObjToInsert
. If where property is a number, object is inserted into this index. Sparse arrays are not supported, so {where:1e6}
inserts row at the end of the list.Sometimes data array consists of different-typed items that require different manifests to render. To allow runtime selection, manifest
property of a control section can contain a function. It receives each data item during runtime and must return suitable manifest.
Property stamp
or equivalent is required to differentiate items.
Dynamic lists functionality allows to build very complex ensembles of applications.
Control section, describing list of child forms, may have id
and/or hash
properties. Each can be a function, a string or an array of strings.
id
function receives item and it’s index as arguments and must return unique item’s id. When id
is a string, it’s assumed to be a pointer inside child form’s data object. If an array — list of pointers (values are concatenated).
hash
function receives same args, but must return unique hash, that changes when any significant item’s property is changed. In general if hash of child form’s data changed, the form is redrawn.
When no id
or hash
defined, sdbm code hash function from BerkeleyDB codebase is used.
If list‘s underlying array with data is modified externally, list must be rebuilt. There are two strategies of combining new and old (already rendered) data:
Ui section for a list can have property merge
, with values true
, false
of function (oldObj, newObj){ }
.
Property omitted or false
value turns on standard behavior — if new object received, new form is always created and this form is bound to new object.
If merge:true
, new data is overlapped over old if row was already rendered. Overlapping is like deep merge, exept arrays, that are taken from new obj entirely.
If a function given, it must modify oldObj
, that is 1st arg, using data from newObj
.
Sections inherit
and expose
manage what properties will manifest inherit from callee or root, or expose to child attached.
Property expose
restricts properties, that can be inherited by childs from current form. If no expose
defined, child forms can inherit any property of parent’s runtime.
Property inherit
defines, what properties must be inherited form parent.
Both properties can be comma-separated strings or arrays of strings.
require
property of a manifest defines a) external resources to load before form starts, b) local plugins required for a form to run.
require
section is an array of groups, each group is processed after previous one finished successfully.
All these checks and preloads are executed before any other form’s function run.
$.my allows to include binary resources into manifest as base64-encoded strings. Section files
of a manifest is a container for them.
All entries of the files
section are processed during form start and each obtain local session URL. Files become accessible from <img src=
or in links.
After form’s start session URLs are accessible as this.files["fname.ext"].url
from inside of any function.
files
section has structure very similar to CouchDB’s _attachments
section of a document.
There are several runtime settings, that has global defaults, but can be overridden using params
property of a manifest.
recalcDepth
and delay
properties can be also set for a selector’s control section.
Each manifest may have lang
section, a localization dictionary:
When form starts an appropriate dict branch is taken, and then merged with lang
section itself. So all app functions can access constants in a manner like this.lang.LANG_CONST
. Source locale branches are not removed, so all lang constants also accessible directly using this.lang.en.LANG_CONST
syntax.
Default locale can be set using $.my.locale('en'/*lang key*/)
globally or using params.locale
property for local override.
If no particular locale present in lang
section, or some locale constants are omitted, en
section is taken (entirely or partially). It means lang.en
section must always be present and complete, if lang
present.
Syntax is $obj.my("cmd", argument)
. $obj
is form instance or form’s control.
Form is manageable during runtime using form command $("#runningForm").my("command", arg)
. Available commands are:
.my("data")
returns link to a live data object.
.my("data", object)
deep merges object with form’s data and redraws form.
Disables/enables form or returns current state. $form.my("disabled", true)
marks all controls as inactive — form comes inactive. $form.my("disabled")
returns false
if the form is in active state.
Returns true
if all controls are in valid state, otherwise returns false
.
Returns {}
if all controls are valid, otherwise returns list of errors like {"#selector":"Error mesage", ...}
with all invalid selectors as keys.
Returns runtime version of form’s manifest.
Redraws the form.
Recalculates dynamic CSS rules for form and its children.
Removes the form. Unbinds all event listeners and removes all plugin instances attached. Returns form’s data.
$form.my("undo", steps)
rolls the form several steps
back. To use this feature params.history property of a manifest must be 1+ — no undo steps are remembered by default.
Returns jQuery data object "my"
, mounted on DOM node, is equivalent to $form.data("my")
. Properties of an object returned:
cid
unique form instance idmid
unique manifest id hashdata
points to form’s data objectid
manifest id, if it had any, or autogenerated idinitial
initial data object deep clonemanifest
form manifestparams
full list of form instance’s settingsui
internal extended ui section Not recommended. Using control‘s commands from inside running manifest‘s functions is not recommended. In those cases better use this.my[command](args)
syntax introduced in 1.2.0 instead.
Syntax — $ctrl.my("command", arg)
. Available commands are:
Returns jQuery object of DOM container of a control.
$ctrl.my("find", "#selector")
searches #selector
inside the form.
$ctrl.my("insert", where, what )
, if applied inside list item, inserts object what
at where
position of the list. To insert at the end — where:1e12
.
If applied inside list item — removes it form the list.
Returns control’s value.
Returns data object "my"
of the control. Properties are:
data
points to data object of the formerrors
points to error list of the formevents
, string, list of events trackedid
manifest idparams
form settingsroot
jQuery object of the formselector
control’s jQuery selectorui
control section.Manifests are single objects, without loop references and more or less scope independent. A manifest defines form’s facade and behavior more or less completely. So delivering a manifest you deliver an app.
If regexps and functions are represented as strings, manifest become valid JSON.
jQuery.my has service function $.my.tojson (obj)
, it converts objects with functions and regexps into JSON. This approach is not unique — functions are converted to strings in CouchDB for example.
$.my.tojson( {x: function () {}} ) >> '{"x": "function (){}"}'
Converting functions into strings we surely loose all scope info, but functions of a manifest are scope-aware in general.
Service function $.my.fromjson( extJsonString )
converts extended JSON into object with functions and regexps.
If manifest has id
property like "ns.Name1.Name2"
, it can be cached using $.my.cache( manifest )
. Cached manifest is available from inside of other forms by string reference like manifest: "ns.Name1.Name2"
.
Property id
of cacheable manifest must be string of latins in dot notation, have no spaces and two parts minimum. ns
is namespace string, Name1.Name2 .... NameN
chain is unique form name.
Use $.my.cache( formId )
to get a clone of cached manifest.
Property id
is a reference in dot-notation. It points to the branch of cache the manifest is put into.
When we first cache manifest of id:"app.Name"
, and then manifest of id:"app.Name.Component"
, first manifest in the cache receives property Component
, that is second manifest.
If we call $.my.cache( "app.Name" )
we obtain app.Name
manifest with app.Name.Component
manifest mounted into Component
property.
Components are like dlls, they can be hot-swapped.
$.my app instances and their controls can both emit messages and listen to pub/sub channels. Pub/sub technique is good for broadcasting events, that may have out-of-app impact. For example, our app loaded a lot of data and notify neighbors, that cache is updated and they are to rebuild item lists.
There are two modes of radio — global broadcast and realm-based broadcast. Listeners for both of them are identical — a control must have listen
object property in ui section, where keys are channels the control is subscribed to.
Difference is how radio event is submitted and propagated.
When radio packet is submitted using $ctrl.trigger("radio", {channel:"chName", message:message})
, transmission packet bubbles until it reaches either:
radio
object, which has event channel as a key, and a function under the key, which returns true
<body>
DOM node.If one is reached, event reflects back down and reaches all children listeners. So if an app wants some radio channel to stay inside app — its manifest must have radio
object property with the channel listed.
There is another way to broadcast — $.my.radio("chName", message)
. This command sends message to every channel subscriber across web page regardless if it’s screened with radio
of parent manifest or not.
The plugin exports several service functions. They are:
Returns jquerymy version like jQuery.my 1.2.6
Gets / sets locale. By default, locale is taken from the leading part of browser locale (mean en-GB
and en-US
both set internal locale to en
).
Converts obj into json. Unlike JSON.stringify converts functions and regexps.
Parse extJSON string and returns object, with functions and regexps if any.
Exec internal ajax request if obj passed. If function passed, replaces internal ajax implemetation ($.ajax by default) with external function.
Returns promise.
Select object properties by mask. Mask is an object, string reference or an array of references.
In no property exist, undefined
is returned.
Unfolds masked object into original.
Simple sdbm hash function, from Berkeley ndb. Non-cryptographic and though blazing fast.
Converts plain text CSS ruleset into object structured as style
manifest property.
jQuery.my plugin includes promise-based modal and non-blocking (popup) dialog implementation. Dialog can be $.my form, simple HTML or jQuery image.
Global modal is singletone and blocks all other inputs. Once opened global modal dialog blocks other global modals’ attempts to init — until closed.
Non-global popup dialogs are good for in-place pop-ups with info or settings for example. They are non-blocking.
Opens or closes modal. Both done done
and promise can handle dialog close. Unlike promise, done
can cancel popup close request. Promise is resolved or rejected on modal close.
Note, that since 1.2.6 $obj.modal
does not conflict with Bootstrap implementation if Bootstrap was initialized before $.my. BT and $.my calls are distinguishable by argumants provided, so if arguments looks suitable for BT, BT.modal
is invoked.
$pivot
object can be $.my control or just DOM node inside $.my form — when parent form is removed, all linked dialogs are cold-closed. $pivot geometry and position are used to align non-modal dialogs.
One $pivot
object can have only one linked and opened dialog at a time. If $pivot.data("modal")
returns object, $pivot
already have dialog opened.
Global blocking modal can be initialized calling $.my.modal(obj)
. This syntax opens global modal that is not linked with any form.
Object
Opens modal and renders $.my form inside. Properties:
manifest
form manifestdata
form data, mutated by dialog on user inputroot
jQuery object to hold modal’s DOM. Default is control’s container or parent form.width
dialog inside content widthesc
allows ‘cold’ close on Esc pressed — promise is rejected with string Cancelled
. Default is false
for non-modals and true
for blocking modals.enter
allows ‘warm’ close on Enter, default is false
nose
string, defines where dialog tip is placed — allowed positions are "left"
, "right"
, "top"
, "bottom"
align
string, determines modal position relative to pivot, like top:103%;left:0px;
that means modal, that is 103% of pivot height shifted from top and zero pixels from left of the pivotglobal
boolean, true
opens global blocking dialogscreen
boolean or string with CSS-compatible color. Defines if blocking screen div must be shown under dialogfocus
auto-focuses first control in the form after init, default is true
.drag
allows dragging the dialog if jQuery UI Draggable plugin is loaded. bound
Number, distance in pixels between modal and it’s root obj, default is false, that is no boundshardClose
Boolean, defines X close button behavior, if true
, closing is cold, if false
closing is warm and can be stopped by .done
functiondone
— function (formErrors, formValue), allows or denies warm close, this
inside the function points to modal form‘s manifest.Promise returned is resolved with form’s data if modal was ‘warm’ closed.
Boolean
Close dialog. Modal close has two scenarios — cold and warm.
Warm close request $.my.modal(false)
can be cancelled — function done(null or err, data)
, called just before close, can return true
to keep modal.
Cold close request $.my.modal(true)
invokes done (null, null)
and regardless of value returned rejects promise with "Cancelled"
value.
jQuery image
Image passed pops up with max available width, if no width
received.
HTML string
HTML content for modal.
Returns true
if modal is visible.
Sets or gets DOM container, that holds modal screen and modal itself. Default is "body"
.