OLD | NEW |
1 Sky Framework | 1 SKY SDK |
2 ============= | 2 ======== |
3 | 3 |
4 Effen is a functional-reactive framework for Sky which takes inspiration from | 4 Sky and Sky's SDK are designed as layered frameworks, where each layer |
5 [React](http://facebook.github.io/react/). Effen is comprised of three main | 5 depends on the ones below it but could be replaced wholesale. |
6 parts: a virtual-dom and diffing engine, a component mechanism and a very early | |
7 set of widgets for use in creating applications. | |
8 | 6 |
9 The central idea is that you build your UI out of components. Components | 7 The bottom-most layer is the Sky Platform, which is exposed to Dart |
10 describe what their view should look like given their current configuration & | 8 code as the ```dart:sky``` package. |
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. | |
13 | 9 |
14 If you just want to dive into code, see the [stocks example](../../../../example
s/stocks). | 10 Above this are the files in the [painting/](painting/) directory, |
| 11 which provide APIs related to drawing graphics. |
15 | 12 |
16 Hello World | 13 Layout primitives are provided in the next layer, found in the |
17 ----------- | 14 [rendering/](rendering/) directory. They use ```dart:sky``` and the |
| 15 APIs exposed in painting/ to provide a retained-mode layout and |
| 16 rendering model for applications or documents. |
18 | 17 |
19 To build an application, create a subclass of App and instantiate it. | 18 Widgets are provided by the files in the [widgets/](widgets/) |
| 19 directory, using a reactive framework. |
20 | 20 |
21 ```HTML | 21 Text input widgets are layered on this mechanism and can be found in |
22 <script> | 22 the [editing2/](editing2/) directory. |
23 import 'hello_world.dart'; | |
24 | |
25 main() { | |
26 new HelloWorldApp(); | |
27 } | |
28 </script> | |
29 ``` | |
30 | |
31 ```dart | |
32 // In hello_world.dart | |
33 import 'package:sky/framework/fn.dart'; | |
34 | |
35 class HelloWorldApp extends App { | |
36 UINode build() { | |
37 return new Text('Hello, world!'); | |
38 } | |
39 } | |
40 ``` | |
41 | |
42 An app is comprised of (and is, itself, a) components. A component's main job is | |
43 to implement `UINode build()`. The idea here is that the `build` method describe
s | |
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. | |
47 | |
48 Nodes | |
49 ----- | |
50 | |
51 A component's `build` method must return a single `UINode` 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`. | |
57 | |
58 How to structure you app | |
59 ------------------------ | |
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. | |
67 | |
68 What does "data flowing down the tree" mean? | |
69 -------------------------------------------- | |
70 | |
71 Consider the case of a checkbox. (i.e. `widgets/checkbox.dart`). The `Checkbox` | |
72 constructor looks like this: | |
73 | |
74 ```dart | |
75 ValueChanged onChanged; | |
76 bool checked; | |
77 | |
78 Checkbox({ Object key, this.onChanged, this.checked }) : super(key: key); | |
79 ``` | |
80 | |
81 What this means is that the `Checkbox` component *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. | |
91 | |
92 Stateful vs. Stateless components | |
93 --------------------------------- | |
94 | |
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. | |
102 | |
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... | |
106 | |
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. | |
120 | |
121 Rendering | |
122 --------- | |
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. | |
131 | |
132 Keys | |
133 ---- | |
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. | |
144 | |
145 Event Handling | |
146 -------------- | |
147 | |
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 `EventListenerNode`. `EventLis
tenerNode` | |
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 | |
155 class MyComp extends Component { | |
156 MyComp({ | |
157 Object key | |
158 }) : super(key: key); | |
159 | |
160 void _handleTap(sky.GestureEvent e) { | |
161 // do stuff | |
162 } | |
163 | |
164 void _customEventCallback(sky.Event e) { | |
165 // do other stuff | |
166 } | |
167 | |
168 UINode build() { | |
169 new EventListenerNode( | |
170 new Container( | |
171 children: // ... | |
172 ), | |
173 onGestureTap: _handleTap, | |
174 custom: { | |
175 'myCustomEvent': _customEventCallback | |
176 } | |
177 ); | |
178 } | |
179 | |
180 _handleScroll(sky.Event e) { | |
181 setState(() { | |
182 // update the scroll position | |
183 }); | |
184 } | |
185 } | |
186 ``` | |
187 | |
188 Styling | |
189 ------- | |
190 | |
191 Styling is the part of Effen which is least designed and is likely to change. | |
192 There are three ways to specify styles: | |
193 | |
194 * `Style` objects which are interned and can be applied to WrapperNodes via th
e | |
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. | |
201 | |
202 If you need to apply a Style to a Component or UINode 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. | |
206 | |
207 Animation | |
208 --------- | |
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. | |
213 | |
214 Performance | |
215 ----------- | |
216 | |
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 |