{"id":21891398,"url":"https://github.com/pitpik/vom","last_synced_at":"2025-04-15T14:11:51.349Z","repository":{"id":141376253,"uuid":"80444426","full_name":"PitPik/VOM","owner":"PitPik","description":"Virtual Object Model (starting point for circularJS)","archived":false,"fork":false,"pushed_at":"2018-09-23T07:59:36.000Z","size":129,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T21:01:57.552Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://dematte.at/VOM","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/PitPik.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-01-30T17:23:28.000Z","updated_at":"2023-07-31T04:47:30.000Z","dependencies_parsed_at":"2024-05-17T04:30:49.988Z","dependency_job_id":null,"html_url":"https://github.com/PitPik/VOM","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/PitPik%2FVOM","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PitPik%2FVOM/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PitPik%2FVOM/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PitPik%2FVOM/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PitPik","download_url":"https://codeload.github.com/PitPik/VOM/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249085427,"owners_count":21210267,"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-11-28T12:35:15.574Z","updated_at":"2025-04-15T14:11:51.341Z","avatar_url":"https://github.com/PitPik.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# VOM (Virtual Object Model)\n\nVOM is a tiny model controller (4.7KB). It can be used to either abstract element structure on a DOM page such as containers, widgets, form items, element groups, etc. to build complex apps or to just simply create a flat model and keep track of changes in this model and reflect them for example in a view.\n\nThe strong point of VOM is that it automatically registers changes in its model and can react on that. It is aware of depth of the model, just like in a DOM structure (so it has children with an index and parents) and therefore can be used for complex structures such as menu trees, categorical structures or for example CMS items on a page. VOM provides an API similar to the DOM-API (appendChild, replaceChild, insertBefore, ...) so it is quite easy to understand and to learn.\n\nVOM can be used as a starting point for a MVC, MVVM or MV* library. It makes it easy to separate view from model from UI. VOM is also super fast as it is very simple and small.\n\nVOM doesn't provide a rendering engine but makes it easy to delegate to such. See the [demo](http://dematte.at/VOM) to understand how VOM can help you to coordinate a model with a view and a UI and how persisting values can be realized.\n\n\n## Usage\n\n```javascript\n\u003cscript type=\"text/javascript\" src=\"VOM.js\"\u003e\u003c/script\u003e\n\u003cscript type=\"text/javascript\"\u003e\n    var vom = new VOM([model], [options]);\n\u003c/script\u003e\n```\nA model may be passed (Array) but can also be left out (or null) as elements can be added after initialisation.\nThe options are also optional as all possible options have a default value.\n\n## Model\n\n```javascript\n[{\n    foo: 'bar',\n    id: 'id-01'\n}, ...]\n```\nThe model is an Array of Objects (can be seen as the children of a DOM's document.body) where each element could have ```childNodes``` (optional name) defined. The childNodes would also be an array of further Objects;\nAfter initialisation of the model, it is then enriched with ```parentNode``` which points to its parent, ```id``` (or whatever ```options.idProperty``` is defining) that gives the element a unique id and ```index``` which is readable only and points to the index of the parent's childNodes (its own position compared to its siblings). In a real DOM there would not be an ```index```, only previousSibling, nextSibling... but I think it is clear what it does.\n\n```javascript\n[{\n    foo: 'bar',\n    id: 'id-01', // read only if not yet in model\n    index: (...), // dynamic, read only\n    parentNode: (...) // dynamic\n}, ...]\n```\n```parentNode``` automatically has a property ```childNodes``` (defined by ```options.childNodes```) as it could otherwhise not hold this element.\n\nA more complex model could look like this:\n```javascript\n[{\n    foo: 'bar',\n    childNodes: [{\n        foo: 'no bar'\n    }]\n}, ...]\n```\n...and this is actually the main reason for this component. You can control complex and deep JSON structures and use them just like a DOM tree but then in a more abstract and simpler way. The following model might be for a menu tree:\n```javascript\n[{\n    text: 'Root item 0',\n    someAttribute: 'foo',\n    isOpen: true,\n    childNodes: [{\n        text: 'Item 0-0',\n    }, {\n        text: 'Item 0-1',\n        isOpen: false,\n        childNodes: [{\n            text: 'Item 0-1-0',\n        }, {\n            text: 'Item 0-1-1',\n            isOpen: false,\n            childNodes: [{\n                text: 'Item 0-1-1-0',\n            }, {\n                text: 'Item 0-1-1-1',\n            }]\n        }, {\n            text: 'Item 0-1-2',\n        }]\n    }, {\n        text: 'Item 0-2',\n        isOpen: false,\n        childNodes: [{\n            text: 'Item 0-2-0',\n        }]\n    }, {\n        text: 'Item 0-3',\n    }]\n}, {\n    text: 'Root item 1',\n    isOpen: false,\n    childNodes: [{\n        id: '1-0',\n        text: 'Item 1-0',\n    }, ...]\n}, ...];\n```\nCombined with ```options.listeners['text', 'isOpen']``` This will end up in a model like:\n```javascript\n[{\n    text: (...), // 'Root item 0',\n    id: 0,\n    index: (...), // 0\n    parentNode: (...), // Object\n    someAttribute: 'foo',\n    isOpen: (...), // true,\n    childNodes: [{\n        text: (...), // 'Item 0-0',\n        id: 1,\n        index: (...), // 0\n        parentNode: (...) // Object\n    }, {...\n```\n\n## Options\nOn initialisation you can add some options. All predefinitions are shown as followed.\n\n```javascript\nparentCheck: false,\n// parentCheck adds a check if methods like appendChild actually make sense as there could be a parent\n// be appended to its own child... and throws then an error.\n\nidProperty: 'id',\n// the key of how the id should be stored in the model ('uuid', ...)\n\nsubscribe: function(property, item, value, oldValue) {},\n// This is actually the key callback that makes VOM so valuable and convenient to be used.\n// All properties in the model that have been enhanced by being defined in options.listeners,\n// through options.enhanceAll and methods called to manipulate the order in the model (like\n// appendChild, replaceChild, ...) if changed, will trigger this callback to be called.\n// removeChild() also triggers the subscribe.\n// 'property' will deliver the property that was changed and defined in options.listeners or the\n// method called to manipulate the model. In case options.enhanceAll is set to true,\n// all properties in the model being changed in the model would trigger this function and\n// deliver its name in property.\n// item is the model part being modified, so value could also be taken from item[property].\n// oldValue is the value of item[property] before it was manipulated. So, in case there was an invalide\n// value set, you can react on it with either setting it back to old value or returning true, which also\n// sets the value back and console.logs a message.\n\nlisteners: [],\n// as described above, this is an Array of Strings that hold the keys of the model that should trigger\n// subscribe() when its value was changed\n// Wildcards '*' can be used in root or in more complex structures like foo.bar.* or foo.*.value,\n// where foo could also be an array. So, ```{ foo: [{value: 1}, {value : 2 }] }```\n\nchildNodes: 'childNodes',\n// Defines the key name given for child elements\n\nenrichModelCallback: function(item) {},\n// right after an element was enhanced this callback is called and provides its data (item / model)\n// for inspection or manipulation, etc. It might be a good idea to set a reference to a real\n// DOM element for future convenience like with getElementsByProperty('element', element); if\n// any rendering is involved...\n// NOTE: items don't get enriched if there is a propery parentNode present\n\nmoveCallback:\n// like subscribe() but for all actions like 'appendChild', 'insertBefore', ...\n\npreRecursionCallback: function(item) {},\n// same as above but it will be called before the childNodes are processed.\n\nthrowErrors: false\n// there are some checks in VOM that might call console.warn if something went wrong.\n// If throwErrors is set to true, there would be an error thrown instead of just a console.warn\n```\n\n## API\n\n```javascript\ngetElementById(id); // id: String\n// returns an element defined by its id if found in model\n\ngetElementsByProperty([property], [value]); // property: String, value: Any\n// returns an Array of elements that match with the value of its property.\n// value could be a string but also a DOM Element or anything else.\n// property string can also point to a deeper element: foo.bar.value\n// if value and property is undefined, the method will return all items\n// if value === undefined, the method returns all items that have a property defined by property\n\ninsertBefore(item, sibling); // item, sibling: model element (Object)\n// inserts an existing or new item to the model just before the defined sibling.\n// New items will be enhanced automatically\n// returns the (new) item\n\ninsertAfter(item, sibling); // item, sibling: model element (Object)\n// inserts an existing or new item to the model just after the defined sibling.\n// New items will be enhanced automatically\n// returns the (new) item\n\nappendChild(item, [parent]); // item, parent: model element (Object)\n// inserts an existing or new item to the model at the end of parent's children Array.\n// childNodes property will be created automatically to parent if it doesn't exist.\n// New items will be enhanced automatically\n// If there is no parent specified, element will be appended to root\n// returns the (new) item\n\nprependChild(item, [parent]); // item, parent: model element (Object)\n// inserts an existing or new item to the model at the beginning of parent's children Array.\n// childNodes property will be created automatically to parent if it doesn't exist.\n// New items will be enhanced automatically\n// If there is no parent specified, element will be prepended to root\n// returns the (new) item\n\nreplaceChild(newItem, item); // newItem, item: model element (Object)\n// replaces and existing item in the model with another existing or new item.\n// New items will be enhanced automatically\n// returns the (new) item\n\nremoveChild(item); // item: model element (Object)\n// Removes an existing item from the model.\n// Does NOT automatically remove the childNodes preoperty from its parent element\n// returns the item\n\nreinforceProperty(model, item, value, [enumarable]) //\n// sets a property as enumerable: false or as set, configurable: false, writable: false.\n// convenient for storing items that don't belong to the model. JSON.strigify\n// can then better deal with the model...\n\naddProperty(property, item, path, readonly)\n// Adds a property to a model item. Whole path is needed to determine the property (foo.bar)\n// if it is not in the root of the item.\n\ngetProperty()\nsortChildren(callback, model, children)\ngetCleanModel()\n\ndestroy()\n// Removes all items from the model and cleans up internal models for garbage collection.\n```\n\n'Enhancement' means that all properties that are defined in ```options.listeners``` will be handled in subscribe. It also means that the ```id``` will be given automatically if not defined in the model and also be set to readonly, ```parentElement``` will be set automatically and be handled in ```subscribe``` and finally ```index``` will be added to the model to determine the position of the element compared to its siblings.\n\n```childNodes``` doesn't have to be set initially if there are no child nodes, but it will be set automatically if methods like ```appendChild()``` or ```prependChild()``` were called and there were no previous child nodes present in its parent.\n\nAll methods are scoped with the instance: ```this``` === instance_of_VOM\n\n## Little example\n```javascript\nvar model = [{\n    foo: 'bar'\n}];\n\nvar vom = new VOM(model, {\n    listeners: ['foo'],\n    subscribe: function(property, object, value, oldValue) {\n        if (property === 'foo') {\n            console.log(\"'foo' was changed from '\" + oldValue + \"' to '\" + value + \"'\");\n        }\n    }\n});\n\nvom.model[0].foo = 'new bar' // console: 'foo' was changed from 'bar' to 'new bar'\n```\n\n## Missing properties\n\nThere are some properties like ```previousSibling```, etc... missing that exist in a real DOM environment. Those are actually not necessary as it is quite easy to find elements in the model:\n\n### previousSibling\n```javascript\nvar previousSibling = item.parentNode.childNodes[item.index - 1];\n```\n\n### nextSibling\n```javascript\nvar nextSibling = item.parentNode.childNodes[item.index + 1];\n```\n\n### firstChild\n```javascript\nvar firstChild = item.childNodes[0];\n```\n\n### lastChild\n```javascript\nvar lastChild = item.childNodes[item.childNodes.length - 1];\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpitpik%2Fvom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpitpik%2Fvom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpitpik%2Fvom/lists"}