{"id":23053509,"url":"https://github.com/component/reactive","last_synced_at":"2025-04-06T09:08:04.121Z","repository":{"id":5253026,"uuid":"6430758","full_name":"component/reactive","owner":"component","description":"Tiny reactive template engine","archived":false,"fork":false,"pushed_at":"2017-03-16T09:43:53.000Z","size":364,"stargazers_count":384,"open_issues_count":12,"forks_count":48,"subscribers_count":21,"default_branch":"master","last_synced_at":"2025-03-30T07:11:56.305Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"lelylan/devices-dashboard-ng","license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/component.png","metadata":{"files":{"readme":"Readme.md","changelog":"History.md","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":"2012-10-28T18:47:31.000Z","updated_at":"2025-03-14T07:18:15.000Z","dependencies_parsed_at":"2022-09-09T22:03:25.084Z","dependency_job_id":null,"html_url":"https://github.com/component/reactive","commit_stats":null,"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/component%2Freactive","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/component%2Freactive/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/component%2Freactive/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/component%2Freactive/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/component","download_url":"https://codeload.github.com/component/reactive/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247457801,"owners_count":20941906,"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-12-16T00:18:28.465Z","updated_at":"2025-04-06T09:08:04.101Z","avatar_url":"https://github.com/component.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# reactive [![Build Status](https://travis-ci.org/component/reactive.svg?branch=master)](https://travis-ci.org/component/reactive)\n\n Simple and Flexible template and view binding engine with support for custom bindings and real-time updates on model changes.\n\n## Installation\n\nWith component:\n```\n$ component install component/reactive\n```\n\nWith npm via [browserify](http://browserify.org/):\n```\n$ npm install reactive\n```\n\n## Quickstart\n\nRendering a basic html template with a predefined data model.\n\n```js\nvar view = reactive('\u003cp\u003eHello {name}!\u003c/p\u003e', {\n  name: 'Adam'\n});\n\n// you can add the view \"element\" to the html whenever you want\n// view.el contains the html element\ndocument.body.appendChild(view.el);\n```\n\n```html\n\u003cp\u003eHello Adam!\u003c/p\u003e\n```\n\n### Handling events\n\nReactive provides an easy way to register handlers for dom events via predefined \"bindings\".\n\n```js\nvar handlers = {\n  clickme: function(ev) {\n    // console.log('button clicked');\n  }\n};\n\nvar template = '\u003cbutton on-click=\"clickme\"\u003eclickme\u003c/button\u003e';\nvar view = reactive(template, {}, {\n  delegate: handlers\n});\n```\n\nA recommended approach is to wrap the `reactive` instance inside of your own *View* classes. See the [Views]() example.\n\n### Iteration\n\nIteration is achieved by using the `each` binding on the element you wish to iterate.\n\n```js\nvar template = '\u003cul\u003e\u003cli each=\"people\"\u003e{this}\u003c/li\u003e\u003c/ul\u003e';\nvar model = {\n  people: ['Sally', 'Billy']\n};\n\nvar view = reactive(template, model);\n```\n\n```html\n\u003cul\u003e\n  \u003cli\u003eSally\u003c/li\u003e\n  \u003cli\u003eBilly\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nYou can push (pop, shift, etc) to the array and the view will be updated accordingly.\n```js\nmodel.people.push('Eve');\n```\n\n```html\n\u003cul\u003e\n  \u003cli\u003eSally\u003c/li\u003e\n  \u003cli\u003eBilly\u003c/li\u003e\n  \u003cli\u003eEve\u003c/li\u003e\n\u003c/ul\u003e\n```\n\n### Hiding and showing elements\n\nDOM elements can be shown or hidden via the `data-visible` and `data-hidden` bindings.\n\nUsing the following html template.\n\n```js\nvar tmpl = '\u003cp data-hidden=\"items.length\"\u003eno items\u003c/p\u003e' +\n  '\u003cul data-visible=\"items.length\"\u003e\u003cli each=\"items\"\u003e{this}\u003c/li\u003e\u003c/ul\u003e';\nvar model = { items: [] };\nvar view = reactive(tmpl, model);\n```\n\nWhen rendering the above, we will see `no items`, because the array is empty.\n\n```js\nmodel.items.push('one');\n```\n\nWill change the output to `· one` and hide `no items`. Notice how `data-visible` and `data-hidden` act in opposite directions.\n\n\n## API\n\n### reactive(string | element, model, [options])\n\nCreate a new reactive instance using `string` or `element` as the template and `model` as the data object. This binds a DOM element to a model.\n\nIf you do not have a data model and want to specify options, you can pass `null` or `{}`. Remember you **must** have this argument before the options argument.\n\nOptions\n\n| option | type | description |\n| --- | --- | --- |\n| delegate | object, instance | an object or instance defining overrides and handlers for properties and events |\n| adapter | function | defines how reactive will interact with the model to listen for changes |\n| bindings | object | define custom bindings (see bindings docs below) |\n\nBind `object` to the given `element` with optional `view` object. When a `view` object is present it will be checked first for overrides, which otherwise delegate to the model `object`.\n\n### set(prop, val)\n\nSet the property `prop` to the given value `val` in the view.\n\n### set({prop: val})\n\nSet multiple properties `prop` and given values `val` in the view.\n\n### get(prop)\n\nGet the value for property `prop`.\n\n### bind(name, fn)\n\n\u003e Recommend using `bindings` option during construction instead. Will be removed in the future.\n\nCreate a new binding called `name` defined by `fn`. See the [writing bindings](#writing-bindings) section for details.\n\n### use(fn)\n\nUse a reactive plugin. `fn` is invoked immediately and passed the reactive instance.\n\n### destroy()\n\nDestroy the reactive instance. This will remove all event listeners on the instance as well as remove the element from the dom.\n\nFires a `destroyed` event upon completion.\n\n## Model Adapters\n\nModel Adapters provide the interface for reactive to interact with your model implementation. By using a custom adapter you can support models from [backbone.js](http://backbonejs.org/#Model), [modella](https://github.com/modella/modella), [bamboo](https://github.com/defunctzombie/bamboo), etc..\n\nYou can make reactive compatible with your favorite model layer by creating a custom adapter. Changes to your model will cause the reactive view to update dynamically. The following API is required for all adapters.\n\n### constructor\n\nThe `adapter` option is a function which accepts one argument, the `model` and should return an instance with all of the adapter methods implemented. *The constructor will be called as a function - without the `new` keyword.*\n\nAs an example, the builtin adapter's constructor is:\n\n```js\nfunction Adapter(model) {\n  if (!(this instanceof Adapter)) {\n    return new Adapter(model);\n  }\n\n  var self = this;\n  self.model = model;\n};\n```\n\nIn addition to the constructor, the adapter must implement these methods:\n\n* subscribe\n* unsubscribe\n* unsubscribeAll\n* set\n* get\n\n### subscribe(prop, fn)\n\nSubscribe to changes for the given property. When the property changes, `fn` should be called.\n\n```js\nAdapter.prototype.subscribe = function(prop, fn) { ... };\n```\n\n### unsubscribe(prop, fn)\n\nUnsubscribe from changes for the given property. The `fn` should no longer be called on property changes for `prop`.\n\n### unsubscribeAll\n\nUnsubscribe all property change events. Used when a reactive instance is being torn down.\n\n### set(prop, val)\n\nSet the property `prop` to the given value `val`.\n\n### get(prop)\n\nGet the value for property `prop`\n\n### Stock Adapters\n\n* [reactive-bamboo](https://github.com/defunctzombie/reactive-bamboo) - an adapter for [bamboo](https://github.com/defunctzombie/bamboo).\n* [reactive-backbone](https://github.com/airportyh/reactive-backbone) - an adapter for Backbone models.\n\n## Plugins\n\nCustom bindings to extend reactive are listed on the [plugins wiki page](https://github.com/component/reactive/wiki#plugins)\n\n\n## Interpolation\n\n  Bindings may be applied via interoplation on attributes or text. For example here\n  is a simple use of this feature to react to changes of an article's `.name` property:\n\n```html\n\u003carticle\u003e\n  \u003ch2\u003e{name}\u003c/h2\u003e\n\u003c/article\u003e\n```\n\n  Text interpolation may appear anywhere within the copy, and may contain complex JavaScript expressions\n  for defaulting values or other operations.\n\n```html\n\u003carticle\u003e\n  \u003ch2\u003e{ name || 'Untitled' }\u003c/h2\u003e\n  \u003cp\u003eSummary: { body.slice(0, 10) }\u003c/p\u003e\n\u003c/article\u003e\n```\n\n Reactive is smart enough to pick out multiple properties that may be used, and\n react to any of their changes:\n\n```html\n\u003cp\u003eWelcome { first + ' ' + last }.\u003c/p\u003e\n```\n\n Interpolation works for attributes as well, reacting to changes as you'd expect:\n\n```html\n\u003cli class=\"file-{id}\"\u003e\n  \u003ch3\u003e{filename}\u003c/h3\u003e\n  \u003cp\u003e\u003ca href=\"/files/{id}/download\"\u003eDownload {filename}\u003c/a\u003e\u003c/p\u003e\n\u003cli\u003e\n```\n\n## Declarative Bindings\n\n  By default reactive supplies bindings for setting properties, listening to events, toggling visibility, appending and replacing elements. Most of these start with \"data-*\" however this is not required.\n\n### data-text\n\nThe `data-text` binding sets the text content of an element.\n\n### data-html\n\nThe `data-html` binding sets the inner html of an element.\n\n### data-\u0026lt;attr\u0026gt;\n\nThe `data-\u003cattr\u003e` bindings allows you to set an attribute:\n\n```html\n\u003ca data-href=\"download_url\"\u003eDownload\u003c/a\u003e\n```\n\n### each\n\nThe `each` binding allows you to iterate a collection of objects within the model:\n\n```html\n\u003cul\u003e\n  \u003cli each=\"children\"\u003e{name}\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nThe model is expected to have a `children` property whose value is an array.\n\n### on-\u0026lt;event\u0026gt;\n\nThe `on-\u003cevent\u003e` bindings allow you to listen on an event:\n\n```html\n\u003cli data-text=\"title\"\u003e\u003ca on-click=\"remove\"\u003ex\u003c/a\u003e\u003c/li\u003e\n```\n\n`remove` is expected to be a method on the specified `delegate` object:\n\n```js\nvar delegate = {\n  remove: function(ev) {\n    console.log('Removing thing!');\n    ...\n  }\n}\n\nreactive(template, model, {\n  delegate: delegate\n});\n```\n\n### data-append\n\n  The `data-append` binding allows you to append an existing element:\n\n```html\n\u003cdiv class=\"photo\" data-append=\"histogram\"\u003e\u003c/div\u003e\n```\n\nThe `histogram` property on the model is expected to contain a DOM element.\n\n### data-replace\n\n  The `data-replace` binding allows you to replace an existing element, and carryover its attributes:\n\n```html\n\u003cdiv class=\"photo\" data-replace=\"histogram\"\u003e\u003c/div\u003e\n```\n\nThe `histogram` property on the model is expected to contain a DOM element.\n\n### data-{visible,hidden}\n\n  The `data-visible` and `data-hidden` bindings conditionally add \"visible\" or \"hidden\" classnames so that you may style an element as hidden or visible.\n\n```html\n\u003cp data-visible=\"hasDescription\" data-text=\"truncatedDescription\"\u003e\u003c/p\u003e\n```\n\n`data-visible` will add a `visible` class if the property is `truthy`. For arrays, use the `.length` property to trigger on empty or non-empty arrays.\n\n`data-hidden` is the opposite of visible and will add a `visibile` class if the value is false and `.hidden` class if the value is truthy.\n\n### data-checked\n\n Toggles checkbox state:\n\n```html\n\u003cinput type=\"checkbox\" data-checked=\"agreed_to_terms\"\u003e\n```\n\n### data-selected\n\n Toggles option state:\n\n```html\n\u003coption data-selected=\"selected\"\u003e\u003c/option\u003e\n```\n\n### Writing bindings\n\nTo author bindings, simply create a function that will accept two arguments, the element and binding value. For example, here is a binding which removes an element when truthy:\n\n```js\nfunction removeIf(el, property){\n  var binding = this;\n  binding.change(function() {\n    if (binding.value(property)) {\n      el.parentNode.removeChild(el);\n    }\n  });\n};\n\nvar template = '\u003cspan remove-if=\"name\"\u003eno name\u003c/span\u003e';\nvar view = reactive(template, { name: 'foobar' }, {\n bindings: {\n  'remove-if': removeIf\n }\n});\n```\n\nNotice that you can call the binding whatever you want when you create your view allowing you to select appropriate names. Binding authors should recommend names that make sense.\n\nHere is another binding which uses [momentjs](http://momentjs.com/) to pretty print a javascript date.\n\n```js\nvar template = '\u003cspan moment=\"timestamp\" format=\"MMM Do YY\"\u003e\u003c/span\u003e';\nvar view = reactive(template, { timestamp: new Date() }, {\n bindings: {\n  'moment': momentFormat\n }\n});\n\nfunction momentFormat(el, property) {\n  var binding = this;\n  var format = el.getAttribute('format');\n  binding.change(function () {\n     var val = binding.value(property);\n     el.innerText = moment(val).format(format);\n  });\n};\n```\n\nWould output the following html\n```html\n\u003cspan\u003eMar 3rd 14\u003c/span\u003e\n```\n\nYou can easily re-use such bindings by making them plugins and enabling them on your instance with `.use()`\n\n## Interpolation\n\n Some bindings such as `data-text` and `data-\u003cattr\u003e` support interpolation. These properties are automatically added to the subscription, and react to changes:\n\n\n ```html\n \u003ca data-href=\"/download/{id}\" data-text=\"Download {filename}\"\u003e\u003c/a\u003e\n ```\n\n## Notes\n\n  Get creative! There's a lot of application-specific logic that can be converted to declarative Reactive bindings. For example here's a naive \"auto-submit\" form binding:\n\n```html\n\u003cdiv class=\"login\"\u003e\n  \u003cform action=\"/user\" method=\"post\" autosubmit\u003e\n    \u003cinput type=\"text\" name=\"name\" placeholder=\"Username\" /\u003e\n    \u003cinput type=\"password\" name=\"pass\" placeholder=\"Password\" /\u003e\n    \u003cinput type=\"submit\" value=\"Login\" /\u003e\n  \u003c/form\u003e\n\u003c/div\u003e\n```\n\n```js\nvar reactive = require('reactive');\n\nvar view = reactive(document.querySelector('.login'), {}, {\n bindings: {\n  autosubmit: autosubmit\n }\n});\n\nfunction autosubmit(el){\n  el.onsubmit = function(e){\n    e.preventDefault();\n    var path = el.getAttribute('action');\n    var method = el.getAttribute('method').toUpperCase();\n    console.log('submit to %s %s', method, path);\n  }\n};\n```\n\n## View patterns\n\nTypically a view object wraps a model to provide additional functionality, this may look something like the following:\n\n```js\nfunction UserView(user) {\n  this.user = user;\n  this.view = reactive(tmpl, user, {\n    delegate: this\n  });\n}\n\nUserView.prototype.clickme = function(ev){ ... }\n```\n\nOften a higher-level API is built on top of this pattern to keep things DRY but this is left to your application / other libraries.\n\nFor more examples view the ./examples directory.\n\n### Run examples and tests\n    \n    $ git clone https://github.com/component/reactive.git\n    $ cd reactive\n    $ npm i\n    $ make\n    $ open examples\n\n    $ make test\n\n## License\n\n  MIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcomponent%2Freactive","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcomponent%2Freactive","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcomponent%2Freactive/lists"}