OLD | NEW |
1 Effen (fn) | 1 Sky Framework |
2 === | 2 ============= |
3 | 3 |
4 Effen is a prototype of a functional-reactive framework for sky which takes insp
iration from [React](http://facebook.github.io/react/). | 4 Effen is a functional-reactive framework for Sky which takes inspiration from |
| 5 [React](http://facebook.github.io/react/). Effen is comprised of three main |
| 6 parts: a virtual-dom and diffing engine, a component mechanism and a very early |
| 7 set of widgets for use in creating applications. |
5 | 8 |
6 Effen is comprised of three main parts: a virtual-dom and diffing engine, a comp
onent mechanism and a very early set of widgets for use in creating applications
. | 9 The central idea is that you build your UI out of components. Components |
| 10 describe what their view should look like given their current configuration & |
| 11 state. The diffing engine ensures that the DOM looks how the component describes |
| 12 by applying minimal diffs to transition it from one state to the next. |
7 | 13 |
8 The central idea is that you build your UI out of components. Components describ
e what their view should look like given their current configuration & state. Th
e diffing engine ensures that the DOM looks how the component describes by apply
ing minimal diffs to transition it from one state to the next. | 14 If you just want to dive into code, see the [stocks example](../../../../example
s/stocks). |
9 | |
10 If you just want to dive into code, see the [stocks example](../examples/stocks)
. | |
11 | |
12 | 15 |
13 Hello World | 16 Hello World |
14 ----------- | 17 ----------- |
15 | 18 |
16 To build an application, create a subclass of App and instantiate it. | 19 To build an application, create a subclass of App and instantiate it. |
17 | 20 |
18 ```HTML | 21 ```HTML |
19 <!-- In hello.sky --> | |
20 <script> | 22 <script> |
21 import 'helloworld.dart'; | 23 import 'hello_world.dart'; |
22 | 24 |
23 main() { | 25 main() { |
24 new HelloWorldApp(); | 26 new HelloWorldApp(); |
25 } | 27 } |
26 </script> | 28 </script> |
27 ``` | 29 ``` |
28 | 30 |
29 ```JavaScript | 31 ```dart |
30 // In helloworld.dart | 32 // In hello_world.dart |
31 import '../fn/lib/fn.dart'; | 33 import 'package:sky/framework/fn.dart'; |
32 | 34 |
33 class HelloWorldApp extends App { | 35 class HelloWorldApp extends App { |
34 Node build() { | 36 Node build() { |
35 return new Text('Hello, World!'); | 37 return new Text('Hello, world!'); |
36 } | 38 } |
37 } | 39 } |
38 ``` | 40 ``` |
39 An app is comprised of (and is, itself, a) components. A component's main job is
to implement `Node build()`. The idea here is that the `build` method describes
the DOM of a component at any given point during its lifetime. In this case, ou
r `HelloWorldApp`'s `build` method just returns a `Text` node which displays the
obligatory line of text. | 41 |
| 42 An app is comprised of (and is, itself, a) components. A component's main job is |
| 43 to implement `Node build()`. The idea here is that the `build` method describes |
| 44 the DOM of a component at any given point during its lifetime. In this case, our |
| 45 `HelloWorldApp`'s `build` method just returns a `Text` node which displays the |
| 46 obligatory line of text. |
40 | 47 |
41 Nodes | 48 Nodes |
42 ----- | 49 ----- |
43 A component's `build` method must return a single `Node` which *may* have childr
en (and so on, forming a *subtree*). Effen comes with a few built-in nodes which
mirror the built-in nodes/elements of sky: `Text`, `Anchor` (`<a />`, `Image` (
`<img />`) and `Container` (`<div />`). `build` can return a tree of Nodes compr
ised of any of these nodes and plus any other imported object which extends `Com
ponent`. | 50 |
| 51 A component's `build` method must return a single `Node` which *may* have |
| 52 children (and so on, forming a *subtree*). Effen comes with a few built-in nodes |
| 53 which mirror the built-in nodes/elements of sky: `Text`, `Anchor` (`<a />`, |
| 54 `Image` (`<img />`) and `Container` (`<div />`). `build` can return a tree of |
| 55 Nodes comprised of any of these nodes and plus any other imported object which |
| 56 extends `Component`. |
44 | 57 |
45 How to structure you app | 58 How to structure you app |
46 ------------------------ | 59 ------------------------ |
47 If you're familiar with React, the basic idea is the same: Application data flow
s *down* from components which have data to components & nodes which they constr
uct via construction parameters. Generally speaking, View-Model data (data which
is derived from *model* data, but exists only because the view needs it), is co
mputed during the course of `build` and is short-lived, being handed into nodes
& components as configuration data. | 60 |
| 61 If you're familiar with React, the basic idea is the same: Application data |
| 62 flows *down* from components which have data to components & nodes which they |
| 63 construct via construction parameters. Generally speaking, View-Model data (data |
| 64 which is derived from *model* data, but exists only because the view needs it), |
| 65 is computed during the course of `build` and is short-lived, being handed into |
| 66 nodes & components as configuration data. |
48 | 67 |
49 What does "data flowing down the tree" mean? | 68 What does "data flowing down the tree" mean? |
50 -------------------------------------------- | 69 -------------------------------------------- |
51 Consider the case of a checkbox. (i.e. `widgets/checkbox.dart`). The `Checkbox`
constructor looks like this: | |
52 | 70 |
53 ```JavaScript | 71 Consider the case of a checkbox. (i.e. `widgets/checkbox.dart`). The `Checkbox` |
| 72 constructor looks like this: |
| 73 |
| 74 ```dart |
54 ValueChanged onChanged; | 75 ValueChanged onChanged; |
55 bool checked; | 76 bool checked; |
56 | 77 |
57 Checkbox({ Object key, this.onChanged, this.checked }) : super(key: key); | 78 Checkbox({ Object key, this.onChanged, this.checked }) : super(key: key); |
58 ``` | 79 ``` |
59 | 80 |
60 What this means is that the `Checkbox` component is *never* "owns" the state of
the checkbox. It's current state is handed into the `checked` parameter, and whe
n a click occurs, the checkbox invokes its `onChanged` callback with the value i
t thinks it should be changed to -- but it never directly changes the value itse
lf. This is a bit odd at first look, but if you think about it: a control isn't
very useful unless it gets its value out to someone and if you think about datab
inding, the same thing happens: databinding basically tells a control to *treat
some remote variable as its storage*. That's all that is happening here. In this
case, some owning component probably has a set of values which describe a form. | 81 What this means is that the `Checkbox` component is *never* "owns" the state of |
| 82 the checkbox. It's current state is handed into the `checked` parameter, and |
| 83 when a click occurs, the checkbox invokes its `onChanged` callback with the |
| 84 value it thinks it should be changed to -- but it never directly changes the |
| 85 value itself. This is a bit odd at first look, but if you think about it: a |
| 86 control isn't very useful unless it gets its value out to someone and if you |
| 87 think about databinding, the same thing happens: databinding basically tells a |
| 88 control to *treat some remote variable as its storage*. That's all that is |
| 89 happening here. In this case, some owning component probably has a set of values |
| 90 which describe a form. |
61 | 91 |
62 Stateful vs. Stateless components | 92 Stateful vs. Stateless components |
63 --------------------------------- | 93 --------------------------------- |
64 All components have access to two kinds of state: (1) configuration data (constr
uctor arguments) and (2) private data (data they mutate themselves). While react
components have explicit property bags for these two kinds of state (`this.prop
` and `this.state`), Effen maps these ideas to the public and private fields of
the component. Constructor arguments should (by convention) be reflected as publ
ic fields of the component and state should only be set on private (with a leadi
ng underbar `_`) fields. | |
65 | 94 |
66 All (non-component) Effen nodes are stateless. Some components will be stateful.
This state will likely encapsulate transient states of the UI, such as scroll p
osition, animation state, uncommitted form values, etc... | 95 All components have access to two kinds of state: (1) configuration data |
| 96 (constructor arguments) and (2) private data (data they mutate themselves). |
| 97 While react components have explicit property bags for these two kinds of state |
| 98 (`this.prop` and `this.state`), Effen maps these ideas to the public and private |
| 99 fields of the component. Constructor arguments should (by convention) be |
| 100 reflected as public fields of the component and state should only be set on |
| 101 private (with a leading underbar `_`) fields. |
67 | 102 |
68 A component can become stateful in two ways: (1) by passing `super(stateful: tru
e)` to its call to the superclass's constructor, or by calling `setState(Functio
n fn)`. The former is a way to have a component start its life stateful, and the
latter results in the component becoming statefull *as well as* scheduling the
component to re-build at the end of the current animation frame. | 103 All (non-component) Effen nodes are stateless. Some components will be stateful. |
| 104 This state will likely encapsulate transient states of the UI, such as scroll |
| 105 position, animation state, uncommitted form values, etc... |
69 | 106 |
70 What does it mean to be stateful? It means that the diffing mechanism retains th
e specific *instance* of the component as long as the component which builds it
continues to require its presence. The component which constructed it may have p
rovided new configuration in form of different values for the constructor parame
ters, but these values (public fields) will be copied (using reflection) onto th
e retained instance whose privates fields are left unmodified. | 107 A component can become stateful in two ways: (1) by passing `super(stateful: |
| 108 true)` to its call to the superclass's constructor, or by calling |
| 109 `setState(Function fn)`. The former is a way to have a component start its life |
| 110 stateful, and the latter results in the component becoming statefull *as well |
| 111 as* scheduling the component to re-build at the end of the current animation |
| 112 frame. |
| 113 |
| 114 What does it mean to be stateful? It means that the diffing mechanism retains |
| 115 the specific *instance* of the component as long as the component which builds |
| 116 it continues to require its presence. The component which constructed it may |
| 117 have provided new configuration in form of different values for the constructor |
| 118 parameters, but these values (public fields) will be copied (using reflection) |
| 119 onto the retained instance whose privates fields are left unmodified. |
71 | 120 |
72 Rendering | 121 Rendering |
73 --------- | 122 --------- |
74 At the end of each animation frame, all components (including the root `App`) wh
ich have `setState` on themselves will be rebuilt and the resulting changes will
be minimally applied to the DOM. Note that components of lower "order" (those n
ear the root of the tree) will build first because their building may require re
building of higher order (those near the leaves), thus avoiding the possibility
that a component which is dirty build more than once during a single cycle. | 123 |
| 124 At the end of each animation frame, all components (including the root `App`) |
| 125 which have `setState` on themselves will be rebuilt and the resulting changes |
| 126 will be minimally applied to the DOM. Note that components of lower "order" |
| 127 (those near the root of the tree) will build first because their building may |
| 128 require rebuilding of higher order (those near the leaves), thus avoiding the |
| 129 possibility that a component which is dirty build more than once during a single |
| 130 cycle. |
75 | 131 |
76 Keys | 132 Keys |
77 ---- | 133 ---- |
78 In order to efficiently apply changes to the DOM and to ensure that stateful com
ponents are correctly identified, Effen requires that `no two nodes (except Text
) or components of the same type may exist as children of another element withou
t being distinguished by unique keys`. [`Text` is excused from this rule]. In ma
ny cases, nodes don't require a key because there is only one type amongst its s
iblings -- but if there is more one, you must assign each a key. This is why mos
t nodes will take `({ Object key })` as an optional constructor parameter. In de
velopment mode (i.e. when sky is built `Debug`) Effen will throw an error if you
forget to do this. | 134 |
| 135 In order to efficiently apply changes to the DOM and to ensure that stateful |
| 136 components are correctly identified, Effen requires that `no two nodes (except |
| 137 Text) or components of the same type may exist as children of another element |
| 138 without being distinguished by unique keys`. [`Text` is excused from this rule]. |
| 139 In many cases, nodes don't require a key because there is only one type amongst |
| 140 its siblings -- but if there is more one, you must assign each a key. This is |
| 141 why most nodes will take `({ Object key })` as an optional constructor |
| 142 parameter. In development mode (i.e. when sky is built `Debug`) Effen will throw |
| 143 an error if you forget to do this. |
79 | 144 |
80 Event Handling | 145 Event Handling |
81 -------------- | 146 -------------- |
82 Events logically fire through the Effen node tree. If want to handle an event as
it bubbles from the target to the root, create an `EventTarget`. `EventTarget`
has named (typed) parameters for a small set of events that we've hit so far, as
well as a 'custom' argument which is a `Map<String, sky.EventListener>`. If you
'd like to add a type argument for an event, just post a patch. | |
83 | 147 |
84 ```JavaScript | 148 Events logically fire through the Effen node tree. If want to handle an event as |
| 149 it bubbles from the target to the root, create an `EventTarget`. `EventTarget` |
| 150 has named (typed) parameters for a small set of events that we've hit so far, as |
| 151 well as a 'custom' argument which is a `Map<String, sky.EventListener>`. If |
| 152 you'd like to add a type argument for an event, just post a patch. |
| 153 |
| 154 ```dart |
85 class MyComp extends Component { | 155 class MyComp extends Component { |
86 MyComp({ | 156 MyComp({ |
87 Object key | 157 Object key |
88 }) : super(key: key); | 158 }) : super(key: key); |
89 | 159 |
90 void _handleClick(sky.GestureEvent e) { | 160 void _handleTap(sky.GestureEvent e) { |
91 // do stuff | 161 // do stuff |
92 } | 162 } |
93 | 163 |
94 void _customEventCallback(sky.Event e) { | 164 void _customEventCallback(sky.Event e) { |
95 // do other stuff | 165 // do other stuff |
96 } | 166 } |
97 | 167 |
98 Node build() { | 168 Node build() { |
99 new EventTarget( | 169 new EventTarget( |
100 new Container( | 170 new Container( |
101 children: // ... | 171 children: // ... |
102 ), | 172 ), |
103 onGestureTap: _handleClick, | 173 onGestureTap: _handleTap, |
104 custom: { | 174 custom: { |
105 'myCustomEvent': _customEventCallback | 175 'myCustomEvent': _customEventCallback |
106 } | 176 } |
107 ); | 177 ); |
108 } | 178 } |
109 | 179 |
110 _handleScroll(sky.Event e) { | 180 _handleScroll(sky.Event e) { |
111 setState(() { | 181 setState(() { |
112 // update the scroll position | 182 // update the scroll position |
113 }); | 183 }); |
114 } | 184 } |
115 } | 185 } |
116 ``` | 186 ``` |
117 | 187 |
118 | |
119 Styling | 188 Styling |
120 ------- | 189 ------- |
121 Styling is the part of Effen which is least designed and is likely to change. Th
ere are two ways to specify styles: | |
122 | 190 |
123 * `Style` objects which are interned and can be applied to Elements via the `s
tyle` constructor parameter. Use `Style` objects for styles which are *not* anim
ated. | 191 Styling is the part of Effen which is least designed and is likely to change. |
124 * An `inlineStyle` string which can be applied to Elements via the `inlineStyl
e` constructor parameter. Use `inlineStyle` for styles which *are* animated. | 192 There are two ways to specify styles: |
| 193 |
| 194 * `Style` objects which are interned and can be applied to Elements via the |
| 195 ``style` constructor parameter. Use `Style` objects for styles which are |
| 196 `*not* animated. |
| 197 |
| 198 * An `inlineStyle` string which can be applied to Elements via the |
| 199 `inlineStyle` constructor parameter. Use `inlineStyle` for styles which |
| 200 *are* animated. |
125 | 201 |
126 If you need to apply a Style to a Component or Node which you didn't construct (
i.e. one that was handed into your constructor), you can wrap it in a `StyleNode
` which also takes a `Style` constructor in it's `style` constructor parameter. | 202 If you need to apply a Style to a Component or Node which you didn't construct |
| 203 (i.e. one that was handed into your constructor), you can wrap it in a |
| 204 `StyleNode` which also takes a `Style` constructor in it's `style` constructor |
| 205 parameter. |
127 | 206 |
128 Animation | 207 Animation |
129 --------- | 208 --------- |
130 Animation is still an area of exploration. Have a look at [AnimatedComponent](co
mponents/animated_component.dart) and [Drawer](components/drawer.dart) for an ex
ample of this this currently works. | 209 |
| 210 Animation is still an area of exploration. Have a look at |
| 211 [AnimatedComponent](components/animated_component.dart) and |
| 212 [Drawer](components/drawer.dart) for an example of this this currently works. |
131 | 213 |
132 Performance | 214 Performance |
133 ----------- | 215 ----------- |
134 It is a design goal that it should be *possible* to arrange that all "build" cyc
les which happen during animations can complete in less than one milliesecond on
a Nexus 5. | |
135 | 216 |
136 | 217 It is a design goal that it should be *possible* to arrange that all "build" |
| 218 cycles which happen during animations can complete in less than one milliesecond |
| 219 on a Nexus 5. |
OLD | NEW |