├── .gitignore ├── .npmignore ├── .nvmrc ├── .travis.yml ├── README.md ├── action.js ├── assets └── trifl-flow.graffle ├── bower.json ├── docs.md ├── idea.coffee ├── index.js ├── lib ├── action.js ├── fun.js ├── route.js ├── trifl.js ├── vdomout.js └── view.js ├── package.json ├── release.sh ├── route.js ├── src ├── action.coffee ├── fun.coffee ├── route.coffee ├── vdomout.coffee └── view.coffee ├── test ├── action-test.coffee ├── globals.coffee ├── mocha.opts ├── route-test.coffee ├── vdomout-test.coffee └── view-test.coffee ├── view.js └── watch /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | public 17 | .DS_Store 18 | .#* 19 | \#* 20 | bower_components 21 | doc 22 | 23 | BrowserStackLocal 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | public 17 | .DS_Store 18 | .#* 19 | \#* 20 | bower_components 21 | doc 22 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "5" 5 | before_script: 6 | coffee -m -o lib -c src 7 | notifications: 8 | email: false 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [DEFUNCT] trifl 2 | ===== 3 | 4 | This project is not maintained. 5 | 6 | Currently I build stuff using: 7 | 8 | * react 9 | * [react-elem](https://github.com/algesten/react-elem) 10 | * [refnux](https://github.com/algesten/refnux) 11 | * [broute](https://github.com/algesten/broute) 12 | 13 | This combination is roughly what trifl tried to achieve. 14 | 15 | ------ 16 | 17 | [![Build Status](https://travis-ci.org/algesten/trifl.svg)](https://travis-ci.org/algesten/trifl) 18 | 19 | > trifling functional views 20 | 21 | Motivation 22 | ---------- 23 | 24 | There are a bunch of user interface libraries breaking new ground into 25 | virtual dom and unidirectional data flow, however they mostly follow a 26 | non-functional programming style. Trifl tries to put functions first. 27 | 28 | Check out the tutorial 29 | ---------------------- 30 | 31 | Check out [the tutorial][pages]. 32 | 33 | Installation 34 | ------------ 35 | 36 | ### Installing with Bower 37 | 38 | ```bash 39 | bower install -S trifl 40 | ``` 41 | 42 | This exposes the global object `trifl`. 43 | 44 | #### With coffeescript 45 | 46 | Use destructuring assignment to pick out the functions wanted. 47 | 48 | ```coffee 49 | {action, updated, handle, view, layout, region, route, path, exec, navigate} = trifl 50 | 51 | action 'dostuff', arg 52 | ``` 53 | 54 | Install all functions in global scope. 55 | 56 | ```coffee 57 | trifl.expose window # All trifl functions are now in window. 58 | trifl.tagg.expose window # Tons of functions. Use at own risk! 59 | ``` 60 | 61 | Pick functions to install in global scope. 62 | 63 | ```coffee 64 | trifl.expose window, 'handler', 'action' 65 | trifl.tagg.expose window, 'div', 'p' 66 | ``` 67 | 68 | #### With javascript 69 | 70 | Use the functions off the `trifl` object or declare them separate. 71 | 72 | ```javascript 73 | trifl.action('dostuff', arg); 74 | 75 | // or 76 | 77 | var action = trifl.action; 78 | var updated = trifl.updated; 79 | var handle = trifl.handle; 80 | var view = trifl.view; 81 | var layout = trifl.layout; 82 | var region = trifl.region; 83 | var route = trifl.route; 84 | var path = trifl.path; 85 | var exec = trifl.exec; 86 | var navigate = trifl.navigate; 87 | 88 | action('dostuff', arg); 89 | 90 | ``` 91 | 92 | Install all functions in global scope. 93 | 94 | ```javascript 95 | trifl.expose(window); // All trifl functions are now in window. 96 | trifl.tagg.expose(window); // Tons of functions. Use at own risk! 97 | ``` 98 | 99 | Pick functions to install in global scope. 100 | 101 | ```javascript 102 | trifl.expose(window, 'handler', 'action'); 103 | trifl.tagg.expose(window, 'div', 'p'); 104 | ``` 105 | 106 | ### Installing with NPM 107 | 108 | ```bash 109 | npm install -S trifl 110 | ``` 111 | 112 | Overview 113 | -------- 114 | 115 | Trifl is a functional web client user interface library with a 116 | unidirectional dataflow and a [virtual dom][vdom]. Compared to other 117 | libraries, trifl makes less out of dispatchers, controllers and model 118 | stores. 119 | 120 | Trifl consists of three parts: [actions](#actions-and-handlers), 121 | [views](#views-and-layouts) and [router](#router-and-paths). The 122 | *actions* are helper functions to aid decoupling of the application 123 | parts. *Views* are render functions whose purpose is make dom nodes 124 | reflect some model state. The *router* is a utility for organising a 125 | url space into visible views and firing actions as results of url 126 | changes. 127 | 128 | Trifl doesn't make components out of *dispatchers* and *controllers* – 129 | they are simple functions, and *models* are nowhere to be found – 130 | implement them any way you want. 131 | 132 | There's more theory in [the tutorial][pages]. 133 | 134 | API 135 | --- 136 | 137 | The API consists of 10 functions plus [tagg][tagg]. 138 | 139 | **Action:** [`action`](#action) [`updated`](#updated) [`handle`](#handle) 140 | 141 | **View:** [`view`](#view) [`layout`](#layout) [`region`](#region) 142 | 143 | **Route:** [`route`](#route) [`path`](#path) [`exec`](#exec) [`navigate`](#navigate) 144 | 145 | Tagg is a template library for writing markup as coffeescript. 146 | 147 | ### Actions and Handlers 148 | 149 | #### action 150 | 151 | `action(n, a1, a2, ...)` 152 | 153 | Dispatches an action named `n` providing variable arguments to the 154 | handler. Only one action can be dispatched at a time apart from 155 | [`updated`](#updated). The intention is to dispatch actions as a result 156 | of user input or asynchronous model updates (such as ajax responses). 157 | 158 | `:: string, aa, ab, ... , az -> a` 159 | 160 | arg | desc 161 | :---|:---- 162 | n | String name of action to perform. Will call the `handler` for this name. 163 | as | Variadic arguments forwarded to the handler function. 164 | ret | Return value from the handler function. 165 | 166 | 167 | ##### action example 168 | 169 | ```coffee 170 | handler 'dostuff', (v) -> # declare a handler 171 | console.log "handler says: #{v}" 172 | return v * 2 173 | 174 | r = action 'dostuff', 42 # prints "handler says: 42" 175 | # r is now 84 176 | ``` 177 | 178 | ### updated 179 | 180 | `updated(n)` 181 | 182 | Updated is a special class of action, without arguments, that is 183 | allowed to be dispatched during action handling. Updates are used to 184 | to signal that a model has been updated and needs re-rendering in the 185 | views. Updates are deduped and handled, in order, after the current 186 | action is finished. 187 | 188 | `:: string -> undefined` 189 | 190 | arg | desc 191 | :---|:---- 192 | n | String name of update. Internally this gets transformed to `"update:"`. 193 | ret | always `undefined` 194 | 195 | ##### updated example 196 | 197 | ```coffee 198 | # model code 199 | searchModel = { 200 | setSearchText: (@text) -> 201 | @requestSearch @text # request an ajax search with @text 202 | @state = 'requesting' # state indicator 203 | updated 'searchmodel' # tell the views we have changed 204 | } 205 | 206 | # dispatcher code 207 | handler 'setsearchtext', (t) -> # declare a handler 208 | searchMode.setSearchText t # propagate action to model 209 | 210 | # UI code 211 | action 'setsearchtext', input.value # action for input changing 212 | 213 | ``` 214 | 215 | #### handle 216 | 217 | `handle(n, f)` 218 | 219 | Declares a handler function `f` for action name `n`. There can only be one 220 | handler for each action and redeclaring the handler will overwrite the 221 | previous. `f` will receive the variadic arguments passed in 222 | [`action`](#action) 223 | 224 | We sometimes refer to pure action handlers as "dispatchers" to be 225 | analogous with other libraries. 226 | 227 | Handlers for updated actions are prefixed `update:` such that 228 | `update('mymodel')` should be handled with 229 | `handle 'update:mymodel', ->`. Update handlers never receive any 230 | arguments. 231 | 232 | We refer to update handlers as "controllers" to describe parallels 233 | with other software libraries. 234 | 235 | arg | desc 236 | :---|:---- 237 | n | String name of handler to declare. Use `update:` for update handlers. 238 | f | Function to declare as handler. 239 | ret | always `f` 240 | 241 | ##### handle example 242 | 243 | ```coffee 244 | handle 'stuff', (a) -> 245 | # do stuff with a 246 | 247 | action 'stuff', 42 # pass 42 to handler 248 | ``` 249 | 250 | See [updated example](#updated-example) for how to bind an update handler. 251 | 252 | 253 | 254 | ### Views and Layouts 255 | 256 | #### view 257 | 258 | `v = view(f)` 259 | 260 | Creates wrapped view function `v` around `f`. The wrapped function 261 | typically use [`tagg`][tagg] to draw a dom tree from a state 262 | preferably provided as function arguments to the created view 263 | function. These functions are sometimes refered to as "render 264 | functions". 265 | 266 | Internally, view functions use [virtual dom][vdom], and the intention 267 | is to "draw a lot". Rather than micro managing view functions to draw 268 | small incremental parts of the page, we rely on virtual dom to make 269 | differential updates (patches) to the actual dom. This means we prefer 270 | to pass entire models to the view functions. 271 | 272 | `:: ((aa, ab, ..., az) -> ?) -> ((aa, ab, ..., az) -> el)` 273 | 274 | arg | desc 275 | :---|:---- 276 | f | Function to wrap as a view function. 277 | ret | The view function. 278 | 279 | ##### The created view function 280 | 281 | `v(a1, a2, ...)` 282 | 283 | The dom element is both the return value when invoking the function 284 | and exposed as a property on the function object (lovely javascript): 285 | 286 | ```coffee 287 | v = view(f) 288 | el = v(...) # dom element 289 | v.el # dom element 290 | ``` 291 | 292 | The initial state of `v.el`, before the view function has ever been 293 | invoked, is an empty placeholder `
`. Calling the function 294 | can change this to any other tag. 295 | 296 | arg | desc 297 | :---|:---- 298 | as | Variadic arguments that will be passed to the inner function `f`. 299 | ret | The dom element rendered. 300 | 301 | ##### event handlers 302 | 303 | Any attribute prefixed `on` will be treated as an event handler and added 304 | to handle events using `node.addEventListener`. I.e. if you want to listen 305 | to `click` or `MyEvent` you would do 306 | 307 | ```coffee 308 | div onclick: (ev) -> 309 | # this function is added using node.addEventListener 'click', fn 310 | div onMyEvent: (ev) -> 311 | # this function is added using node.addEventListener 'MyEvent', fn 312 | ``` 313 | 314 | ##### mutation observers 315 | 316 | A DOM `MutationObserver` is added using the attribute `observe` 317 | 318 | The attribute has two forms 319 | 320 | 1. With a straight handler function `observe:(mutations) ->`. This 321 | will use defaults observation options: `{childList:true, 322 | attributes:true, attributeOldValue:true, subtree:true}` 323 | 324 | 2. With an object `observe:{callback:handler, options:{...}}` where 325 | the object has `callback` function for the mutations and `options` to 326 | specify which observe options to use. 327 | 328 | ```coffee 329 | div observe:{ 330 | options:{characterData:true} 331 | callback: (mutations) -> 332 | # deal with mutations 333 | } 334 | ``` 335 | 336 | ##### view example 337 | 338 | ```coffee 339 | v = view (newslist) -> 340 | ul class:'newslist', -> 341 | newslist.forEach (news) -> 342 | li key:news.id, -> 343 | a href:"/news/#{news.slugid}", news.title, onclick -> 344 | navigate "/news/#{news.slugid}" 345 | span class="desc", news.description 346 | 347 | v.el # is currently a placeholder
348 | document.body.appendChild v.el # insert into dom 349 | 350 | v(model.newslist) # draw view. changes placeholder to