{"id":20275893,"url":"https://github.com/kazanexpress/bound","last_synced_at":"2025-07-13T13:34:12.877Z","repository":{"id":57359854,"uuid":"146223454","full_name":"KazanExpress/bound","owner":"KazanExpress","description":"Data-binding made easy","archived":false,"fork":false,"pushed_at":"2018-10-09T12:09:22.000Z","size":562,"stargazers_count":24,"open_issues_count":0,"forks_count":2,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-25T03:41:40.776Z","etag":null,"topics":["binding","bindings","bound","cool","customizable","decent","framework","javascript","new","reactive","two-way-bindings","two-way-data-binding","two-way-databinding","two-way-synchronization","types","typescript","typescript-library","work-in-progress"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/KazanExpress.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":".github/code-of-conduct.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-08-26T23:14:13.000Z","updated_at":"2023-11-22T15:19:05.000Z","dependencies_parsed_at":"2022-09-06T21:24:36.607Z","dependency_job_id":null,"html_url":"https://github.com/KazanExpress/bound","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KazanExpress%2Fbound","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KazanExpress%2Fbound/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KazanExpress%2Fbound/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KazanExpress%2Fbound/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/KazanExpress","download_url":"https://codeload.github.com/KazanExpress/bound/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248351401,"owners_count":21089269,"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":["binding","bindings","bound","cool","customizable","decent","framework","javascript","new","reactive","two-way-bindings","two-way-data-binding","two-way-databinding","two-way-synchronization","types","typescript","typescript-library","work-in-progress"],"created_at":"2024-11-14T13:11:40.215Z","updated_at":"2025-04-11T05:42:16.155Z","avatar_url":"https://github.com/KazanExpress.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# simple-bound\n\u003cp align=\"center\"\u003e\n\n  \u003cbig\u003eA simple data binding library for node and browser with no dependencies.\u003c/big\u003e\n\n  \u003ca href=\"https://travis-ci.org/KazanExpress/bound\"\u003e\n    \u003cimg src=\"https://img.shields.io/travis/KazanExpress/bound/master.svg?style=flat-square\"\n      alt=\"Travis (.org) branch\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/simple-bound\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/simple-bound.svg?style=flat-square\"\n      alt=\"npm\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/KazanExpress/bound\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/github-repo-lightgray.svg?style=flat-square\"\n      alt=\"\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/simple-bound?activeTab=dependencies\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/dependencies-none-blue.svg?style=flat-square\"\n      alt=\"Travis (.org) branch\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/simple-bound\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/dt/simple-bound.svg?style=flat-square\"\n      alt=\"npm\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\u003cbr/\u003e\n\n```bash\nnpm install --save simple-bound\n```\nFor more options see [installation](#installation)\n\n## Why?\n\nMany JavaScript libraries and frameworks use some form of data-binding under the hood. That binding usually comes in various shapes and sizes, all of which end up in the \"vendor\" part of your project. But currently there's no truly utilitary and versatile solution. So most of the time you want to use the power of, let's say, two-way binding in you own library or project - you have to code that part from scratch.\n\n`Bound` aims to change that.\n\n`Bound` is currently in the alpha state, there might be ~~some~~ a lot of bugs. Feel free to report them in the [issues section](https://github.com/KazanExpress/bound/issues/new). 🙂\n\n---\n\n## Table of contents\n\n- [What is Bound?](#what-is-bound)\n- [Installation](#installation)\n- [Simple example](#simple-example)\n- [How it works](#how-it-works)\n- [API](#api)\n  - [TLDR (examples)](#tldr)\n  - [Binding](#binding)\n    - [Constructor](#binding-constructor)\n    - [Instance](#binding-instance)\n    - [Static fields](#binding-static-fields)\n  - [Bound](#bound)\n    - [Constructor](#bound-constructor)\n    - [Instance](#bound-instance)\n    - [Static fields](#bound-static-fields)\n- [Coming Soon](#coming-soon)\n\n---\n\n## What is Bound?\n\nBound is a small and customizable library that allows precise data binding management.\n\nBut what is data binding?\n\n[Data binding](https://en.wikipedia.org/wiki/Data_binding) is a general technique that binds data sources of two general types (`provider` and `consumer`, `master` and `slave`) and syncronizes them.\n\nFor example, let's imagine two objects bound together using this technique:\n\n```js\nobject1 /* {\n  property: 'value'\n} */\n\nobject2 /* {\n  property: 'value'\n} */\n\nobject1.property = 'new value'\nconsole.log(object2.property) // outputs 'new value'\n\nobject2.property = 'another value'\nconsole.log(object1.property) // outputs 'another value'\n```\n\nAs we can see here, both objects seem to react to changes in one another, updating their properties reactively.\n\nMost applications and frameworks use this approach to synchronize their models and views, as described in this [article](https://www.wintellect.com/data-binding-pure-javascript/).\nBut data-binding is not only useful for synchronizing views to models - there are a lot of applications as use-cases for this technique. And `Bound` aims to cover as most of them as possible.\n\nIt does not focus entirely on model-view data-binding, but rather tries to encapsulate the whole concept of data binding, allowing you to control where your bindings go and what your bindings do.\n\n### General concepts\n\nAll \"members\" of the data-binding relationship can be called **subscribers**.\nThere are two general types of data flow between subscribers in data-binding: two-way (when both objects' properties react to each other, example above) and one-way.\n\nIn one-way data-binding there exist two types of binding subscribers:\n - \"masters\" - dictate changes to all other subscribers in the relationship.\n - \"slaves\" - accept changes from masters but cannot broadcast/dicate their own changes\n\n`Bound` allows to handle both one-way and two-way data bindings with ease.\n\n---\n\n## Installation\n\n### Install as dependency\n\n```bash\nnpm install --save simple-bound\n# or\nyarn add simple-bound\n```\n\n### Import and use\n\n**ES**\n```js\nimport Bound from 'simple-bound'\n\n// You also can import separate modules:\nimport Binding from 'simple-bound/dist/lib/binding'\nimport BaseBound from 'simple-bound/dist/lib/bound/base'\n```\n\n**CommonJS**\n```js\nconst Bound = require('simple-bound').default;\n```\n\n**Script tag**\n```html\n\u003cscript src=\"https://unpkg.com/simple-bound\" onload=\"bound.Bound = bound.default\"\u003e\u003c/script\u003e\n\n\u003cscript\u003e\n  const binding = new bound.Binding(false, 'value');\n\u003c/script\u003e\n```\n\n---\n\n## Simple example\n\nLet's say you want to bind two objects together in a way that a change to one object would automatically change the other.\n\nIt's very simple to do with `Bound`:\n\n```js\nconst obj1 = {\n  prop: 'foo'\n};\n\nconst obj2 = {\n  prop: 'foo'\n};\n\n// Send the proto object to snapshot the structure.\nconst bound = new Bound(obj1 /* Used for snapshoting the object structure, not for the actual binding */);\n\n// Bind both objects via Bound instance:\nbound.bind(obj1);\nbound.bind(obj2);\n\nobj1.prop = 'bar';\nconsole.log(obj2.prop);\n// -\u003e \"bar\"\n// Magic!\n```\n\n---\n\n## How it works\n\n### Binding relationships\n\nEach time a [new binding relationship](#binding) is created, the data is stored inside that relationship. After that, subscribers can be added to the relationship.\n\nEach \"master\" subscriber's value points to the value inside the relationship, therefore automatically sharing it with others:\n```js\nlet obj = { test: 'foo' };\nlet obj2 = { test: 'foo' };\n\nconst binding = new Binding(/* twoWay */ true, /* defaultValue */ '');\n\nbinding.addSubscriber(obj, 'test');\nbinding.addSubscriber(obj2, 'test');\n\n// Now both obj.test and obj2.test point to the same variable inside `binding`\n```\n\nEach \"slave\" subscriber's value remains within its original container, but is updated whenever a __master__'s value changes via a [notification](#binding-instance):\n```js\nlet obj = { test: 'foo' };\nlet obj2 = { test: 'foo' };\n\nconst binding = new Binding(/* twoWay */ false, /* defaultValue */ '');\n\nbinding.addSubscriber(obj, 'test', 'master');\nbinding.addSubscriber(obj2, 'test', 'slave');\n\nobj2.test = 'bar'; // Nothing happens here, obj.test is still 'foo'\n\nobj.test = 'new value'; // Notification is sent to obj2, updating its `test` property to 'new value'\n```\n\n### Object relationships\n\nThe [`Bound`](#bound) class groups relationships together in a form of an object to subscribe all of their fields to changes.\n\nEssentially, it maps all bound object's properties to their [binding relationships](#binding) using internal [storage](#bound-instance) that contains these relationships in a map identical to the object itself.\n\n---\n\n## API\n\nAPI can seem a bit tricky at first, because the concept of data-binding does not imply any uniformal way of doing it.\n\nWe recommend checking out an [interactive playground at RunKit](https://npm.runkit.com/simple-bound) for better intuitive understanding of how things work.\n\n### TLDR\n\n\u003cdetails\u003e\u003csummary\u003eClick to expand\u003c/summary\u003e\n\n```js\nimport Bound, {\n  bound,\n  Binding\n} from 'simple-bound';\n\n// Creates a Bound instance from an object snapshot\nconst bound = new Bound({\n  test: 'foo',\n  nested: {\n    property: 2\n  }\n});\n\nbound.boundObject // The first actual bound object.\nbound.storage // Stores bindings in a structure identical to the original object.\n\n\nlet obj = {\n  test: 'foo'\n}\n\nlet obj2 = {\n  test: 'foo'\n}\n\n// Creates a Bound instance from the object and returns instance.boundObject\nconst justABoundObject = bound({\n  test: 'prop'\n});\n\njustABoundObject.__bound__ // Bound instance\njustABoundObject.__bound__.bind(obj);\n\njustABoundObject.test = 'bar';\nconsole.log(obj.test); // -\u003e \"bar\"\n\njustABoundObject.__bound__.storage // { test: Binding {} }\njustABoundObject.__bound__.boundObject // === justABoundObject\n\nobj = justABoundObject.__bound__.unbind(obj); // obj is now free from bindings\n\n\n// Binding class\nconst binding = new Binding(/* twoWay */ false, /* defaultValue */ '');\n\nbinding.addSubscriber(obj, 'test', 'master');\nbinding.addSubscriber(obj2, 'test', 'slave');\n\n// Updates all subscribers\nbinding.set('test'); // obj.test === 'test' \u0026\u0026 obj2.test === 'test'\n\n// Master: updates all subscribers\nobj.test = 'foo';    // obj2.test === 'foo' \u0026\u0026 binding.get() === 'foo'\n\n// Slave: updates only itself\nobj2.test = 'bar';   // obj.test === 'foo' \u0026\u0026 binding.get() === 'foo'\n\n// Master: updates all subscribers\nobj.test = 'baz';    // obj2.test === 'baz' \u0026\u0026 binding.get() === 'baz'\n\nbinding.removeSubscriber(obj2); // By object reference\nbinding.removeSubscriber(0); // By index\nbinding.clearSubscribers();\n```\n\n\u003c/details\u003e\n\n---\n\n### Binding\n\u003e Stores and updates single property bindings.\n\nCan be thought of as a primitive in terms of data-binding.\nIts main responsibility is to manage single-property bindings, hence the name - `Binding` as in \"one, single binding\".\n\nIt helps to manipulate bindings on the lowest possible level.\n\nCreation of a `Binding` instance is equivalent to the creation of a new data-binding relationship.\nEach newly added member \"subscribes\" to notifications about changes in the relationship's value.\n\n#### Binding constructor\n```js\nnew Binding(/* is always two-way */ false, /* default initial value */ 'value')\n```\n\nargument     | type      | description\n-------------|-----------|--------------\ntwoWay       | `boolean` | Determines if all the bindings associated with the instance should be two-way\ndefaultValue | `any`     | Sets the default value for subscribers that do not have a value.\n\n#### Binding instance\n```js\nconst instance = new Binding(false, 'value');\n```\n\n##### Properties\n\u003e ```ts\n\u003e instance.subscribers: Array\u003cISubscriber\u003e\n\u003e ```\n\u003e \n\u003e Contains an array of subscribers in the following format:\n\u003e ```js\n\u003e {\n\u003e   obj: subscriberObject,\n\u003e   prop: 'subscriberObjectPropertyKey',\n\u003e   role: 'slave' | 'master' | undefined\n\u003e }\n\u003e ```\n\n\u003e ```ts\n\u003e instance.twoWay: boolean\n\u003e ```\n\u003e READ-ONLY\n\u003e \n\u003e Defines if a binding should always be 2-way and ignore roles.\n\n\u003e ```js\n\u003e instance.value\n\u003e ```\n\u003e PROTECTED\n\u003e\n\u003e Stores the value of the binding relationship for \"masters\".\n\n\u003e ```ts\n\u003e instance.plugins: Array\u003cIBindingPlugin\u003e \n\u003e ```\n\u003e\n\u003e An array of plugins to use.\n\n##### Methods\n\u003e ```js\n\u003e instance.get()\n\u003e ```\n\u003e A generic get function that is applied to subscribers.\n\u003e Gets `instance.value`.\n\n\u003e ```js\n\u003e intance.set('new value')\n\u003e ```\n\u003e A generic set function that is applied to subscribers.\n\u003e Sets `instance.value` and notifies subscribers.\n\n\u003e ```js\n\u003e instance.notify('value')\n\u003e ```\n\u003e Asynchroniously notifies slave subscribers about the value change.\n\n\u003e ```js\n\u003e instance.addSubscriber(obj, 'propName', 'master' | 'slave' | undefined)\n\u003e ```\n\u003e Binds an object prop and subscribes it to master-subscribers' changes.\n\n\u003e ```js\n\u003e instance.addMasterSubscriber(obj, 'propName')\n\u003e instance.addSlaveSubscriber(obj, 'propName')\n\u003e ```\n\u003e Binds an object prop as a master or slave and subscribes it to master-subscribers' changes\n\n\u003e ```js\n\u003e instance.removeSubscriber(obj, 'propName') // by reference\n\u003e instance.removeSubscriber(2) // by index\n\u003e ```\n\u003e Unbinds an object's property and unsubscribes it from changes.\n\n\u003e ```js\n\u003e instance.clearSubscribers()\n\u003e ```\n\u003e Clears all subscribers from the binding.\n\n\n#### Binding static fields\n\u003e ```js\n\u003e Binding.config === {\n\u003e   debug: false\n\u003e }\n\u003e ```\n\u003e Global binding config. Changes affect all instances.\n\n\u003e ```js\n\u003e Binding.subscriptionsEqual(subscriber1, subscriber2)\n\u003e ```\n\u003e Checks subscribers' objects for reference and prop-name equality.\n\n---\n\n### Bound\n\u003e Allows multiple full-object bindings.\n\nStores bindings and binds objects together, providing the highest possible abstraction level for bindings.\n\n#### Bound constructor\n```js\nnew Bound({ property: 'value' })\n```\n\nCreates an instance of Bound using a proto object.\n\n#### Bound instance\n```js\nconst bound = new Binding({ property: 'value' });\n```\n\n##### Bound properties\n\u003e ```ts\n\u003e bound.storage: IBindingStorage\n\u003e ```\n\u003e Stores bindings in a structure that is identical to the binding-prototype-object.\n\n\u003e ```js\n\u003e bound.boundObject\n\u003e ```\n\u003e A bound object created from a constuctor's snapshot object.\n\u003e Contains an instance of the Bound class itself by the `__bound__` key.\n\n##### Bound methods\n\u003e ```js\n\u003e bound.bind(obj, /* two-way? */ true)\n\u003e ```\n\u003e Binds an object to all other current subscribers.\n\u003e If the second argument is false\n\n\u003e ```js\n\u003e bound.unbind(/* object reference */ obj)\n\u003e ```\n\u003e Unbinds an object and destroys all of its listeners\n\n##### Bound static fields\n\u003e ```js\n\u003e Bound.config === {\n\u003e   debug: false\n\u003e }\n\u003e ```\n\u003e Global binding config. Changes affect all instances.\n\n\u003e ```js\n\u003e Bound.isBound(/* object reference */ obj)\n\u003e ```\n\u003e Checks whether an object has already been bound.\n\n---\n\n## Coming Soon\n\n  - Plugins\n  - Event listeners\n  - Interceptors and pipes (maybe?)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkazanexpress%2Fbound","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkazanexpress%2Fbound","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkazanexpress%2Fbound/lists"}