| OLD | NEW |
| (Empty) |
| 1 Sky Framework | |
| 2 ============= | |
| 3 | |
| 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. | |
| 8 | |
| 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. | |
| 13 | |
| 14 If you just want to dive into code, see the [stocks example](../../../../example
s/stocks). | |
| 15 | |
| 16 Hello World | |
| 17 ----------- | |
| 18 | |
| 19 To build an application, create a subclass of App and instantiate it. | |
| 20 | |
| 21 ```HTML | |
| 22 <script> | |
| 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 |