{"id":19019010,"url":"https://github.com/martinrixham/datum","last_synced_at":"2025-04-23T04:46:41.440Z","repository":{"id":23914794,"uuid":"27295113","full_name":"MartinRixham/Datum","owner":"MartinRixham","description":"An opinionated data binding library","archived":false,"fork":false,"pushed_at":"2024-10-29T22:38:34.000Z","size":823,"stargazers_count":12,"open_issues_count":6,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-06T07:12:15.431Z","etag":null,"topics":["data-binding","datum","dom","javascript"],"latest_commit_sha":null,"homepage":"https://datumjs.com","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MartinRixham.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-11-29T07:39:24.000Z","updated_at":"2024-10-29T22:38:33.000Z","dependencies_parsed_at":"2024-10-03T14:23:35.311Z","dependency_job_id":"d2b5dffd-f228-4ade-9cd9-9f50ee41de03","html_url":"https://github.com/MartinRixham/Datum","commit_stats":{"total_commits":482,"total_committers":3,"mean_commits":"160.66666666666666","dds":0.008298755186721962,"last_synced_commit":"e0a813310ba9b292825f272e6e651506669c4a76"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MartinRixham%2FDatum","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MartinRixham%2FDatum/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MartinRixham%2FDatum/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MartinRixham%2FDatum/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MartinRixham","download_url":"https://codeload.github.com/MartinRixham/Datum/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249323126,"owners_count":21251097,"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":["data-binding","datum","dom","javascript"],"created_at":"2024-11-08T20:10:30.868Z","updated_at":"2025-04-17T07:31:27.319Z","avatar_url":"https://github.com/MartinRixham.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Datum.js\n\nAn opinionated data binding library.\n\n## The Basics of Data Binding with Datum\n\n### Initialisation\n\nThe two components of a web page built with Datum.js are the *template* and the *view model*.\nThe template is the HTML between the `\u003cbody\u003e \u003c/body\u003e` tags before Datum.js has modified it.\nYou the developer are responsible for putting the template in the body of the DOM.\nDatum.js is agnostic about how you do this.\n(If you're having trouble jQuery's [load](https://api.jquery.com/load/) method is a good option.)\n\nThe view model is a single JavaScript object that contains all of the data that will be displayed on the web page and all of the logic for interacting with the web page.\nYou tell Datum.js what object this is by passing it to a constructor called `BindingRoot`.\n\n    var viewModel = {};\n    \n    new BindingRoot(viewModel);\n    \nDeclaring the binding root can be done only once, presumably as soon as the page loads.\n\n### The View Model\n\nThe view model has two kinds of property, *data properties* and *bindings*.\nThe data properties contain the data that define the state of the view model.\nThe bindings are special objects that define how that data is displayed on the page.\nYou can create a binding using the `Binding` constructor.\n\n    var viewModel = {\n    \n        myDatum: \"Hello world.\",\n        myBinding: new Binding()\n    };\n    \n    new BindingRoot(viewModel);\n    \n### Binding Attributes\n\nEach binding property has at least one corresponding *binding attribute* on the template.\nThis attribute tells Datum.js which DOM element to bind each binding to.\nA binding attribute is always called `data-bind` and its value is the name of the binding property in the view model.\n\n    \u003cbody\u003e\n      \u003cdiv data-bind=\"myBinding\"\u003e\u003c/div\u003e\n    \u003c/body\u003e\n\n### Callbacks\n\nThe binding `myBinding` above will have no effect on the page.\nTo make something happen you need to construct the binding with a callback.\nThere are a few different callbacks you can use.\nThe one called `text` is used to put text on the page.\n\n    var viewModel = {\n    \n        myDatum: \"Hello world.\",\n        myBinding: new Binding({\n        \n            text: function() { return this.myDatum; }\n        })\n    };\n    \n    new BindingRoot(viewModel);\n    \n### Text\n\nAs seen above, the *text callback* sets the text content of the elements to which it is bound.\nThe callback can contain any logic so long as it returns the value which is to be displayed.\nIf any of the data properties used in the body of the callback changes, the text callback will be reevaluated and the text on the page updated.\nIn the example above you can change the text on the page by assigning to the `myDatum` property.\n\n    viewModel.myDatum = \"Eh up planet.\";\n    \nThe text callback has a parameter which is the element to which it is bound.\nYou can also create a text binding using the `Text` constructor, but generally it is more useful to use the `Binding` constructor so you can also pass it some of the callbacks described below.\n\n    var viewModel = {\n    \n        myDatum: \"Hello world.\",\n        myBinding: new Text(function(element) { return this.myDatum; })\n    };\n    \n    new BindingRoot(viewModel);\n    \n### Value\n\nThe *value callback* provides a two way binding between an `\u003cinput /\u003e` element and a data property.\nIt is a single callback that is called when either a change event is triggered on the input element or one of the data dependencies of the callback changes.\nIn the first case the value of the input element is passed as the first parameter to the callback.\nIn the second case `undefined` is passed.\nThe second parameter is always the element to which it is bound.\n\n```\n\u003cbody\u003e\n  \u003cinput type=\"text\" data-bind=\"myBinding\" /\u003e\n\u003c/body\u003e\n```\n```\nvar viewModel = {\n\n    myDatum: \"Hello world.\",\n    myBinding: new Binding({\n\n        value: function(value) {\n\n            if (value) {\n\n                this.myDatum = value;\n            }\n\n            return this.myDatum;\n        }\n    })\n};\n\nnew BindingRoot(viewModel);\n```\nA standard two way binding will have the above form, though of course you are free to experiment.\nThere is also a `Value` constructor to which you can pass the callback directly.\n\n### Click\n\nThe *click callback* is called whenever a click event is raised on an element to which it is bound.\nAgain the element is passed as the first parameter and there is also a `Click` constructor.\n\n```\n\u003cbody\u003e\n  \u003cbutton id=\"button-id\" data-bind=\"myBinding\"\u003e\u003c/button\u003e\n\u003c/body\u003e\n```\n```\nvar viewModel = {\n\n    myDatum: \"The text on the button\",\n    myBinding: new Binding({\n\n        text: function() { return this.myDatum; },\n        click: function(element) {\n\n            alert(\"You just clicked the button with id \" + element.id + \".\");\n        }\n    })\n};\n\nnew BindingRoot(viewModel);\n```\n### Visible\n\nThe *visible callback* can be used to hide and show elements.\nIf the callback returns false the element will have the style `display: none;` applied to it.\nIf the callback returns true the display style will be set to the value it had when the element was first bound.\n\n### The Object Binding\n\nThe view model will typically be a nested data structure with sub-objects bound to parts of the DOM.\nA sub-object bound to an element becomes the view model for the part of the DOM made up of that element and its sub-elements.\nJust like its parent view model it can have data properties, binding properties and its own sub-objects.\nObjects are bound to elements in the same way as bindings by putting a `data-bind` attribute on the element whose value is the name of object property.\n\n```\n\u003cbody\u003e\n  \u003cdiv data-bind=\"mySubobject\"\u003e\n    \u003cspan data-bind=\"text\"\u003e\u003c/span\u003e\n  \u003c/div\u003e\n\u003c/body\u003e\n```\n```\nvar viewModel = {\n\n    mySubobject: {\n\n        text: new Text(function() { return \"Hello world.\"; })\n    }\n};\n\nnew BindingRoot(viewModel);\n```\nBe careful to ensure that the structure of the view model is mirrored in the DOM hierarchy.\nAn object bound to an element must contain all bindings within that element.\nNo binding can be applied from outside the bound object even if the property name is the same as that binding attribute.\n\nThe object binding has one more useful feature.\nIf the bound object is set to null then the child elements of the element to which it is bound will be removed from the DOM.\nIf the object is replaced then the elements are put back too.\nThus parts of the DOM can be shown only when there is data available to populate them.\nThis is a more natural way to hide and show elements than the visible binding.\n\n## Advanced Topics\n\n### DOM Mutation\n\nA unique feature of Datum.js is its ability to cope with updates to both the view model and the DOM.\nAs expected the DOM will be automatically updated to reflect changes in the view model.\nBut if you manually update the DOM say by attaching a new template, the view model will simply bind to the new template populating it with the same data, but potentially with a new layout.\n\nThe best practice when using Datum.js is to take full advantage of this capability by incrementally binding templates to the view model only when they are needed on the page.\nSo although Datum.js only allows one view model and one template on a page it is perfectly possible to asynchronously load components onto the page at any point during the running of the application.\n\n### The Binding Callback\n\nOften the most convenient time to load a new template is just after binding its view model to the DOM using the object binding.\nThis way the template can be placed straight inside the element to which the view model was just bound.\nTo facilitate this when applying the object binding Datum.js will look for and call a method called `onBind` if it exists on the object being bound.\nThe element to which the object was just bound is passed as the first parameter to `onBind` so a new template can be easily placed inside the element.\n\n    var viewModel = {\n\n        onBind: function(element) {\n\n            $(element).load(\"myTemplate.html\");\n        }\n    };\n\n    new BindingRoot(viewModel);\n\n### Dependency tracking\n\nAll of the binding callbacks that can be passed to the `Binding` constructor track their own dependencies.\nThis means that you don't have to worry about updating any of the data on the page when the data in the view model changes.\nThis will happen automatically.\n\n### Serialisation\n\nIt is common to want to be able to turn objects into JSON to send to the server.\nFor this reason Datum.js attaches a `toJSON` method to each object in the view model.\nThis method returns an object containing only data properties that can be easily stringified and sent to the server.\n\n    var viewModel = {\n    \n        datum: \"Hello world.\"\n    };\n    \n    new BindingRoot(viewModel);\n\n    var jsonString = JSON.stringify(viewModel);\n\nIf an object contains data that should not be serialised then it can implement its own `toJSON` method.\nThis method should return an object that contains just its serialisable data.\nBeginners are encouraged to use the default implementation of `toJSON` and not to worry if a few redundant properties are sent to the server.\n\n### The Array Binding\n\nThe array binding is quite like the object binding except the contents of the element to which an array is bound will be repeated for each element of the bound array.\n\n```\n\u003cbody\u003e\n  \u003cdiv data-bind=\"array\"\u003e\n    \u003cdiv\u003e\n      \u003cdiv data-bind=\"text\u003e\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/body\u003e\n```\n```\nvar viewModel = {\n\n    array: [\n\n        { text: new Text(function() { return \"this\"; }) },\n        { text: new Text(function() { return \"that\"; }) },\n        { text: new Text(function() { return \"tother\"; }) }\n    ]\n};\n\nnew BindingRoot(viewModel);\n```\nThe above template and view model would produce the following HTML when bound.\n\n```\n\u003cbody\u003e\n  \u003cdiv data-bind=\"array\"\u003e\n    \u003cdiv\u003e\n      \u003cdiv data-bind=\"text\u003ethis\u003c/div\u003e\n    \u003c/div\u003e\n    \u003cdiv\u003e\n      \u003cdiv data-bind=\"text\u003ethat\u003c/div\u003e\n    \u003c/div\u003e\n    \u003cdiv\u003e\n      \u003cdiv data-bind=\"text\u003etother\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/body\u003e\n```\nNote that the children of the element to which the array is bound are the elements to which each of the objects in the array are bound.\n\n### Classes\n\nThe *classes binding* allows you to add and remove CSS classes from elements.\nIt requires an object containing callbacks named for the classes to be added or removed.\n\n    var viewModel = {\n\n        myBinding: new Binding({\n        \n            classes: {\n            \n                enabled: function() { return false; },\n                \"first-item\": function() { return true; }\n            }\n        })\n    };\n    \n    new BindingRoot(viewModel);\n\n### Events\n\nThe *events binding* registers event handlers on an element.\n\n    var viewModel = {\n\n        myBinding: new Binding({\n        \n            events: {\n            \n                change: function() { alert(\"Value changed!\"); },\n                keyup: function() { alert(\"Key was pressed!\"); }\n            }\n        })\n    };\n    \n    new BindingRoot(viewModel);\n    \nThe event object is passed as the callback's first parameter.\n\n### The Init, Update and Destroy bindings\n\nIn some cases the provided bindings will not cover all of the functionality of an application.\nWhen this is so the *init*, *update* and *destroy* bindings can be used to create custom bindings.\n\nThe *init callback* is called when the binding is bound to an element.\nAfterwards the init callback will not be called again for that element, but will be called if the binding is bound to a new element, each time passing the bound element as the first parameter.\nThe *update callback* is called when the binding is first attached to an element and then again whenever one of its dependencies changes.\nThe *destroy callback* is called when the binding is removed from an element.\n\n### Testing\n\nIn order to simulate user interaction Datum.js provides test handles that can be called programmatically.\nTo retrieve a test handle simply call a binding as a function.\n\n    var binding = new Click(function() { alert(\"clicked!\"); });\n\n    var testHandle = binding();\n\nThe test handle gives you access to the callbacks you used to construct the binding.\n\n    testHandle.click();\n\nA typical test might look like the following.\n\n    var viewModel = {\n\n        label: \"click me\",\n        button: new Binding({\n\n            text: function() { return this.label; },\n            click: function() { this.label = \"clicked!\"; }\n        })\n    };\n\n    var testHandle = viewModel.button();\n\n    testHandle.click();\n\n    var buttonLabel = testHandle.text();\n\n    assertEqual(buttonLabel, \"clicked!\");\n\nSince using Datum.js allows you to completely separate layout and logic, the entire functionality of an application can be tested in this way without ever binding the view model to a template.\n\n## Installation\n\n### Install with NPM\n\n    npm install Datum\n\nor\n\n    npm install @datumjs/datum\n\n### Install with Maven\n\nPlace the following dependency in your `pom.xml` file.\n\n    \u003cdependency\u003e\n      \u003cgroupId\u003eorg.webjars\u003c/groupId\u003e\n      \u003cartifactId\u003eDatum\u003c/artifactId\u003e\n      \u003cversion\u003e0.12.3\u003c/version\u003e\n    \u003c/dependency\u003e\n\nFor other Java build tools check out the Maven [repository](https://mvnrepository.com/artifact/org.webjars/Datum).\n\n## Bugs\n\nBugs can be reported on [GitHub](https://github.com/MartinRixham/Datum/issues), though at this point Datum is very stable and most work continues in library repositories such as [Pieces](https://github.com/martinrixham/pieces).\n\n## Examples\n\nThe following websites were created with Datum:\n\n* [piecesofdata.com](https://piecesofdata.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartinrixham%2Fdatum","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmartinrixham%2Fdatum","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartinrixham%2Fdatum/lists"}