{"id":15574609,"url":"https://github.com/tjvr/koel","last_synced_at":"2025-07-28T08:05:07.460Z","repository":{"id":35216262,"uuid":"39475393","full_name":"tjvr/koel","owner":"tjvr","description":"Observables meet Sugared DOM. ","archived":false,"fork":false,"pushed_at":"2017-08-14T09:18:57.000Z","size":27,"stargazers_count":5,"open_issues_count":2,"forks_count":1,"subscribers_count":4,"default_branch":"dev","last_synced_at":"2025-07-06T03:49:38.862Z","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/tjvr.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-07-21T23:44:59.000Z","updated_at":"2018-03-31T09:53:17.000Z","dependencies_parsed_at":"2022-08-04T00:30:09.467Z","dependency_job_id":null,"html_url":"https://github.com/tjvr/koel","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tjvr/koel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjvr%2Fkoel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjvr%2Fkoel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjvr%2Fkoel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjvr%2Fkoel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tjvr","download_url":"https://codeload.github.com/tjvr/koel/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjvr%2Fkoel/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267482004,"owners_count":24094508,"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","status":"online","status_checked_at":"2025-07-28T02:00:09.689Z","response_time":68,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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-10-02T18:20:14.562Z","updated_at":"2025-07-28T08:05:07.428Z","avatar_url":"https://github.com/tjvr.png","language":"JavaScript","readme":"`koel` is a javascript library in two parts.\n\n- The first part is a tiny, efficient rewrite of\n  [http://knockoutjs.com/](knockout.js).\n\n- The second is a rewrite of [the `el()`\n  function](http://blog.fastmail.com/2012/02/20/building-the-new-ajax-mail-ui-part-2-better-than-templates-building-highly-dynamic-web-pages/)\n  used in FastMail.\n\n## ko ##\n\n**ko** is a tiny data binding library. It lets you make two things:\n\n  - **Observables**, which wrap a value (such as a string or a number).\n\n    ```js\n    var fruitOfTheDay = ko('banana');\n    ```\n\n      - To **read** from an observable, call it like you would a function:\n\n        ```js\n        fruitOfTheDay(); // =\u003e 'banana'\n        ```\n\n      - To **write** to an observable, use `.assign()`.\n\n        ```js\n        fruitOfTheDay.assign('melon');\n        ```\n\n- **Computeds**, which wrap a function.\n\n    ```js\n    var lunch = ko(function() {\n      return soupOfTheDay() + ' followed by ' + fruitOfTheDay();\n    });\n    ```\n\n    ko does **dependency detection**: while the function is executing, it\n    remembers every observable which the computed reads from. It sets up a\n    subscription to each dependency, and will re-compute the function when any\n    of them change.\n\n    The return value of the function is used as the new value. Any subscribers\n    to the computed will be notified of the change.\n\n    The function should not return an Observable.\n\n    Computeds are a kind of observable: you read from them in the same way. You\n    may read from one computed inside another.\n\n    You can't assign to a computed.\n\nThat's about it; observables and computeds make up the core of ko.\n\n\n### Additional Methods ###\n\nObservables also support the following:\n\n  - \u003ccode\u003e**.subscribe(**function() { … } _[_, callNow_]_**)**\u003c/code\u003e\n\n    Explicitly subscribe to the observable.\n\n    ```js\n    fruitOfTheDay.subscribe(function(fruit) {\n      document.title = fruit;\n    });\n    ```\n\n    Every time the observable's value changes, the callback will be invoked\n    with the new value. This is useful when you want to run a function every\n    time a value changes, but unlike a computed, the function doesn't produce a\n    result.\n\n    The return value of the function is ignored.\n\n    subscribe() takes an optional second argument **callNow**, which\n    defaults to true.  If it's true, it will run the callback immediately.\n\n  - \u003ccode\u003e**.compute(**function(value) { … }**)**\u003c/code\u003e\n\n    Convenience method. The following are identical:\n\n    ```js\n    observable.compute(f)\n    ```\n\n    ```js\n    computed(function() {\n      var value = observable();\n      return f(value);\n    });\n    ```\n\n  - \u003ccode\u003e**.destroy()**\u003c/code\u003e\n\n    Remove all subscribers from the observable.\n\n    If it's a computed, this will also remove the related subscriptions from\n    each of our dependencies.\n\n### Extensions ###\n\nYou can extend koel with new types of observable, if you need to send more\nspecific updates than assignment. This is the mechanism used [by\nobservableArray](#arrays).\n\n  - \u003ccode\u003e**.subscribe**({ name: function() { … }, … }**)**\u003c/code\u003e\n\n    Handle an event with the given name. It takes a dict, so you can pass\n    multiple event handlers.\n\n    emit() pops its name argument and calls each handler with the remaining\n    arguments. Handlers are called in the order they were defined.\n\n  - \u003ccode\u003e**.emit**(name _[_, arguments… _]_**)**\u003c/code\u003e\n\n    Emit an event describing a change to this observable.\n\nAll events change the observable's value, so using `.subscribe()` will get you\nall kinds of changes. If you want to handle only direct assignment events --\nthat is, calls to .assign() -- listen for the `'assign'` event.\n\n### The ko object ###\n\nThere are also the following methods on `ko` itself:\n\n  - **`ko.observable()`** -- in case you really want an observable function.\n\n  - **`ko.computed()`** -- will fail if its argument *isn't* a function.\n\n  - \u003ccode\u003e**ko.subscribe(**v, function(value) { … }**)**\u003c/code\u003e -- subscribe to\n    v if its an observable, otherwise just pass it straight to the callback.\n\n    More efficient than the equivalent\n    `ko.observable(v).subscribe(function(value) { … })`, since it doesn't\n    create an observable if it doesn't need to.\n\n  - **`ko.isObservable(v)`** -- is v an observable?\n\n  - **`ko.plugin()`** -- a simple way of extending the `ko()` function.\n\n\n## el ##\n\n**el** is a helper function for creating DOM elements.\n\nFor background, read the [blog post about Sugared DOM](http://blog.fastmail.com/2012/02/20/building-the-new-ajax-mail-ui-part-2-better-than-templates-building-highly-dynamic-web-pages/), which this function is based on.\n\nel takes three arguments: `selector`, `attrs`, and `children`. Either or both\nof the last two arguments may be omitted.\n\n  - **`selector`** looks like a CSS selector. It consists of a tag name,\n    followed by `#the-id` and then many `.class-names`. Any of them may\n    be omitted; the default tag is `div`.\n\n    Examples:\n\n    ```js\n    el('span');       // =\u003e \u003cspan /\u003e\n    el('.hat-shop');  // =\u003e \u003cdiv class=\"hat-shop\" /\u003e\n    el('');           // =\u003e \u003cdiv /\u003e\n\n    el('div#main.blue.very-big');\n    // =\u003e \u003cdiv id=\"main\" class=\"blue very-big\" /\u003e\n    ```\n\n  - **`attrs`** is a dictionary of attributes. These will be set as attributes\n    on the resulting DOM element.\n\n    Example:\n\n    ```js\n    el('a', {\n      href: 'http://google.com',\n      target: '_blank',\n    }, \"follow this link\");\n    ```\n\n    The following property aliases are supported: `class` `className`\n    `defaultValue` `for` `html` `text` `value`\n\n    All escaping is handled by the browser.\n\n    If a value is an observable, el will automatically **subscribe** to it,\n    updating the attribute whenever the observable changes.\n\n    If you give an observable to the **value** property, el will set up\n    appropriate `change` event listeners. This is handy for elements like\n    `input` and `select`. (This doesn't work for computeds, since they can't be\n    assigned.)\n\n    To bind **event handlers**, use special \u003ccode\u003eon_*«event»*\u003c/code\u003e\n    attributes:\n\n    ```js\n    el('button', {\n      on_click: function(event) {\n        // do stuff\n      },\n    }, \"click me\");\n    ```\n\n  - **`children`** is a string or an array.\n\n    If it's a string, it will be used instead of the `textContent` property.\n    (You may not set the text both ways on the same element.)\n\n    Otherwise, each element of the array is either a string or a DOM element.\n    Strings will be converted into text nodes.\n\n    children may be an observable.\n\n    Examples:\n\n    ```js\n    el('h1', \"Hi there!\");  // =\u003e \u003ch1\u003eHi there!\u003c/h1\u003e\n\n    var score = ko(6);\n    el('p', ['You have ', el('span', score), ' new messages!']);\n    // =\u003e \u003cp\u003eYou have \u003cspan\u003e6\u003c/span\u003e new messages!\u003c/p\u003e\n    ```\n\n    Final example, using [observable arrays](#arrays):\n\n    ```js\n    var cheeses = ko(['cheddar', 'stilton', 'brie']);\n    el('ul', cheeses);\n\n    cheeses.push('camembert');\n    ```\n\n\n## Arrays ##\n\nkoel has a bonus third part: **observable arrays**.\n\nPlain observable arrays wouldn't be that useful, since you wouldn't be able to\ntell what changed.\n\n```js\nvar animals = ko([\n  'cow',\n  'sheep',\n  'horse',\n]);\nfunction addPillow() {\n  animals.assign(animals.concat(['pillow']));\n}\nanimals.subscribe(function(newArray) {\n  /*\n   * we have the new array -- but what changed?!\n   */\n});\naddPillow();\n```\n\nko includes an array plugin which uses observable's event-emitter system to\ngive more useful updates. el uses it in order to do efficient DOM updates.\n\nObservable arrays have the following wrapper interface:\n\n  - \u003ccode\u003e**insert(**index, item**)**\u003c/code\u003e -- insert before item at index\n  - \u003ccode\u003e**replace(**index, item**)**\u003c/code\u003e -- similar to `animals()[index] =\n    item`\n  - \u003ccode\u003e**remove(**index**)**\u003c/code\u003e -- remove item at index\n  - \u003ccode\u003e**push(**item**)**\u003c/code\u003e -- add item to end of array\n  - \u003ccode\u003e**pop()**\u003c/code\u003e → remove last item\n  - \u003ccode\u003e**shift()**\u003c/code\u003e → remove first item\n\nYou can get more interesting updates by listening for the following events:\n\n```js\nanimals.subscribe({\n  assign: function(newArray) { … },\n  replace: function(index, item) { … },\n  insert: function(index, item) { … },\n  remove: function(index) { … },\n});\n```\n\nUsing `animals.subscribe(function() { … })` would give you updates anytime the\narray changes for any reason. The **assign** handler will only fire when the\nentire array is replaced by calling .assign().\n\n### Derived arrays\n\nThere's also a bonus third `ko` concept, in addition to observables and\ncomputeds: **deriveds**.\n\nIt's useful to be able to call `map` or `filter` on an array, but recomputing it over the whole array wouldn't be terribly efficient:\n\n```js\nel('ul.favourite-animals', ko(function() {\n  return animals().map(function(name) {\n    return el('li', name);\n  });\n});\n```\n\nSo the wrapped interface also includes the following methods, which return a\nderived array.\n\n  - \u003ccode\u003e**map(**function() { … }**)**\u003c/code\u003e\n  - \u003ccode\u003e**filter(**function() { … }**)**\u003c/code\u003e\n\nYou can't modify a derived.\n\nExample:\n\n```js\nel('ul.favourite-animals', animals.map(function(name) {\n  return el('li', name);\n});\n```\n\nAny replace/insert/remove changes are propogated to the derived, being careful\nto only recompute items if their dependencies have changed. So you get\nthe minimal possible change to the derived array. Since el supports this too,\nkoel gives you efficient DOM updates for lists.\n\n-------------------------------------------------------------------------------\n\nQ\u0026A\n===\n\nHere are some questions. No-one's asked them yet, because koel's just a quick\nlibrary I needed for a project. But the answers may be helpful.\n\nWhat if an array element changes?\n---------------------------------\n\nThis is considered an array update:\n\n```js\narray.replace(1, 'four');\n```\n\nThis is not:\n\n```js\narray()[1].fooBar = 'six';\n```\n\nAn array tracks the objects _in_ it, not their _state_. It's just a list of pointers, if you like.\n\nIf you want to track element properties, make them into observables.\n\nCan I have an array of observables?\n-----------------------------------\n\nDon't do that; you'll get confused.\n\nYou could do this instead:\n\n```js\narray = ko([\"cow\", \"sheep\", \"horse\"]);\narray.update(1, \"elephant\");\n\n// -elsewhere in your code-\n\narray.on('replace', function(index, item) {\n  // . . .\n});\n```\n\nOr you could have an array of objects, where some of the object's properties\nmight be observable.\n\nBut ES7 has Object.observe built-in!\n------------------------------------\n\n[Object.observe](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/observe)\ndoes sound cool. But right now, [only Chrome supports\nit](http://caniuse.com/#search=observe).\n\nAnd I prefer observables, since they're an object in their own right\n(\"first-class\"). Object.observe only lets you subscribe to property\nupdates on an object.\n\nDoes it support IE8?\n--------------------\n\nProbably not.\n\nShould I use this?\n------------------\n\nNope. Use a proper framework instead, such as [Overture](http://overturejs.com/).\n\n\"I have a failing case!\"\n------------------------\n\n```js\nvar A = ko(5);\n\nvar filter = ko(function(){\n    var divisor = A();\n    return function(element){\n        return element % divisor === 0;\n    };\n});\n\nvar list = ko(function(){ return [A()]; });\n\nvar filteredList = ko(function(){ var l = list().filter(filter()); console.log(l); return l;});\n\nA.assign(7)\n```\n\nShut up, Dan.\n\n\nLicense\n=======\n\nMIT. (It's small enough that you could always rewrite it yourself anyway.)\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftjvr%2Fkoel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftjvr%2Fkoel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftjvr%2Fkoel/lists"}