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