|Herbert Vojčík e0b8e7427c Fix README.||1 week ago|
|.idea||4 years ago|
|example-counter||1 week ago|
|example-todo||1 week ago|
|src||1 week ago|
|.gitignore||2 years ago|
|.travis.yml||2 years ago|
|Gruntfile.js||1 week ago|
|LICENSE-MIT||10 months ago|
|README.md||1 week ago|
|bower.json||1 week ago|
|config-browser.js||1 year ago|
|config-node.js||1 year ago|
|deploy.js||2 years ago|
|devel.js||2 years ago|
|index.html||1 week ago|
|jquery.amd.json||2 years ago|
|local.amd.json||2 years ago|
|package.json||1 week ago|
|testing.js||2 years ago|
|trapped.png||4 years ago|
|xontent.amd.json||2 years ago|
Inspired by the idea that was present in AngularJS, Trapped is an Amber that creates bidirectional data-biuding between elements in HTML page (the view side) and a data object (the model side).
Similarly to AngularJS, you write expressions specifying what data
to bind to and how to transform it directly in HTML
data-trap attribute, and changes to those data are
automagically communicated there and back.
Easy way (but you cannot save and reload with new code):
Visit the classic Counter example at http://www.herby.sk/trapped/example-counter/counter.html or simple Todo similar to one in AngularJS page at http://www.herby.sk/trapped/example-todo/todo.html.
Hard way (but you can save and reload with new code):
Clone this repo. Run
bower install to get amber, which is a dependency.
Get the cli to start amber server:
npm -g install amber-cli.
Then start the server:
amber serve from the project root directory. It starts on port 4000.
http://localhost:4000/example-counter/counter.html (similarly for Todo example)
in your browser. Page opens, dialog to open IDE appears.
Trapped is closest to the MVVM variant of MVC. MVVM splits application into Model (the application logic and data loaded / saved from external sources), View (the presentation facilities, not containg any application logic) and ViewModel (the subset of data that is going to be presented at any point of time).
Trapped fills the place of the View and the ViewModel (View being synchronized with ViewModel bidirectionally), leaving you to build your Model any way you wish and abstracting presentation details and user events away from you by letting the Model just observe and manipulate contents of the ViewModel.
AppModelin the examples). You can build it any way you wish, it has to be able to hold all the data ViewModel may need to hold. It can also hold some methods for manipulation of these data, which is good for the example, but in real project this should be the responsibility of Model.
Appin the examples). This is more or less mechanical task - just create subclass of right base class and fill in
initializemethod appropriately, so that it wraps the real data (instance of the class from previous paragraph). This wrapper class implement the blackboard pattern - in which many external observers (called specialists) observe the data object (called blackboard), make partial changes and react to them. Elements of View are observers of the ViewModel blackboard, as should be the parts of the Model (this way, Model and View are completely decoupled and both see only changes to the blackboard, that is, the ViewModel).
data-trap) in which you describe what data to bind to (path) and how to process it in the way to the user or from the user (processors).
Trapped start: anArraywhere anArray should contain instances of all blackboards (most often you will only have one,
blackboardin the examples) that are intended to be used (
blackboard modify: #((value)) do: [ :old | console log: old. 2 * old ]or this in the Todo example:
blackboard modify: #((todos) 1 done) do: [ :state | state not ].
All data binding and processing info for an HTML element
is contained in an attribute
It consists of several statements separated by dot (
Each statement consists of several expressions, separated by colon (
The first expression is called path. It describes the location of the data the expression is processing, relative to actual position, similarly to how relative paths work in file systems.
The second expression is the processing chain. It describes
the series of transformations the data goes through in its way
to the view or back. If not present, the default
contents is used.
The role of other expressions is not defined in the moment.
All expressions represent the array of strings, numbers or sub-expressions
and have same syntax: they consists of series of space-separated
strings (without reserved characters),
whole numbers (float ones use decimal dot, which is used as delimiter),
or a sub-expression within parentheses (
The syntax is resembling Smalltalk literal array syntax very closely -
so similar or same expressions can be used in API in code as well
For example paths
#((todos) 1 done) from API examples
in previous chapter could be written in
'(todos) 1 done'.
Syntactic sugar: as
(foo) happens often in
it can be written equivalently as
~foo, to improve readability.
So above paths would likely be written
~todos 1 done instead.
! This section is common to
data-trap paths and in-code trapped paths !
The Trapped data-path is an array of elements: either strings, numbers or a sub-arrays. These are used to denote the (relative) location of a piece of data in a Trapped blackboard, and is used to read or write from / to this position.
Elements of a path are equivalent to elements of paths in classic file systems: each elements is one step deeper in a tree hierarchy. Thus, to read a data denoted by a path, Trapped starts from actual position, reads the contents denoted by first element, use the result to read the contents denoted by second elements etc. until the end. To write the data, the algorithm is similar to reading one, byt the last element is used to write the data instead.
foo at: aStringis performed;
foo at: aString put: valueis performed;
foo at: aNumberis performed;
foo at: aNumber put: valueis performed;
#(bar)is read from foo,
foo baris performed;
#(bar)is written to foo,
foo bar: valueis performed.
In addition, these operation are error-tolerant - if any data in the path is
selectors do not exist, indexes are out of bounds etc., the result of the whole expression
blackboard modify: #((todos) 1 done) do: [ :state | state not ] example
from previous chapter essentially does
| x | x := blackboard todos at: 1. x at: 'done' put: (x at: 'done') not
Plus, of course, all the bookkeeping of the blackboard.
The Trapped processing chain descriptor is an array of elements: either strings, numbers or a sub-arrays. These are used to describe the transformations and operations that happen with a piece of data in the way from view-model to a view or back.
The descriptor describes the way how a processing elements
in a chain are created (which ones, and with what parameters).
These then process the data (see
These elements are created by sending a message to the factory object.
In the moment,
TrappedProcessor class itself serves as a factory.
aFactory foois used to create processing element;
aFactory foois used to create processing element;
(foo something bar anotherThing)occurs,
aFactory foo: something bar: anotherThingis used to create processing element, the odd elements representing the keywords of the message, the even elements representing the arguments (which can be string, number or array);
Note: thus, in processing chain descriptors,
describe the same one-element processing chain where the lone processing element
is created inside Trapped by running
(signal increment) whenClicked from counter example
describes two-element processing chain, with first element created
TrappedProcessor signal: 'increment' and the second one
TrappedProcessor whenClicked. All mentioned methods
whenClicked are factory methods that
create instance of appropriate processor class,
The elements created as described above are used
in sequence to process data. This includes not only transforming,
but also reading/writing it from/to DOM element
contents writes data to
reading/writing from/to blackboard and other bookkeeping.
The processing chain that is data-binding (contains certain
elements that switch on databinding,
contents being one of them),
observes a blackboard at the position given in path.
If a data changes in blackboard, the event is queued
and the data is eventually taken, filled in a
and processed by elements in a chain, in order, beginning with the first element,
toView: aDataCarrier. This method may read (
value:) the piece of data carried. It must explicitly ask to proceed
proceed) to push the data carrier to processing by next element.
It may also choose not to send
proceed and stop the processing chain
for this piece of data.
Processing elements can also subscribe to events in a DOM
and start moving data from DOM into a blackboard. In that case,
data from DOM are fed into another
and it is processed by every element of the chain, in backward order,
toModel: aDataCarrier. Again, this methods reads or writes
the piece of data and proceeds to preceding element or stops the chain.
If the data travels to the beginning of the chain, it is written
to the blackboard at the position denoted in path.
If the processing chain is not data-binding (it does not contain
any element that responds
it is meta-processing. In that case, it does not observe the blackboard;
but it can use path for other purposes.
Since meta-processing chain is not observing blackboard,
the data event can not start it. It is instead started once,
immediately after creating it (that is, after parsing and processing
data-trap or when
trap:processors: is called directly in code),
toView: chain and setting
true as the "data"
in the data carrier.
Often, meta-processing chain contains control structures (guards, loops), so it will actually subscribe the blackboard itself for later processing - it just does it itself, not using automatic blackboard subscription via path.
(signal increment) whenClicked from Counter example
++ button is meta-processing: it does not observe any data
in the blackboard. Instead, whenClicked installs
click event handler on the element, which starts
when button is clicked, feeding it with
true as the data.
toModel: passes the data unchanged,
and signal increment's
aBlackboard modify: aPath do: [ :value | value increment ],
that is, signal processor modifies blackboard at path
by sending a message supplied in its parameter.
You can look at the code of both of these processors yourself:
find their respective factory methods (
in a factory (
TrappedProcessor class), see what classes are
they instantiating and look into those classes.
All processing elements (also called processors)
are instances of some subclass of
Basic processor contents which is used as a default
when no processing chain is specified, is contained within
Trapped-Frontend package; all the other processors supplied
by Trapped which will be described later, are contained
Trapped-Processors package. If you look at the factory
TrappedProcessor class), you see that their factory methods
are in category
*Trapped-Processors). The processor implementations
are in one package with their factory methods, which are
extending the factory.
This is by design - for your application,
you may need to create your own processor, which you do easily:
create your subclass of
TrappedProcessor, and extend the factory
with its creating method. The keywords of this creating method
will need to be used inside
data-trap or in call to
The name of the class is not used in the descriptors,
only the name of factory method is important.
You may end up with library of processors -
this is what
Trapped-Processors package is,
the library of processors included in Trapped itself.
(Reference to all supplied processors: TBD)