{"id":13432553,"url":"https://github.com/algesten/trifl","last_synced_at":"2025-03-22T08:31:00.930Z","repository":{"id":57379898,"uuid":"30568820","full_name":"algesten/trifl","owner":"algesten","description":"[DEFUNCT] trifling functional views","archived":false,"fork":false,"pushed_at":"2017-03-19T14:40:34.000Z","size":177,"stargazers_count":36,"open_issues_count":4,"forks_count":1,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-05-01T00:48:42.230Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/algesten.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-02-10T01:36:54.000Z","updated_at":"2022-09-09T11:57:51.000Z","dependencies_parsed_at":"2022-09-05T13:50:29.283Z","dependency_job_id":null,"html_url":"https://github.com/algesten/trifl","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/algesten%2Ftrifl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/algesten%2Ftrifl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/algesten%2Ftrifl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/algesten%2Ftrifl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/algesten","download_url":"https://codeload.github.com/algesten/trifl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244931456,"owners_count":20534007,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T02:01:13.315Z","updated_at":"2025-03-22T08:31:00.596Z","avatar_url":"https://github.com/algesten.png","language":"JavaScript","readme":"[DEFUNCT] trifl\n=====\n\nThis project is not maintained.\n\nCurrently I build stuff using:\n\n* react\n* [react-elem](https://github.com/algesten/react-elem)\n* [refnux](https://github.com/algesten/refnux)\n* [broute](https://github.com/algesten/broute)\n\nThis combination is roughly what trifl tried to achieve.\n\n------\n\n[![Build Status](https://travis-ci.org/algesten/trifl.svg)](https://travis-ci.org/algesten/trifl) \n\n\u003e trifling functional views\n\nMotivation\n----------\n\nThere are a bunch of user interface libraries breaking new ground into\nvirtual dom and unidirectional data flow, however they mostly follow a\nnon-functional programming style. Trifl tries to put functions first.\n\nCheck out the tutorial\n----------------------\n\nCheck out [the tutorial][pages].\n\nInstallation\n------------\n\n### Installing with Bower\n\n```bash\nbower install -S trifl\n```\n\nThis exposes the global object `trifl`.\n\n#### With coffeescript\n\nUse destructuring assignment to pick out the functions wanted.\n\n```coffee\n{action, updated, handle, view, layout, region, route, path, exec, navigate} = trifl\n\naction 'dostuff', arg\n```\n\nInstall all functions in global scope.\n\n```coffee\ntrifl.expose window        # All trifl functions are now in window.\ntrifl.tagg.expose window   # Tons of functions. Use at own risk!\n```\n\nPick functions to install in global scope.\n\n```coffee\ntrifl.expose window, 'handler', 'action'\ntrifl.tagg.expose window, 'div', 'p'\n```\n\n#### With javascript\n\nUse the functions off the `trifl` object or declare them separate.\n\n```javascript\ntrifl.action('dostuff', arg);\n\n// or\n\nvar action   = trifl.action;\nvar updated  = trifl.updated;\nvar handle   = trifl.handle;\nvar view     = trifl.view;\nvar layout   = trifl.layout;\nvar region   = trifl.region;\nvar route    = trifl.route;\nvar path     = trifl.path;\nvar exec     = trifl.exec;\nvar navigate = trifl.navigate;\n\naction('dostuff', arg);\n\n```\n\nInstall all functions in global scope.\n\n```javascript\ntrifl.expose(window);        // All trifl functions are now in window.\ntrifl.tagg.expose(window);   // Tons of functions. Use at own risk!\n```\n\nPick functions to install in global scope.\n\n```javascript\ntrifl.expose(window, 'handler', 'action');\ntrifl.tagg.expose(window, 'div', 'p');\n```\n\n### Installing with NPM\n\n```bash\nnpm install -S trifl\n```\n\nOverview\n--------\n\nTrifl is a functional web client user interface library with a\nunidirectional dataflow and a [virtual dom][vdom]. Compared to other\nlibraries, trifl makes less out of dispatchers, controllers and model\nstores.\n\nTrifl consists of three parts: [actions](#actions-and-handlers),\n[views](#views-and-layouts) and [router](#router-and-paths). The\n*actions* are helper functions to aid decoupling of the application\nparts. *Views* are render functions whose purpose is make dom nodes\nreflect some model state. The *router* is a utility for organising a\nurl space into visible views and firing actions as results of url\nchanges.\n\nTrifl doesn't make components out of *dispatchers* and *controllers* –\nthey are simple functions, and *models* are nowhere to be found –\nimplement them any way you want.\n\nThere's more theory in [the tutorial][pages].\n\nAPI\n---\n\nThe API consists of 10 functions plus [tagg][tagg].\n\n**Action:** [`action`](#action) [`updated`](#updated) [`handle`](#handle)\n\n**View:** [`view`](#view) [`layout`](#layout) [`region`](#region)\n\n**Route:** [`route`](#route) [`path`](#path) [`exec`](#exec) [`navigate`](#navigate)\n\nTagg is a template library for writing markup as coffeescript.\n\n### Actions and Handlers\n\n#### action\n\n`action(n, a1, a2, ...)`\n\nDispatches an action named `n` providing variable arguments to the\nhandler. Only one action can be dispatched at a time apart from\n[`updated`](#updated). The intention is to dispatch actions as a result\nof user input or asynchronous model updates (such as ajax responses).\n\n`:: string, aa, ab, ... , az -\u003e a`\n\narg | desc\n:---|:----\nn   | String name of action to perform. Will call the `handler` for this name.\nas  | Variadic arguments forwarded to the handler function.\nret | Return value from the handler function.\n\n\n##### action example\n\n```coffee\nhandler 'dostuff', (v) -\u003e   # declare a handler\n    console.log \"handler says: #{v}\"\n    return v * 2\n\nr = action 'dostuff', 42    # prints \"handler says: 42\"\n                            # r is now 84\n```\n\n### updated\n\n`updated(n)`\n\nUpdated is a special class of action, without arguments, that is\nallowed to be dispatched during action handling. Updates are used to\nto signal that a model has been updated and needs re-rendering in the\nviews. Updates are deduped and handled, in order, after the current\naction is finished.\n\n`:: string -\u003e undefined`\n\narg | desc\n:---|:----\nn   | String name of update. Internally this gets transformed to `\"update:\u003cn\u003e\"`.\nret | always `undefined`\n\n##### updated example\n\n```coffee\n# model code\nsearchModel = {\n    setSearchText: (@text) -\u003e\n        @requestSearch @text    # request an ajax search with @text\n        @state = 'requesting'   # state indicator\n        updated 'searchmodel'   # tell the views we have changed\n}\n\n# dispatcher code\nhandler 'setsearchtext', (t) -\u003e # declare a handler\n    searchMode.setSearchText t  # propagate action to model\n\n# UI code\naction 'setsearchtext', input.value  # action for input changing\n\n```\n\n#### handle\n\n`handle(n, f)`\n\nDeclares a handler function `f` for action name `n`. There can only be one\nhandler for each action and redeclaring the handler will overwrite the\nprevious. `f` will receive the variadic arguments passed in\n[`action`](#action)\n\nWe sometimes refer to pure action handlers as \"dispatchers\" to be\nanalogous with other libraries.\n\nHandlers for updated actions are prefixed `update:` such that\n`update('mymodel')` should be handled with\n`handle 'update:mymodel', -\u003e`. Update handlers never receive any\narguments.\n\nWe refer to update handlers as \"controllers\" to describe parallels\nwith other software libraries.\n\narg | desc\n:---|:----\nn   | String name of handler to declare. Use `update:\u003cname\u003e` for update handlers.\nf   | Function to declare as handler.\nret | always `f`\n\n##### handle example\n\n```coffee\nhandle 'stuff', (a) -\u003e\n    # do stuff with a\n\naction 'stuff', 42   # pass 42 to handler\n```\n\nSee [updated example](#updated-example) for how to bind an update handler.\n\n\n\n### Views and Layouts\n\n#### view\n\n`v = view(f)`\n\nCreates wrapped view function `v` around `f`. The wrapped function\ntypically use [`tagg`][tagg] to draw a dom tree from a state\npreferably provided as function arguments to the created view\nfunction. These functions are sometimes refered to as \"render\nfunctions\".\n\nInternally, view functions use [virtual dom][vdom], and the intention\nis to \"draw a lot\". Rather than micro managing view functions to draw\nsmall incremental parts of the page, we rely on virtual dom to make\ndifferential updates (patches) to the actual dom. This means we prefer\nto pass entire models to the view functions.\n\n`:: ((aa, ab, ..., az) -\u003e ?) -\u003e ((aa, ab, ..., az) -\u003e el)`\n\narg | desc\n:---|:----\nf   | Function to wrap as a view function.\nret | The view function.\n\n##### The created view function\n\n`v(a1, a2, ...)`\n\nThe dom element is both the return value when invoking the function\nand exposed as a property on the function object (lovely javascript):\n\n```coffee\nv = view(f)\nel = v(...)  # dom element\nv.el         # dom element\n```\n\nThe initial state of `v.el`, before the view function has ever been\ninvoked, is an empty placeholder `\u003cdiv\u003e\u003c/div\u003e`. Calling the function\ncan change this to any other tag.\n\narg | desc\n:---|:----\nas  | Variadic arguments that will be passed to the inner function `f`.\nret | The dom element rendered.\n\n##### event handlers\n\nAny attribute prefixed `on` will be treated as an event handler and added\nto handle events using `node.addEventListener`. I.e. if you want to listen\nto `click` or `MyEvent` you would do\n\n```coffee\ndiv onclick: (ev) -\u003e\n    # this function is added using node.addEventListener 'click', fn\ndiv onMyEvent: (ev) -\u003e\n    # this function is added using node.addEventListener 'MyEvent', fn\n```\n\n##### mutation observers\n\nA DOM `MutationObserver` is added using the attribute `observe`\n\nThe attribute has two forms\n\n1. With a straight handler function `observe:(mutations) -\u003e`. This\nwill use defaults observation options: `{childList:true,\nattributes:true, attributeOldValue:true, subtree:true}`\n\n2. With an object `observe:{callback:handler, options:{...}}` where\nthe object has `callback` function for the mutations and `options` to\nspecify which observe options to use.\n\n```coffee\ndiv observe:{\n    options:{characterData:true}\n    callback: (mutations) -\u003e\n        # deal with mutations\n    }\n```\n\n##### view example\n\n```coffee\nv = view (newslist) -\u003e\n    ul class:'newslist', -\u003e\n        newslist.forEach (news) -\u003e\n            li key:news.id, -\u003e\n                a href:\"/news/#{news.slugid}\", news.title, onclick -\u003e\n                    navigate \"/news/#{news.slugid}\"\n                span class=\"desc\", news.description\n\nv.el                             # is currently a placeholder \u003cdiv\u003e\u003c/div\u003e\ndocument.body.appendChild v.el   # insert into dom\n\nv(model.newslist)                # draw view. changes placeholder to \u003cul\u003e...\nv.el                             # is now \u003cul\u003e...\u003c/ul\u003e\n```\n\n#### layout\n\n`l = layout(f)`\n\nCreates a special class of view function that use\n[`region(n)`](#region) in the wrapped `f` function to create \"pigeon\nholes\" in the dom where other views can be inserted.\n\nLayouts *organize* the dom into named placeholders that can display any\nother view.\n\n**Note:** Layouts differs from views in that they *always* invoke `f`\n  – without any arguments – straight away when created.\n\n`:: ((aa, ab, ..., az) -\u003e ?) -\u003e ((aa, ab, ..., az) -\u003e el)`\n\narg | desc\n:---|:----\nf   | Function to wrap as a layout view function.\nret | The layout view function.\n\n\n##### The created layout function\n\nThe created function bevaves exactly like other\n[view function](#the-created-view-function) also exposing an `.el`\nproperty.\n\n*However the layout is always rendered upon creation.*\n\nLayouts can take arguments, but must not rely upon them, since the\nfirst invokation, when created, will not provide any.\n\n##### Region functions\n\nAny [`region`](#region) declared in the wrapped function `f` will be\nexposed as properties alongside `.el`. These are called *region\nfunctions*.\n\nNotice that `region` is just syntactic sugar. We can manually insert\nregions in the layout by using tag attributes `data-region=\"name\"`.\n\nRegion functions are lazy inside [`route`](#route) and\n[`action`](#action). They don't actuate the change to the dom until\nthe route/action is finished.\n\n##### layout example\n\n```coffee\nl = layout -\u003e\n    div -\u003e\n        div region('leftnav'), class:'left'\n        div region('main'), class:'main'\n\nl.el        # the outer \u003cdiv\u003e\nl.leftnav   # region function for inner div.left\nl.main      # region function for inner div.main\n```\n\nRegion functions are \"setters\" that take one argument, another view,\nand inserts that `view.el` into the region.\n\n```\nl = layout -\u003e ...  # create a layout with region('leftnav')\nv = view -\u003e ...    # create a view\n\nl.leftnav(v)       # put view in leftnav region\n```\n\n#### region\n\n`region(n)`\n\nSpecial [`tagg`][tagg] attribute that inserts a `data-region=\"\u003cn\u003e\"`\ninto the dom used by [`layout`](#layout).\n\n`:: string -\u003e {k:v}`\n\narg | desc\n:---|:----\nn   | String name of the region function to expose on the [`layout`](#layout) function.\nret | An attribute object `{\"data-region\":\u003cn\u003e}`\n\n##### region example\n\nSee [layout example](#layout-example).\n\n\n\n### Router and Paths\n\n#### route\n\n`route(f)`\n\nDeclares the route function `f` which will be invoked each time the\nurl changes. There can only be one such function. The url is\n\"consumed\" and \"executed\" using nested scoped\n[`path`](#path)/[`exec`](#exec) functions.\n\n`:: (() -\u003e) -\u003e undefined`\n\narg | desc\n:---|:----\nf   | The one and only route function.\nret | Always `undefined`\n\n##### route usage\n\nThe following usage shows how nested `path` declarations creates\n\"scoped\" functions that consumes part of the current url.\n\n```coffee\n# the current url is: \"/some/path/deep?panda=42\"\n\nroute -\u003e\n\n    path '/some', -\u003e\n        # at this point we \"consumed\" '/some'\n        exec (part, query) -\u003e\n            # part is '/path'\n            # query is {panda:42}\n\n        path '/deep', -\u003e\n            exec (part, query) -\u003e\n                # part is ''\n                # query is {panda:42}\n\n    # at this point we haven't consumed anything\n    exec (part, query) -\u003e\n        # part is '/some/path'\n        # query is {panda:42}\n\n    path '/another', -\u003e\n        # will not be invoked for the current url\n\n```\n\n##### route example\n\nThis tries to illustrate a more realistic example, including\n[layout](#layout), [region](#region) and [action](#action).\n\n```coffee\nisItem = (part, query) -\u003e part?.length \u003e 1     # '' means list\n\nroute -\u003e\n\n    appview.top navbar          # show navbar view in top region\n    appview.main homeview       # show home view in main region\n\n    path '/news/', -\u003e           # consume '/news/'\n        if exec isItem                            # test if this is a news item\n            exec (slugid) -\u003e                      # use exec to get slugid from scoped path\n                action 'selectarticle', slugid    # fire action to fetch article\n                appview.main articleview          # show articleview in main region\n        else\n            action 'refreshnewslist'\n            appview.main newslistview             # show newslist view in main region\n\n    path '/aboutus', -\u003e                           # consume '/aboutus'\n        appview.main aboutusview                  # show aboutus view in main region\n```\n\nNotice that [region functions](#region-functions) and\n[navigate](#navigate) are lazy inside `route`, which means it is fine\nto call `appview.main` many times. Only the last call to\n`appview.main` will be run when the route function finishes. The same\ngoes for `navigate`, only the last `navigate` will be used.\n\n#### path\n\n`path(p,f)`\n\nAs part of [`route`](#route) declares a function `f` that is invoked\nif we \"consume\" url part `p` of the current (scoped) url.\n\narg | desc\n:---|:----\np   | The string url part to match/consume.\nf   | Function to invoke when url part matches.\nret | Always `undefined`\n\n##### path example\n\nSee [route usage](#route-usage) and [route example](#route-example).\n\n#### exec\n\n`exec(f)`\n\nAs part of [`route`](#route) executes `f` with arguments\n`(part,query)` for the current path scope.\n\narg | desc\n:---|:----\nf   | Function to invoke with `(part,query)`\nret | The result of the executed function.\n\n##### exec example\n\nSee [route usage](#route-usage) and [route example](#route-example).\n\n#### navigate\n\n`navigate(l)`\n`navigate(l, false)`\n\nNavigates to the location `l` using [pushState][push] and checks to\nsee if the url changed in which case the [route function](#route) is\nexecuted.\n\nThe function takes an optional second boolean argument that can be\nused to supress the execution of the route function.\n\nThis function is lazy when used inside [route](#route), only the last\nlocation will be used when the route function finishes.\n\n`:: string -\u003e undefined`\n`:: string, boolean -\u003e undefined`\n\narg | desc\n:---|:----\nl   | The (string) location to navigate to. Can be relative.\nt   | Optional boolean set to false to supress route function triggering.\nret | always `undefined`\n\n##### navigate example\n\n```coffee\n# if browser is at \"http://my.host/some/where\"\n\nnavigate 'other'    # changes url to \"http://my.host/some/other\"\nnavigate '/news'    # changes url to \"http://my.host/news\"\n\n\nnavigate '/didnt', false  # changes url to \"http://my.host/didnt\"\n                          # without running the route function\n```\n\nAcknowledgements\n----------------\n\nTrifl is inspired by:\n\n* [Flux](https://facebook.github.io/flux/) for unidirectional ideas.\n* [React](https://facebook.github.io/react/) for the virtual dom,\n  although we use another [virtual dom implementation][vdom].\n* [Backbone](http://backbonejs.org/) for views and considering url\n  routing a core component.\n* [Marionette](http://marionettejs.com/) for layouts and regions.\n\nLicense\n-------\n\nThe MIT License (MIT)\n\nCopyright © 2015 Martin Algesten\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n[tagg]: https://github.com/algesten/tagg\n[vdom]: https://github.com/Matt-Esch/virtual-dom\n[push]: https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history#The_pushState()_method\n[pages]: http://algesten.github.io/trifl/\n","funding_links":[],"categories":["JavaScript","Libraries"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falgesten%2Ftrifl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falgesten%2Ftrifl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falgesten%2Ftrifl/lists"}