Skip to content

Instantly share code, notes, and snippets.

@jaredly
Created April 2, 2015 00:59
Show Gist options
  • Select an option

  • Save jaredly/8f3f19c688dbcd2eb6ef to your computer and use it in GitHub Desktop.

Select an option

Save jaredly/8f3f19c688dbcd2eb6ef to your computer and use it in GitHub Desktop.
Dominex TODO rename
{
"title": "Dominex TODO rename",
"created": 1426174963481,
"opened": 1427936357702,
"repl": "ijs",
"root": {
"id": "777d5hlgtdnv7h3uejxmj3emx7vpvhox",
"created": 1426174963622,
"modified": 1426174963622,
"content": "Dominex TODO rename",
"children": [
{
"id": "ht160tu09y3zrffspskw5nrod9sxx9v5",
"created": 1426646196536,
"modified": 1426646196536,
"collapsed": true,
"content": "For working [notebook](http://notablemind.local:6682/#/doc/y3rtmsuorlfvnsp8sgng108cgw1jv91g)",
"type": "base",
"children": []
},
{
"id": "0x35gh68vt63yfi14irf88pe8zb92zwn",
"created": 1426194448491,
"modified": 1426194448491,
"collapsed": true,
"content": "Background Reading",
"type": "base",
"children": [
{
"id": "t4mrg70uh236kqwt6oeds3v1vl7in20j",
"created": 1426194454802,
"modified": 1426194454803,
"collapsed": false,
"content": "Read",
"type": "base",
"children": [
{
"id": "rw755x48ihciw81u3ne4jtxarj8zkq99",
"created": 1426194519280,
"modified": 1426194519280,
"collapsed": true,
"content": "http://futurice.com/blog/reactive-mvc-and-the-virtual-dom",
"type": "base",
"children": []
},
{
"id": "t2u244esfvhnt1d22oqz5i41lqeknhw3",
"created": 1426194528218,
"modified": 1426194528218,
"collapsed": false,
"content": "http://swannodette.github.io/2013/07/31/extracting-processes/",
"type": "base",
"children": []
},
{
"id": "y7b4ane68brl0rm3bjvu82zv3tizok00",
"created": 1426194536831,
"modified": 1426194536831,
"collapsed": true,
"content": "http://potetm.github.io/2014/01/27/responsive-design-csp.html",
"type": "base",
"children": []
},
{
"id": "k3iww1ilpbzj0di7txyg3bjetihfbhws",
"created": 1426195068516,
"modified": 1426195068516,
"collapsed": true,
"content": "https://github.com/staltz/cycle/blob/master/docs/faq.md",
"type": "base",
"children": []
}
]
},
{
"id": "p9yghcuvujwigmi885d9gw9vdugu6cpc",
"created": 1426194455946,
"modified": 1426194455946,
"collapsed": false,
"content": "Not Read",
"type": "base",
"children": [
{
"id": "20u2i4g3iev0mdtme22ff38h7sw862za",
"created": 1426194462107,
"modified": 1426194462107,
"collapsed": true,
"content": "http://elm-lang.org/blog/Blazing-Fast-Html.elm",
"type": "base",
"children": []
},
{
"id": "dm9e7o73708d20n7zbbrtg7pnx5633si",
"created": 1426194457336,
"modified": 1426194457336,
"collapsed": true,
"content": "http://elm-lang.org/learn/Architecture.elm",
"type": "base",
"children": []
},
{
"id": "vmejjnksxvj5jr8cw9bji1ie16ysevpi",
"created": 1426194508933,
"modified": 1426194508933,
"collapsed": true,
"content": "https://medium.com/@garychambers108/functional-reactive-react-js-b04a8d97a540",
"type": "base",
"children": []
},
{
"id": "ek069t5jsd77ydrbto6l0mrco6hxa9a2",
"created": 1426194513879,
"modified": 1426194513879,
"collapsed": true,
"content": "http://joshbassett.info/2014/reactive-uis-with-react-and-bacon/",
"type": "base",
"children": []
},
{
"id": "4tdreq1w40qicjungeft146q4awyd8zx",
"created": 1426195087475,
"modified": 1426195087475,
"collapsed": true,
"content": "https://github.com/arqex/curxor/issues/1#issuecomment-72642125",
"type": "base",
"children": []
},
{
"id": "rirxltuua0lygeroz9gqeufbcdhq4w0j",
"created": 1426195091986,
"modified": 1426195091986,
"collapsed": true,
"content": "https://github.com/ubolonton/js-csp/issues/40",
"type": "base",
"children": []
},
{
"id": "0q6905dnomlstorhzj0oqtn2feb0g890",
"created": 1426195097185,
"modified": 1426195097185,
"collapsed": true,
"content": "https://github.com/staltz/cycle/issues/74",
"type": "base",
"children": []
}
]
},
{
"id": "7ubs9fuz7zetumwk51j3yuy0txethol6",
"created": 1426194475581,
"modified": 1426194475581,
"collapsed": true,
"content": "[add dx to this list on the vdom wiki](https://github.com/Matt-Esch/virtual-dom/wiki)",
"type": "base",
"children": []
}
]
},
{
"id": "cwmb6moqlh6iabbtru06mvwllwoh6w9s",
"created": 1426174967766,
"modified": 1426174967767,
"collapsed": false,
"content": "Blog posts",
"type": "base",
"children": [
{
"id": "k13cn42b4kk4h92u34wskvxigq1swr92",
"created": 1426184580577,
"modified": 1426184580577,
"collapsed": false,
"content": "Misc ANN Tagline intros",
"type": "base",
"children": [
{
"id": "00djqks88sbs6ppmez5vdrdxyty23hox",
"created": 1426222527998,
"modified": 1426222527998,
"collapsed": true,
"content": "I made a reactive virtual-dom based framework in <500 lines of code. Backbone was 856 lines.",
"type": "base",
"children": []
},
{
"id": "b6laq9hxta7d6ozht6vwz5zl3ipjpnes",
"created": 1426184604526,
"modified": 1426184604526,
"collapsed": true,
"content": "With react, one big win was realizing that the `separate technologies` did not actually represent separate concerns. But now let's actually separate concerns.",
"type": "base",
"children": []
},
{
"id": "s1eauo6tui6alyjzna57urahuql0manv",
"created": 1426184639162,
"modified": 1426184639162,
"collapsed": false,
"content": "What are these concerns?",
"type": "base",
"children": [
{
"id": "s9tav8m5adpm8axztmfsoadywyon796y",
"created": 1426184644529,
"modified": 1426184644529,
"collapsed": true,
"content": "Data management + transformation",
"type": "base",
"children": []
},
{
"id": "9abckir3dqm0z06goorh7dx8rgm3mmm1",
"created": 1426184652304,
"modified": 1426184652304,
"collapsed": true,
"content": "Interface display",
"type": "base",
"children": []
},
{
"id": "ns0gppsqye3pnis6g2kyqmpn93mfcc07",
"created": 1426184660418,
"modified": 1426184660418,
"collapsed": true,
"content": "Complex interaction logic",
"type": "base",
"children": []
}
]
},
{
"id": "qemjsks8ux3lvt7ugr3h1lxus39zwv4v",
"created": 1426184586055,
"modified": 1426184586131,
"collapsed": true,
"content": "David Nolen [would call these concerns] \"event stream coordination\", \"interface representation\", \"event stream processing\".",
"type": "base",
"children": []
},
{
"id": "p53bexbjqmrc7esm7btywp63cn2tdvxy",
"created": 1426186314381,
"modified": 1426186314381,
"collapsed": true,
"content": "When you separate these concerns, your code gets cleaner, more maintainable, and more composable. `Mixins` start to make a lot more sense (and cause fewer problems).",
"type": "base",
"children": []
}
]
},
{
"id": "x2jmnikray8ke6y3jtspzpuwpcl88ke5",
"created": 1426174974615,
"modified": 1426174974615,
"collapsed": false,
"content": "ANN",
"type": "base",
"children": [
{
"id": "v14gatv7dinivfdnbaxkyw7u8gwcs43f",
"created": 1426184970553,
"modified": 1426184970553,
"collapsed": true,
"content": "## Intro",
"type": "base",
"children": [
{
"id": "ww0k9dnskelxs82kc7d45whvxoed4tft",
"created": 1426175063885,
"modified": 1426175063885,
"collapsed": true,
"content": "I'm a huge react person, and I have just gotten [really into] reactive streams, and I was thinking `how can I make the most awesome framework in the world?`. Here's what I've come up with.",
"type": "base",
"children": []
},
{
"id": "s11jjghytjd1xb8sqcc3r3p9uz947ihe",
"created": 1426175225147,
"modified": 1426175225147,
"collapsed": true,
"content": "**Dominex** is an opinionated reactive framework, that is entirely built around streams. It defaults you toward responsive, scalable patterns that are easy to read/maintain/test and end up being *really fast* (see benchmarks).",
"type": "base",
"children": []
},
{
"id": "7gee3nlqjhypk8e1zezf2ikfyj4fdb70",
"created": 1426177908957,
"modified": 1426177908957,
"collapsed": true,
"content": "It leverages [virtual-dom] and [Kefir], and takes a lot of inspiration from `Cycle`.",
"type": "base",
"children": []
},
{
"id": "4do1qpbg1w73j6nw0wyttiakbzkca7l6",
"created": 1426177191302,
"modified": 1426177191302,
"collapsed": true,
"content": "If you're new to the ideas of reactive streams and the language surrounding them, things might get a bit dense. {{}}",
"type": "base",
"children": []
},
{
"id": "h5lle33oezc00i269c8ldw9bh7ojh8uh",
"created": 1426177897190,
"modified": 1426177897191,
"collapsed": true,
"content": "TODO mention virtual-dom",
"type": "base",
"children": []
},
{
"id": "xqf9o5ejs5jnire4bmq0knt0xa25vq78",
"created": 1426179328117,
"modified": 1426179328117,
"collapsed": true,
"content": "What are we going to talk about?",
"type": "base",
"children": []
},
{
"id": "uzk6vlude0h2zfd7zczuco9jkdpegbk9",
"created": 1426199768421,
"modified": 1426199768421,
"collapsed": true,
"content": "What even is this?",
"type": "ordered_list",
"children": []
},
{
"id": "dhcm0yaen0tfgab8fcmln24o0dco6g0a",
"created": 1426179335483,
"modified": 1426179335483,
"collapsed": true,
"content": "Componets",
"type": "ordered_list",
"children": []
},
{
"id": "12qe0wlm3f4xokgt5pn31t3ofvyaqe87",
"created": 1426179341734,
"modified": 1426179341734,
"collapsed": true,
"content": "Components and State",
"type": "ordered_list",
"children": []
},
{
"id": "2tj2zccjdkw8m654okys61yn4q7j0wu6",
"created": 1426179343504,
"modified": 1426179343504,
"collapsed": true,
"content": "Complex Interactions and Intents (with a drag + drop example)",
"type": "ordered_list",
"children": []
},
{
"id": "wfn6z8l91ic204g5yc0x4keljfzsbjxr",
"created": 1426184431967,
"modified": 1426184431967,
"collapsed": true,
"content": "Complex Data and the Model",
"type": "ordered_list",
"children": []
},
{
"id": "325gqze72hyyuic5pbk90m5pb5m9xgwl",
"created": 1426179346255,
"modified": 1426179346255,
"collapsed": true,
"content": "Flux",
"type": "ordered_list",
"children": []
}
]
},
{
"id": "nmqbfq7k11omjrhblg2874n6kko3cyxm",
"created": 1426199773828,
"modified": 1426199773828,
"collapsed": true,
"content": "## What even is this?",
"type": "base",
"children": [
{
"id": "o3dae9mut3kpgubu8c6yble88oiiuvfx",
"created": 1426199777728,
"modified": 1426199777728,
"collapsed": true,
"content": "In large part, it's an experiment. It's a weekend project that I decided to explore a bit further. It's an exploration of \"what web UI dev could look like\".",
"type": "base",
"children": []
},
{
"id": "j8lmcqsbmjkt6bj0efqjiq76rggssmmw",
"created": 1426199939740,
"modified": 1426199939740,
"collapsed": true,
"content": "At the very least I hope it will make people think about the fundamental assumptions / design principles of their framework of choice, and how they limit or influence code clarity and expressive power.",
"type": "base",
"children": []
},
{
"id": "imfyc1xufboic43nmhaq51kq4x7jtjij",
"created": 1426205910902,
"modified": 1426205910903,
"collapsed": true,
"content": "It's also an attempt to bring the worlds of `React` and `Rx` a little closer together -- to get people from both sides interested in the other.",
"type": "base",
"children": []
},
{
"id": "8dhgk26qj9f85r9ziw4iuzekly660ck0",
"created": 1426205959483,
"modified": 1426205959484,
"collapsed": true,
"content": "By envisioning the future, maybe it will influence how React develops as well.",
"type": "base",
"children": []
}
]
},
{
"id": "rpchiktxii0s9w1kqhokfwpu3gtbo6xx",
"created": 1426177745791,
"modified": 1426177745791,
"collapsed": true,
"content": "## Components",
"type": "base",
"children": [
{
"id": "kjpyh41hyodcazy4uel2hqmiebra39ra",
"created": 1426177751916,
"modified": 1426177751916,
"collapsed": true,
"content": "In Dominex, as in React, `Components` are the name of the game. Re-use and composability are the sweet smell of productivity.",
"type": "base",
"children": []
},
{
"id": "lv1u94wajg0x8pqeua45rvoezjze9isb",
"created": 1426175470334,
"modified": 1426175470335,
"collapsed": true,
"content": "Here is the most basic type of component:",
"type": "base",
"children": []
},
{
"id": "xkpydpb4fl1px6w5ovhcpeh1ztbmfs9y",
"created": 1426175481739,
"modified": 1426175481739,
"collapsed": true,
"content": "function MyFavoriteComponent({name, onResponse}) {\n return <div>\n <h1>Hello {name}!</h1>\n <button onClick={onResponse}>Respond</button>\n </div>\n}\n\n// later used like:\n<MyFavoriteComponent name=\"Jaqueline\" onResponse={logResponse}/>\n// logResponse left intentionally unexplained. We'll get into streams and event handlers in a bit.",
"type": "ipython",
"children": [],
"language": "javascript"
},
{
"id": "cw1u1uv6pyic6ob6yz8y3q6dedc8nok9",
"created": 1426175598425,
"modified": 1426175598425,
"collapsed": true,
"content": "This component has no internal state, and no complicated event handling. One thing you might find strange if you haven't yet jumped on the `babeljs gravy train` is the `{}` in the function arguments -- but that's just sweet `es6` sugar so you don't have to do `props` and then `props.name` later on. It makes the function signature clearer as well.",
"type": "base",
"children": [],
"language": "javascript"
},
{
"id": "7n8c1531m9inr8v3dtktqryh27hji3xt",
"created": 1426175727942,
"modified": 1426175727942,
"collapsed": true,
"content": "These components also **don't re-render if the props don't change**. This is one of those fast defaults. Prop values are compared with `===`, so immutable js objects are your friends. If you can't satisfy this expectation (you mutate a prop and expect it to re-render), then you need to mark `MyFavoriteComponent.alwaysRerender = true`.",
"type": "base",
"children": []
}
]
},
{
"id": "uc1bw9w2da95x45uj3uo4hl6ailljuxq",
"created": 1426176488508,
"modified": 1426176488508,
"collapsed": true,
"content": "## Components and State",
"type": "base",
"children": [
{
"id": "egehtof131qupjylscmlxtwbbnni2u3k",
"created": 1426175952045,
"modified": 1426175952045,
"collapsed": true,
"content": "If you need some state, the next step up is a `StatefulComponent`. This is where things get interesting. Time to break out the flow diagram.",
"type": "base",
"children": []
},
{
"id": "9wys7zckqsrby8mhfoo9p4lo6oveqzgk",
"created": 1426175987674,
"modified": 1426175987675,
"collapsed": true,
"content": "Woohoo! You can see there are a lot of things going on here.",
"type": "image",
"children": [],
"language": "javascript",
"imageSrc": "data:image/png;base64,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0iV8fHzYs2cPVatWJTIyksWLF7N06VKV64QP3507d/Dz8+Pu3bts2LCB6tWrywlcYmIiX375Jb/++utby2dlZTF69GhGjRold+du376dyZMn07JlyxJ5BkEoLHV1dXmCxtq1a+WJao0aNeKbb75h3Lhx7N+/v8A6mjZtyrJly7Czs2PRokX4+/tjYWFREuELJeD3339n6dKlvH79WmWISqNGjRg8eLC8L/ny5cuJjY1l3rx5BdZ379497O3tUVNTIysri8OHD1OvXr1ifYbSIBK/YtawYUPGjh0rj797k62tLefOnePFixd4eHiQnp6ucr5jx45069aNXr16cffu3ULd79tvv+WPP/5g/PjxzJw5k88//xx3d3e6devG+fPnqV+/PgEBAVy6dIlmzZoVul6h9N24cYOuXbuio6PDtGnTGDZsGM2bNyckJKRQS6989913dO7cGRMTEwB8fX1p3749jo6OxR26IPxnkyZNYurUqSxbtkz+EGxoaIibmxsnTpxg0qRJZGVlvbV8//79uXLlCllZWUybNo1p06aVVOhCMUpMTGTChAkMHDiQzz//nKdPn3Ly5En5vJGRkdzDpaWlhbe3N/fv3yc7Ozvf+tavX8+UKVNo27YtkZGR+Pr6UrVq1RJ5lpImxviVgPT09AL3SIWcLtoff/yRAQMG5JlanpyczObNm3F2dmbIkCHvvF9KSgonTpxg48aNSJLEmTNnSE5OplatWri7u/O///0PgAULFnD27Nl3LvshfBguXLjAV199xdKlS9m/fz9+fn6oqanh5uaGpaUlbm5uby3r5eXF6dOn5Zbna9euERsbW6R7RgtCcbp//z5jx45l+PDhVK9eXT5+69Ytzp07x7Zt28RwhU/Y+fPniY2NpX///ly7do1BgwbRt29fVq1aBcDRo0cZMmQI3377LRoaGvj4+HDq1Cl5AtvbvHr1im+++QZjY2PKly9PREQEu3btQltbuyQeq1SIxO8D8urVK1xdXVEoFAwaNEhelR5yBisfOHCASpUqsXz58kKN1Tt69CitW7ematWq8lT0x48fy2NbTp48ybhx43j27FmxPZPw/o4dO0aLFi2wsLCgf//++Pj4sHbtWr7++mtWrFiBv78/V69eRUdHJ9/ywcHBTJ48mcmTJ6Ompsbjx48JCAjg8OHDYsyn8FGJjY1l0KBBfP7553z2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"
},
{
"id": "wy2omqlceeqlf9telwvgcncc7emzd8tl",
"created": 1426176123014,
"modified": 1426176123014,
"collapsed": true,
"content": "All of the arrows represent streams. Remember, [everything is a stream].",
"type": "base",
"children": []
},
{
"id": "345hli5qe0ozwgsu6vtnlo4mliztd6ei",
"created": 1426176647470,
"modified": 1426176647470,
"collapsed": true,
"content": "Let's start with the `Model`.",
"type": "base",
"children": []
},
{
"id": "hsykah5vaj7wf8qrygoudvhhzb581sw4",
"created": 1426176655569,
"modified": 1426176655569,
"collapsed": true,
"content": "function MyStatefulModel(props, intents) {\n return {\n name$: props('name'),\n text$: props('initialText').skipDuplicates().merge(intents('text-change')),\n }\n}",
"type": "ipython",
"children": [],
"language": "javascript"
},
{
"id": "2muzv4xeptuhxx8i009m4rf0xdeob065",
"content": "> What's with all the `$`s? It's a convention I picked up from [cycle], and is pronounced \"stream\". e.g. `name$` is read `name stream`.",
"children": [],
"created": 1426192858040,
"modified": 1426198336184,
"collapsed": true,
"type": "base"
},
{
"id": "tyjmbegt4retzn9qhwzpl9aheobfg3y2",
"created": 1426176816450,
"modified": 1426176816450,
"collapsed": true,
"content": "So what's going on here? A `Model` function is called when the component is instantiated, and returns an object representing the component's state. The `values` in this object are each streams.",
"type": "base",
"children": []
},
{
"id": "weno2gikf9pgj5d3wvlwcd8u5060upgg",
"created": 1426176899309,
"modified": 1426176899309,
"collapsed": true,
"content": "The `name` value is just passed through from props. (`props(\"somename\")` returns a stream).",
"type": "ordered_list",
"children": []
},
{
"id": "9kqle6dfan3nhpibkjafogh7hzqefodt",
"created": 1426176949399,
"modified": 1426176949399,
"collapsed": true,
"content": "The `text` piece of state is first initialized from props, but than changes in response to the `text-change` intent. It will also be reset if the `initialText` prop ever changes.",
"type": "ordered_list",
"children": []
},
{
"id": "dnnnylgk5qr3rub00119q696cxya7pno",
"created": 1426177011259,
"modified": 1426177011259,
"collapsed": true,
"content": "To see how this comes together, let's throw in a view:",
"type": "base",
"children": []
},
{
"id": "27glgfvg6ozn9ie02k9vt1y5vfe6ua9y",
"created": 1426177042410,
"modified": 1426177042410,
"collapsed": true,
"content": "function MyStatefulView([name, text], intents) {\n return <div>\n Hello {name}, tell us a bit about yourself:\n <textarea\n value={text}\n placeholder=\"a little bit about yourself...\"\n onChange={intents('text-change', e => e.target.value)}/>\n </div>\n}",
"type": "ipython",
"children": [],
"language": "javascript"
},
{
"id": "j1q82agp7gwjtyx62dduxy6c8txsjt84",
"created": 1426177064439,
"modified": 1426177064440,
"collapsed": true,
"content": "From the view's perspective, it doesn't actually matter what came from props and what came from state -- that's the model's concern. The view just takes some data and renders it, along with interaction hooks.",
"type": "base",
"children": []
},
{
"id": "dku7h65bny6eqqjn8gw59hgvxj000efn",
"created": 1426177241501,
"modified": 1426177241501,
"collapsed": true,
"content": "The view function receives first a list of state values (in the same order they were declared in the Model function), and then an `intents` function which allows you to register events on nodes.",
"type": "base",
"children": []
},
{
"id": "b4evqfy2ytsjqhw1pjscgn5t5f6cy6tc",
"content": "> one interesting thing here is that the `intents` function accepts a mapper, and here we just extract the value out of the input and pass that on to the intent.",
"children": [],
"created": 1426178895328,
"modified": 1426178927218,
"collapsed": true,
"type": "base"
},
{
"id": "s1uv3w3icvr0vx029pw4wo7y82198rj4",
"created": 1426177395579,
"modified": 1426177395579,
"collapsed": true,
"content": "You then declare your component as a simple combination of the model and view:",
"type": "base",
"children": []
},
{
"id": "3q84218zjkfr9x2ywhn85u2v4g8lpv40",
"content": "let MyStatefulComponent = {\n view: MyStatefulView,\n model: MyStatefulModel,\n}\n\n// later use:\n<MyStatefulComponent name=\"Judith\"/>",
"children": [],
"created": 1426176473222,
"modified": 1426177394284,
"collapsed": true,
"type": "ipython",
"language": "javascript"
},
{
"id": "zo26pqwlee6h2umopz31wriff89k35bt",
"created": 1426176593693,
"modified": 1426176593693,
"collapsed": true,
"content": "In a `StatefulComponent`, the view rerenders whenever state changes.",
"type": "base",
"children": []
}
]
},
{
"id": "yqeh624obn2h3qckzvyqqhq3slbhnr1a",
"created": 1426177880050,
"modified": 1426177880050,
"collapsed": true,
"content": "## Complex Interactions and `Intent`",
"type": "base",
"children": [
{
"id": "i8s5bsdt8m4op9dn0gbg0f43az8wnnci",
"created": 1426178050439,
"modified": 1426178050439,
"collapsed": true,
"content": "> If we're getting to the `tldr` stage, you can always [jump to the code] or [the demos].",
"type": "base",
"children": []
},
{
"id": "9c6j0cvw3vxketlvi4zdfsddfoe7kwfn",
"created": 1426177959238,
"modified": 1426177959238,
"collapsed": true,
"content": "Lots of the demos involving Reactive Streams in javascript revolve around complex interactions that are really smooth in Rx, but complicated elsewhere. So let's jump into that with the prototypal {} `drag n drop` example.",
"type": "base",
"children": []
},
{
"id": "o0r61dj1izndwyvi33qmfwh4xasw11mi",
"created": 1426178148071,
"modified": 1426178148071,
"collapsed": true,
"content": "While you can get pretty far with just `onClick` and `onChange` handlers (the DOM gives you a fair amount), sometimes what you really want is rich interaction that is some combination of lower-level events.",
"type": "base",
"children": []
},
{
"id": "mzhnhjbnc2twzish1j7xjkpa28m1rmbg",
"created": 1426178233105,
"modified": 1426178233105,
"collapsed": true,
"content": "For drag and drop, we want to start dragging on `mousedown`, then respond to `mousemove`, and stop dragging on mouseup.",
"type": "base",
"children": []
},
{
"id": "s51ryu4p40qkmpm6k336y9cqzsqlxo9q",
"created": 1426178264234,
"modified": 1426178264234,
"collapsed": true,
"content": "> for this example, I've made some assumptions for the sake of brevity. There is a full [dragndrop demo] which has all the features you actually want, with not too many more lines of code.",
"type": "base",
"children": []
},
{
"id": "k11idgl13lamud99gqljjgttn5xlwzro",
"created": 1426178338568,
"modified": 1426178338568,
"collapsed": true,
"content": "function DragIntent(intents, events) {\n events('mousedown').onValue(down => {\n // signal that we're dragging\n intents('dragging').emit(true)\n // this stream takes mousemove events, sending along the x and y, until there is a mouseup\n let drags = events('mousemove').takeUntilBy(events('mouseup')).map(evt => {\n evt.preventDefault()\n return {x: evt.pageX, y: evt.pageY}\n })\n // forward the stream to the `dragPos` intent.\n intents('dragPos').plug(drags)\n // the `drags` stream has ended, meaning there was a `mouseup` event. Stop dragging.\n drags.onEnd(_ => intents('dragging').emit(false))\n })\n}",
"type": "ipython",
"children": [],
"language": "javascript"
},
{
"id": "vq75gprzgxka4mszvtbmd72zoqck0xo9",
"created": 1426178742913,
"modified": 1426178742914,
"collapsed": true,
"content": "Now lets look at how the model manages those intents:",
"type": "base",
"children": []
},
{
"id": "7ilqj6ek9kddvdijjhfqw3imvf1skoen",
"created": 1426178751773,
"modified": 1426178751773,
"collapsed": false,
"content": "function DragModel(props, intents) {\n return {\n pos$: intents('dragPos').toProperty({x: 0, y: 0}),\n dragging$: intents('dragging').toProperty(false),\n children$: props('children'),\n }\n}",
"type": "ipython",
"children": [],
"language": "javascript"
},
{
"id": "h4947c2jyikiymyymfgvj3gwtmbavedj",
"created": 1426178769244,
"modified": 1426178769244,
"collapsed": true,
"content": "Couldn't be simpler. The `pos$` value stream starts at `0,0` and then responds to the `dragPos` intent, `dragging$` stream responds to the `dragging` intent, and the `children$` stream is a straight passthrough from props.",
"type": "base",
"children": []
},
{
"id": "c29hesl4nwglli7ddflel2c7s3x59d7u",
"created": 1426178704771,
"modified": 1426178704771,
"collapsed": true,
"content": "The view then simply takes the data from the model and renders it to a tree, while also `transcluding` its children. It registers the events that are required by the `Intent` function.",
"type": "base",
"children": [],
"language": "javascript"
},
{
"id": "vuzf0j362keq7ebp4s4235r8pc604xx9",
"created": 1426178637006,
"modified": 1426178637006,
"collapsed": true,
"content": "function DragView([pos, dragging, children], intents, events) {\n return <div\n style={{\n backgroundColor: dragging ? 'yellow' : 'white',\n position: 'absolute',\n top: pos.y + 'px',\n left: pos.x + 'px',\n }}\n onMouseDown={events('mousedown')}\n onMouseMove={events('mousemove')}\n onMouseUp={events('mouseup')}>\n {children}\n </div>\n}",
"type": "ipython",
"children": [],
"language": "javascript"
},
{
"id": "roc8ahw4n947p6us9cwzvreyxkc60urr",
"created": 1426178688780,
"modified": 1426178688780,
"collapsed": true,
"content": "[TODO animated GIF]",
"type": "base",
"children": [],
"language": "javascript"
},
{
"id": "015rs566qmszuw1p6i890jsknad3fnix",
"created": 1426178700426,
"modified": 1426178700426,
"collapsed": true,
"content": "Then it can be used like:",
"type": "base",
"children": []
},
{
"id": "3dxp0by2zy84azdarm0qkwwl9f46no51",
"created": 1426179406976,
"modified": 1426179406976,
"collapsed": true,
"content": "let Drag = {\n model: DragModel,\n view: DragView,\n intent: DragIntent,\n}\n\ndx.render(<Drag>\n <h3>Drag me around!</h3>\n You know you want to.\n</Drag>, document.body)",
"type": "ipython",
"children": [],
"language": "javascript"
}
]
},
{
"id": "urvxgdax8fbg6a97ywqqag8zhq4wumya",
"created": 1426186413480,
"modified": 1426186413480,
"collapsed": true,
"content": "## A note about mixins",
"type": "base",
"children": [
{
"id": "3kajvcxwc58m8tcc618codmqj9zanm3p",
"created": 1426186418343,
"modified": 1426186533547,
"collapsed": true,
"content": "In the drag example, all of the logic for dragging was contained in the `DragIntent` function, which simply expects a few events and outputs a few intents. There's nothing stopping you from doing",
"type": "base",
"children": []
},
{
"id": "6hfat5lbujuv4xmyuyr3b7qc2w7j5qdd",
"created": 1426186568883,
"modified": 1426186568883,
"collapsed": true,
"content": "function SomeOtherThingsThatNeedsDragIntent(intents, events) {\n DragIntent(intents, events) // make dragging work!\n // do other event/intent processing\n}",
"type": "ipython",
"children": [],
"language": "javascript"
},
{
"id": "f6kmhftm6xnqm5tu2ru64q98oyqdak87",
"content": "See how simple that was?",
"children": [],
"created": 1426186636521,
"modified": 1426186642253,
"collapsed": true,
"type": "base"
}
]
},
{
"id": "qsb9wtq6yvzwj9tiwowv5mmzqfbec0xk",
"created": 1426184996105,
"modified": 1426184996105,
"collapsed": true,
"content": "## Complex Data and the Model",
"type": "base",
"children": [
{
"id": "iromm1ecjrv54cr5ct6hv6e7i9vjfu0p",
"created": 1426191876726,
"modified": 1426191876726,
"collapsed": true,
"content": "**TODO maybe break this into a separate post**",
"type": "base",
"children": []
},
{
"id": "u5864dixm0546lfptarabzu8323mvadn",
"created": 1426191609924,
"modified": 1426191609924,
"collapsed": false,
"content": "So far nothing we've looked at really justifies the existance of the `Model` component. The Model functions have pretty much just passed through the props and intent data untouched. Now we'll look at a slightly more complicated example data-wise: a drop-down menu.",
"type": "base",
"children": []
},
{
"id": "hwollnzpchgxldc8rj8uieq5cifn5lav",
"created": 1426191935435,
"modified": 1426191935435,
"collapsed": true,
"content": "This time I'll start by showing the view:",
"type": "base",
"children": []
},
{
"id": "9tg2b1wvxhqylnzcz8kqbmepp8hs6o7q",
"created": 1426191942200,
"modified": 1426191942200,
"collapsed": true,
"content": "function MenuView([items, highlight, selection], intents) {\n return <ul>\n {items.map((name, i) => <li\n onMouseOver={intents('move', i)}\n style={{\n backgroundColor: (selection === i ? '#a5a' : (highlight === i ? '#faf' : 'white'))\n }}>{name}</li>)}\n </ul>\n}",
"type": "ipython",
"children": [],
"language": "javascript",
"waiting": false,
"started": 1426193462747,
"session": "3egaryvdyi26pqlw1q4opx29p06701pr",
"executed": "function MenuView([items, highlight, selection], intents) {\n return <ul>\n {items.map((name, i) => <li\n onMouseOver={intents('move', i)}\n style={{\n backgroundColor: (selection === i ? '#a5a' : (highlight === i ? '#faf' : 'white'))\n }}>{name}</li>)}\n </ul>\n}",
"finished": 1426193462823,
"display_collapsed": false,
"outputs": []
},
{
"id": "02j2p8jybhc97bqwp6oz4ugh3h6rzpmx",
"created": 1426192283661,
"modified": 1426192283661,
"collapsed": true,
"content": "Pretty straightforward menu display. One thing that happens is firing off a menu \"move\" event when an item is hovered.",
"type": "base",
"children": [],
"language": "javascript"
},
{
"id": "b03ej04so72ml1huysgtbnov84gr2zsx",
"content": "let CODES = {\n 13: 'ENTER',\n 38: 'UP_ARROW',\n 40: 'DOWN_ARROW',\n 9: 'TAB',\n 27: 'ESC',\n}\nlet MOVES = {\n UP_ARROW: 'up',\n DOWN_ARROW: 'down',\n TAB: 'down',\n ENTER: 'select',\n ESC: 'clear',\n}",
"children": [],
"created": 1426192413572,
"modified": 1426192466457,
"collapsed": true,
"type": "ipython",
"language": "javascript",
"waiting": false,
"started": 1426193465727,
"session": "3egaryvdyi26pqlw1q4opx29p06701pr",
"executed": "let CODES = {\n 13: 'ENTER',\n 38: 'UP_ARROW',\n 40: 'DOWN_ARROW',\n 9: 'TAB',\n 27: 'ESC',\n}\nlet MOVES = {\n UP_ARROW: 'up',\n DOWN_ARROW: 'down',\n TAB: 'down',\n ENTER: 'select',\n ESC: 'clear',\n}",
"finished": 1426193465741,
"display_collapsed": false,
"outputs": []
},
{
"id": "g8zagathehnbd7di4ytvk5jdfvs5vnm3",
"created": 1426192323892,
"modified": 1426192323892,
"collapsed": true,
"content": "function MenuIntent(intents, events) {\n intents('move').plug(events('$mount').flatMapLatest(node => {\n return Kefir.fromEvent(node.ownerDocument, 'keydown')\n .map(e => CODES[e.keyCode])\n .filter(k => !!k)\n .map(k => MOVES[k])\n }))\n}",
"type": "ipython",
"children": [],
"language": "javascript",
"waiting": false,
"started": 1426193466578,
"session": "3egaryvdyi26pqlw1q4opx29p06701pr",
"executed": "function MenuIntent(intents, events) {\n intents('move').plug(events('$mount').flatMapLatest(node => {\n return Kefir.fromEvent(node.ownerDocument, 'keydown')\n .map(e => CODES[e.keyCode])\n .filter(k => !!k)\n .map(k => MOVES[k])\n }))\n}",
"finished": 1426193466599,
"display_collapsed": false,
"outputs": []
},
{
"id": "ani26joej990jwhe4v3jqbh0kavdi595",
"created": 1426192330803,
"modified": 1426192330803,
"collapsed": true,
"content": "The `Intent` side of things also has something new: lifecycle events. `$mount` is fired when the component is mounted, and provides the associated node. This component then listens to global keydown handlers and passes commands to the `move` intent.",
"type": "base",
"children": [],
"language": "javascript"
},
{
"id": "6d8vbrmi15on67j99wo2mia8lx932h9q",
"created": 1426192644971,
"modified": 1426192644971,
"collapsed": true,
"content": "> One of the awesome things about reactive streams is that they're generally pretty good at taking care of themselves. For example, when that `keydown` stream is no longer `active` (observed by anyone down the line), it will remove the event listener from the document.",
"type": "base",
"children": []
},
{
"id": "enac1zii65dhiq361i4awuquk23mvnpd",
"created": 1426192789548,
"modified": 1426192789548,
"collapsed": true,
"content": "Now we get to the meat: the model. How are moves translated into highlight and selection indices?",
"type": "base",
"children": []
},
{
"id": "tonaqkmhyhx2svntih1skmi957i5vmtl",
"created": 1426192726506,
"modified": 1426192726506,
"collapsed": true,
"content": "function MenuModel(props, intents) {\n let list$ = props('list')\n let move$ = intents('move').merge(list$.mapTo('resize'))\n let highlight$ = Kefir.sampledBy([list$], [move$]).scan(moveHighlight, 'none')\n let selection$ = Kefir.sampledBy([list$, highlight$], [move$]).scan(moveSelect, 'none')\n\n return {\n list$,\n highlight$,\n selection$,\n }\n}\n\nfunction moveHighlight(highlighted, [list, move]) {\n let ln = list.length\n if ('number' === typeof move) {\n return move // item index sent by a mouse hover\n }\n if (highlighted === 'none') {\n switch (move) {\n case 'up': return ln - 1\n case 'down': return 0\n }\n return highlighted\n }\n if (highlighted >= ln) highlighted = ln - 1\n switch (move) {\n case 'up': return highlighted === 0 ? ln - 1 : highlighted - 1\n case 'down': return (highlighted + 1) % ln\n }\n return highlighted\n}\n\nfunction moveSelect(selected, [list, highlight, move]) {\n let ln = list.length\n if (move === 'select') return highlight\n if (move === 'clear') return 'none'\n if (selected >= ln) selected = ln - 1\n return selected\n}",
"type": "ipython",
"children": [],
"language": "javascript",
"waiting": false,
"started": 1426193478330,
"session": "3egaryvdyi26pqlw1q4opx29p06701pr",
"executed": "function MenuModel(props, intents) {\n let list$ = props('list')\n let move$ = intents('move').merge(list$.mapTo('resize'))\n let highlight$ = Kefir.sampledBy([list$], [move$]).scan(moveHighlight, 'none')\n let selection$ = Kefir.sampledBy([list$, highlight$], [move$]).scan(moveSelect, 'none')\n\n return {\n list$,\n highlight$,\n selection$,\n }\n}\n\nfunction moveHighlight(highlighted, [list, move]) {\n let ln = list.length\n if ('number' === typeof move) {\n return move // item index sent by a mouse hover\n }\n if (highlighted === 'none') {\n switch (move) {\n case 'up': return ln - 1\n case 'down': return 0\n }\n return highlighted\n }\n if (highlighted >= ln) highlighted = ln - 1\n switch (move) {\n case 'up': return highlighted === 0 ? ln - 1 : highlighted - 1\n case 'down': return (highlighted + 1) % ln\n }\n return highlighted\n}\n\nfunction moveSelect(selected, [list, highlight, move]) {\n let ln = list.length\n if (move === 'select') return highlight\n if (move === 'clear') return 'none'\n if (selected >= ln) selected = ln - 1\n return selected\n}",
"finished": 1426193478386,
"display_collapsed": false,
"outputs": []
},
{
"id": "8d5x64yo98e7n6th74garogyik0f78w7",
"created": 1426192980006,
"modified": 1426192980006,
"collapsed": true,
"content": "Notice that in the `MenuModel`, there's no notion of \"current state\" anywhere, *it's all streams*. `stream.scan()` handles the \"current highlight\" and \"current select\" logic, passing them in as the first param to `moveHighlight` and `moveSelect`.",
"type": "base",
"children": []
},
{
"id": "t5dn0duy1f7x231ejyyu8tbbf8xgyw0z",
"created": 1426194009558,
"modified": 1426194009558,
"collapsed": true,
"content": "Another wonderful thing is that the actual logic has been placed in `pure functions`. For a given set of input, they will always return the same output. This makes unit testing almost trivial.",
"type": "base",
"children": []
},
{
"id": "0hqalp80j6a6x5fmovgt31a50sdrhrc8",
"created": 1426194118341,
"modified": 1426194118341,
"collapsed": true,
"content": "One perhaps not-immediately-obvious thing is the purpose of line `3`, where the list stream is merged into the `move` stream as the command `resize`. This makes it so that when the list is changed (ex. in response to a dynamic query), the highlight and selected ",
"type": "base",
"children": []
}
]
},
{
"id": "7f6yvlld05t0yzyr25ez8szqavupj6rd",
"created": 1426179591339,
"modified": 1426179591339,
"collapsed": false,
"content": "## TODO `autocomplete` dropdown subitem (list selection) as an example of why the `Model` is a thing.",
"type": "base",
"children": []
},
{
"id": "em2oq473b3iis6vqma1hgduzpmz757fl",
"created": 1426222668697,
"modified": 1426222668697,
"collapsed": true,
"content": "## FLUX and all its friends",
"type": "base",
"children": []
}
]
},
{
"id": "0fvch6vcbbm0folph29i2tbj01boo0a2",
"created": 1426174978750,
"modified": 1426174978750,
"collapsed": false,
"content": "## requestAnimationFrame coordination with the `RaffleMaster`",
"type": "base",
"children": [
{
"id": "q32435spqe1ankuqs9k2hpamb9yjpfgx",
"created": 1426174993078,
"modified": 1426174993079,
"collapsed": true,
"content": "`vdom` source code, coordinating RAF across view render streams",
"type": "base",
"children": []
}
]
},
{
"id": "q6zithklydfvd0kw4uxlzebz2njwblka",
"created": 1426222035946,
"modified": 1426222035946,
"collapsed": false,
"content": "## \"Let's a make a front-end reactive framework with VirtualDOM and Kefir.js\"",
"type": "base",
"children": [
{
"id": "t8pnc5qnzik1h8kgng493zcqu7jwrsv6",
"created": 1426222059793,
"modified": 1426222059793,
"collapsed": true,
"content": "C'mon, it'll be fun!",
"type": "base",
"children": []
},
{
"id": "an4iastjpkprgstg2hkdrgphsl1cfan6",
"created": 1426222312177,
"modified": 1426222312178,
"collapsed": true,
"content": "The really hard part is deciding on best practices, conventions, and APIs. I've taken care of all of that, so we'll just get into the actual creation of it. This is best consumed with a tall glass of milk and some cookies. I can wait.",
"type": "base",
"children": []
},
{
"id": "zlwxbloeyjk6f5qha5w1khrpqrl5etcn",
"created": 1426222373656,
"modified": 1426222373656,
"collapsed": true,
"content": "Virtual DOM, a primer",
"type": "base",
"children": []
},
{
"id": "i7wlhu3gctzw4li5kkiqt0qpgzdlb9ri",
"created": 1426222380813,
"modified": 1426222380813,
"collapsed": true,
"content": "Kefir.js - what, streams?",
"type": "base",
"children": []
},
{
"id": "k89e3k4l6nf82mcvqdpvf0gamodk0xj6",
"created": 1426222387438,
"modified": 1426222387438,
"collapsed": true,
"content": "",
"type": "base",
"children": []
}
]
}
]
}
]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment