Herbert Vojčík пре 10 година
родитељ
комит
f4560ffcb2
1 измењених фајлова са 267 додато и 59 уклоњено
  1. 267 59
      README.md

+ 267 - 59
README.md

@@ -1,86 +1,294 @@
-What is trapped?
+1 Introduction
+====
+
+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
+in a `data-trap` attribute, and changes to those data are
+automagically communicated there and back.
+
+2 Quick jump in
+====
+
+**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 similat 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.
+Then start the server: `node bower_components/amber/bin/amber serve`
+from the project root directory. It starts on port 4000.
+Visit `http://localhost:4000/example-counter/counter.html` (similarly for Todo example)
+in your browser. Amber IDE opens.
+
+3 Big picture
+====
+
+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_.
+
+More precisely:
+
+1. You provide the class for _ViewModel_ data (`AppModel` in 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_.
+1. You create the class for _ViewModel_ itself (`App` in the examples).
+This is more or less mechanical task - just create subclass of right
+base class and fill in `initialize` method 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_).
+1. You write HTML and annotate the elements
+with the data-binding expressions (attribute `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_).
+1. When initializing the page, you must call `Trapped start: anArray`
+where _anArray_ should contain instances of all blackboards (most often
+you will only have one, `blackboard` in the examples)
+that are intended to be used (`smalltalk.Trapped._start_([blackboard]);` JavaScript statement
+in HTML page in the examples).
+1. From that point on, you should only modify or watch data of the blackboard
+using its API. For the example, try this is Counter example:
+`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 ]`.
+
+4 `data-trap` attribute
+====
+
+All data binding and processing info for an HTML element
+is contained in an attribute `data-trap`.
+
+Syntax
 ----
 
-Trapped is a library for Amber that aims to create bidirectional data-binding UI for Amber.
-When it is for Amber, how else should it be called than "trapped"?
+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.
 
-It is inspired by AngularJS. But it does not aim to be hard port of AngularJS into Amber,
-it just tries to bring some of the good things, but stay true to Amber/Smalltalk way of doing web UI.
+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.
 
-What stage it is in?
+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
+as in `data-trap` attribute.
+For example paths `#((value))` and `#((todos) 1 done)` from API examples
+in previous chapter could be written in `data-trap` as `'(value)'` and `'(todos) 1 done'`.
+
+Syntactic sugar: as `(foo)` happens often in `data-trap` expressions,
+it can be written equivalently as `#foo`, to improve readability.
+So above paths would likely be written `#value` and `#todos 1 done` instead.
+
+Semantics of path
 ----
 
-Very early. Nevertheless, very basic things are already working.
-You _can_ try it, though it still misses a lot.
+! 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.
 
-What is working:
- - viewmodel -> view update propagation
- - showing simple data in view
- - iterations in view
- - viewmodel -> view change propagation for some tags
+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.
 
-What is missing:
- - optimizations ;-)
- - view -> viewmodel change propagation everywhere
+ - if _string_ path element is read from _foo_, `foo at: aString` is performed;
+ - if _string_ path element is written to  _foo_, `foo at: aString put: value` is performed;
+ - if _number_ path element is read from _foo_, `foo at: aNumber` is performed;
+ - if _number_ path element is written to _foo_, `foo at: aNumber put: value` is performed;
+ - if _subarray_ path element `#(bar)` is read from _foo_, `foo bar` is performed;
+ - if _subarray_ path element `#(bar)` is written to _foo_, `foo bar: value` is performed.
 
-Enhancements for the future:
- - better change/update model (implicit, not explicit).
+ In addition, these operation are error-tolerant - if any data in the path is `nil`,
+ selectors do not exist, indexes are out of bounds etc., the result of the whole expression
+ is `nil`.
 
-How can I try it?
+So, the `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.
+
+Semantics of processing chain
 ----
 
-**Easy way** (but you cannot save and reload with new code):
+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.
 
-Visit http://www.herby.sk/trapped/demo.html.
+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 `TrappedProcessor` class).
+These elements are created by sending a message to the factory object.
+In the moment, `TrappedProcessor` class itself serves as a factory.
 
-**Hard way** (but you can save and reload with new code):
+ - if _string_ processing element description `foo` occurs,
+ 	`aFactory foo` is used to create processing element;
+ - using  _number_ processing element description
+ 	is useless and produces an error (currently silent);
+ - if one-element  _array_ processing element description `(foo)` occurs,
+ 	`aFactory foo` is used to create processing element;
+ - if even-element  _array_ processing element description `(foo something bar anotherThing)` occurs,
+ 	`aFactory foo: something bar: anotherThing` is 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);
+ - using odd-element  _array_ processing element description other than one-element
+ 	is useless and produces an error (currently silent).
 
-Clone this repo, with submodules as well (amber is bundled as submodule).
-Then start the server: `node vendor/amber/bin/amber-cli.js serve`
-from the project root directory. It starts on port 4000.
-Visit `http://localhost:4000/demo.html` in your browser. Amber IDE opens.
+Note: thus, in processing chain descriptors, `contents`, `#contents` and `(contents)`
+describe the same one-element processing chain where the lone processing element
+is created inside Trapped by running `TrappedProcessor contents`.
+
+Another example: `(signal increment) whenClicked` from counter example
+describes two-element processing chain, with first element created
+by `TrappedProcessor signal: 'increment'` and the second one
+by `TrappedProcessor whenClicked`. All mentioned methods
+(`contents`, `signal:` and `increment` are factory methods that
+create instance of appropriate processor class,
+subclass of `TrappedProcessor`).
+
+5 Processing chain
+====
 
-**Play with it:**
+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 `TagBrush` via `contents:`),
+reading/writing from/to blackboard and other bookkeeping.
+
+Data-binding chain
+----
 
-The Todo example from AngularJS is ported into the demo page.
+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 `TrappedDataCarrier` instance
+and processed by elements in a chain, in order, beginning with the first element,
+by calling `toView: aDataCarrier`. This method may read (`value`) or
+write (`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 `TrappedDataCarrier` instance,
+and it is processed by every element of the chain, _in backward order_,
+by calling `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_.
+
+Meta-processing chain
+----
 
-Trapped itself is in `Trapped-Frontend` and `Trapped-Backend` packages.
-The demo page itself is in `demo.html` and its code is in `Trapped-Demo` package,
-in classes `App` (which is wrapping `AppModel`) and `AppView`.
+If the processing chain is not data-binding (it does not contain
+any element that responds `true` to `isExpectingModelData`),
+it is _meta-processing_. In that case, it does not observe the blackboard;
+but it can use _path_ for other purposes.
 
-`App` is the view model entity (its instance is put
-into global variable `AppEntity` in `demo.html`), that is,
-facade and wrapper around the real object,
-and `AppView` is the view. `AppModel` is plain Smalltalk class
-holding data and having some behaviour. Instance of this class
-is wrapped by `App`.
+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),
+running the `toView:` chain and setting `true` as the "data"
+in the data carrier.
 
-The entity wraps any object (via `model:`, as seen in `App >> initialize`).
-The view is subclass of plain `Widget`, but inside it, uses of `trap:`
-(and others of  `trap:xxx:` family) on `TagBrush`
-and `path trapDescend: block` allows you to bind data from view model.
-You can also iterate arrays in the model using `HTMLCanvas >> trapIter:do:`
-or `TagBrush >> trapIter:after:`.
+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_.
 
-To see viewmodel->view update working, try this in Workspace:
+Example: `(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 `toModel:` processing
+when button is clicked, feeding it with `true` as the data.
+_whenClicked_'s `toModel:` passes the data unchanged,
+and _signal increment_'s `toModel:` does
+`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 (`signal:`, `whenClicked`)
+in a factory (`TrappedProcessor class`), see what classes are
+they instantiating and look into those classes.
 
-```smalltalk
-AppEntity modify: #((todos)) do: [ :old | old, { #{'text'->'try the guts'. 'done'->true} } ]
-```
+6 Processors
+====
 
-The number and list of items should update. If you do
+All processing elements (also called processors)
+are instances of  some subclass of `TrappedProcessor`.
 
-```smalltalk
-AppEntity modify: #((title)) do: [ 'My title' ]
-```
+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
+ in `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.
 
-The title of the page as well as header should be updated.
+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 `trap:processors:` API.
+The name of the class is not used in the descriptors,
+only the name of factory method is important.
 
-The `modify:do:` should be used for update since it changes as well as signals the change.
-When using `ListKeyedIsolatedEntity` class as wrapper entity,  `read:do:` and `modify:do:`
-guard the data by doing deep copies behind the scene.
+You may end up with library of processors -
+this is what `Trapped-Processors` package is,
+the library of processors included in Trapped itself.
 
-If you wish to, you can change the raw data you put into `model:` by hand,
-but then be sure to call `AppEntity dispatcher changed: #((title))` or similar
-(you can do `AppEntity dispatcher changed: #()` to signal everything in `AppEntity` has changed,
-but then everything depending upon it will redraw).
+(Reference to all supplied processors: TBD)