{"id":22423374,"url":"https://github.com/prantlf/backbone.composite-model","last_synced_at":"2025-03-27T05:40:55.928Z","repository":{"id":36419175,"uuid":"40724152","full_name":"prantlf/backbone.composite-model","owner":"prantlf","description":"Supports composite Backbone.Model objects","archived":false,"fork":false,"pushed_at":"2022-01-30T15:36:23.000Z","size":1278,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-01T11:07:59.500Z","etag":null,"topics":["backbone","nested-models"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/prantlf.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-08-14T16:12:15.000Z","updated_at":"2022-01-16T12:40:45.000Z","dependencies_parsed_at":"2022-09-03T10:50:34.348Z","dependency_job_id":null,"html_url":"https://github.com/prantlf/backbone.composite-model","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prantlf%2Fbackbone.composite-model","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prantlf%2Fbackbone.composite-model/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prantlf%2Fbackbone.composite-model/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prantlf%2Fbackbone.composite-model/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/prantlf","download_url":"https://codeload.github.com/prantlf/backbone.composite-model/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245791899,"owners_count":20672668,"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":["backbone","nested-models"],"created_at":"2024-12-05T18:10:50.113Z","updated_at":"2025-03-27T05:40:55.908Z","avatar_url":"https://github.com/prantlf.png","language":"JavaScript","readme":"# backbone.composite-model\n\n[![Latest version](https://img.shields.io/npm/v/backbone.composite-model)](https://www.npmjs.com/package/backbone.composite-model)\n![Bower version](https://img.shields.io/bower/v/backbone.composite-model.svg)\n[![Dependency status](https://img.shields.io/librariesio/release/npm/backbone.composite-model)](https://www.npmjs.com/package/backbone.composite-model)\n[![Test code coverage](https://codecov.io/gh/prantlf/backbone.composite-model/branch/master/graph/badge.svg?token=8itqsA2HVj)](https://codecov.io/gh/prantlf/backbone.composite-model)\n[![Code Climate](https://codeclimate.com/github/prantlf/backbone.composite-model/badges/gpa.svg)](https://codeclimate.com/github/prantlf/backbone.composite-model)\n\nSupports composite [`Backbone.Model`] objects which represent a \"master\"\nmodel containing \"slave\" models or collections maintained automatically\naccording to the composite model configuration\n\n* [Motivation Example](#motivation-example)\n  + [Flat Model](#flat-model)\n  + [Manual Composite Model](#manual-composite-model)\n  + [Reusable Composite Model](#reusable-composite-model)\n* [Synopsis](#synopsis)\n  + [Composite Configuration](#composite-configuration)\n  + [Configuration Examples](#configuration-examples)\n* [What Is It For?](#what-is-it-for)\n* [What Is It Not For?](#what-is-it-not-for)\n* [Installation](#installation)\n* [Build](#build)\n* [Contributing](#contributing)\n* [Release History](#release-history)\n* [License](#license)\n\n## Motivation Example\n\nLet's have a versioned file-system model: folders containing files, files\nconsisting of versions:\n\n![File-System Model](https://raw.githubusercontent.com/prantlf/backbone.composite-model/master/docs/file-system.png)\n\nA JSON object representing a file would look like this:\n\n```text\n{\n  \"id\": ...,        // unique ID of the file\n  \"name\": '...',    // display name of the file\n  \"parent\": {...},  // object describing the parent folder\n  \"versions\": [...] // array of file version description objects\n}\n```\n\n### Flat Model\n\nModelling it with a flat [`Backbone.Model`] would look like this:\n\n```javascript\nvar FileModel = Backbone.Model.extend({\n      urlRoot: '/files'\n    });\n```\n\n[Backbone] events for attribute changes in models and model additions /\nremovals in collections work only for \"flat\" models; only for first-level\nattributes of the main model:\n\n```javascript\n// Declare a model representing a file information\nvar file = new FileModel({id: 1});\n// Inform whenever the file information has been fetched and is ready\nfile.on('sync', function (file) {\n  console.log('File information ready for', file.get('name'));\n});\n// Inform whenever the parent folder of the current file has changed\n// THIS DOES NOT WORK: watching 'parent.id'\nfile.on('change:parent.id', function (parent) {\n  console.log('Location changed to', parent.get('name'));\n});\n// Fetch information about the initial file - only the first event above\n// will be triggered; the second will be never triggered\nfile.fetch();\n```\n\nWe would not be able to pass the parent or version information alone to some\nview and let it refreshed as another file would be fetched, for example.\n\n### Manual Composite Model\n\nIf the `parent` and `versions` child objects should be exposed like real\n[Backbone] objects, to be able to work with their events in controllers\nand views, associated objects can be created and maintained whenever the\n\"master\" model changes, for example:\n\n```javascript\nvar FolderModel = Backbone.Model.extend({...}),\n    VersionModel = Backbone.Model.extend({...}),\n    VersionCollection = Backbone.Collection.extend({\n      model: VersionModel,\n      ...\n    }),\n    FileModel = Backbone.Model.extend({\n      initialize: function (attributes, options) {\n        // Initialize the child models and collections\n        attributes || (attributes = {});\n        this.parent = new FolderModel(attributes.parent, options);\n        this.versions = new VersionCollection(attributes.versions, options);\n        // Whenever the \"master\" model is re-fetched, update the child ones\n        this.on('sync', function (model, response, options) {\n          this.parent.set(this.attributes.parent, options);\n          this.versions.reset(this.attributes.versions, options);\n        }, this);\n      },\n      urlRoot: '/files'\n    });\n```\n\nAccessing the child models or collections is possible using the\n[Backbone] interface, including the change events:\n\n```javascript\n// Declare a model representing a file information\nvar file = new FileModel({id: 1});\n// Inform whenever the file information has been fetched and is ready\nfile.on('sync', function (file) {\n  console.log('File information ready for', file.get('name'));\n});\n// Inform whenever the parent folder of the current file has changed\n// THIS WORKS NOW: watching 'id' of the child model `file.parent`\nfile.parent.on('change:id', function (parent) {\n  console.log('Location changed to', parent.get('name'));\n});\n// Fetch information about the initial file - both events above will be\n// triggered\nfile.fetch();\n// Fetch information about another file - the first event will be always\n// triggered, the second one will be triggered only if the new file has\n// a different parent folder than the previous one\nfile.set('id', 2)\n    .fetch();\n```\n\n### Reusable Composite Model\n\nModelling the same scenario with the help of the `Backbone.CompositeModel`\nwould look like this:\n\n```javascript\nvar FileModel = Backbone.Model.extend({\n      // Declare what attributes map to what child models or collections\n      composite: {\n        parent: FolderModel,\n        versions: VersionCollection\n      },\n      initialize: function (attributes, options) {\n        // Initialize the child models and collections\n        this.makeComposite(options);\n      },\n      urlRoot: '/files'\n    });\n// Extend the prototype with methods managing the child models and collections\nBackbone.mixinCompositeModel(FileModel.prototype);\n```\n\nThe `FileModel` above will have the child objects `parent` and `versions`\nmaintained automatically, whenever they change in the \"master\" model.\n\n## Synopsis\n\nThe `Backbone.CompositeModel` offers a common implementation of the so-called\n\"master-slave\" or \"parent-child\" model/collection pattern, that propagates\nthe changes caused by `set`, `unset`, `clear`, `fetch` and `save` methods on\nthe \"master\" model to the \"slave\" models and/or collections, which are fully\nowned by the \"master\" model, including their creation.\n\nChild models are supposed to be created from object literals:\n\n![Slave Model](https://raw.githubusercontent.com/prantlf/backbone.composite-model/master/docs/slave-model.png)\n\nChild collections are supposed to be created from arrays:\n\n![Slave Collection](https://raw.githubusercontent.com/prantlf/backbone.composite-model/master/docs/slave-collection.png)\n\nInitialization of a *composite model* should include the following parts:\n\n1. Provide the `composite` configuration object as a property in the\n   prototype or in the new instance initialization `options`\n2. Call the `makeComposite` method from the `constructor` or from the\n   `initialize` method\n3. Extend the \"master\" model's prototype by calling the\n   `Backbone.mixinCompositeModel` method\n\n```javascript\nvar MasterModel = Backbone.Model.extend({\n      // Declare what attributes map to what child models or collections\n      composite: {\n        child: SlaveModel,\n      },\n      initialize: function (attributes, options) {\n        // Initialize the child models and collections\n        this.makeComposite(options);\n      },\n      ...\n    });\n// Extend the prototype with methods managing the child models and collections\nBackbone.mixinCompositeModel(MasterModel.prototype);\n```\n\n### Composite Configuration\n\nThe `composite` configuration object maps an attribute name to the \"slave\"\nmodel specification:\n\n```text\n'\u003cattribute name\u003e': \u003c\"slave\" model specification\u003e\n```\n\nThe *attribute name* has to exist in the `this.attributes` of the \"master\"\nmodel and will back up the \"slave\" model or collection.  The property on the\n\"master\" model will be created using the attribute name by default.\n\nThe *\"slave\" model specification* can be either a [`Backbone.Model`]\ndescendant for \"slave\" models or a [`Backbone.Collection`] descendant for\n\"slave\" collections.  It will be the function object to create the \"slave\"\nmodel or collection from:\n\n```text\n'\u003cattribute name\u003e': Backbone.Model | Backbone.Collection\n```\n\nThe default creation and maintenance of the \"slave\" models and collections\ncan be overridden by passing an object literal as the \"slave\" model\nspecification:\n\n```text\n'\u003cattribute name\u003e': {\n  type: Backbone.Model | Backbone.Collection,\n  ...\n}\n```\n\nThe following properties of the \"slave\" model specification object are\nsupported:\n\n* `type`: [`Backbone.Model`] descendant for \"slave\" models or a\n  [`Backbone.Collection`] descendant for \"slave\" collections (required)\n* `property`: Property name to store the \"slave\" model or collection on the\n  \"master\" model with (optional; the attribute name is the default)\n* `options`: Additional options to pass to the constructor of the \"slave\"\n  model or collection (optional; undefined by default)\n* `method`: Method to call on the \"slave\" model or collection if updated data\n  are being set (optional; `set` is the default for models, `add` for\n  collections)\n* `parse`: Function to call before the value is passed to child model or\n  collection constructor or to the `set` / `add` method to \"massage\" the\n  input data (optional; `undefined` is the default)\n\n### Configuration Examples\n\nMaintain a `parent` model based on the `this.attributes.parent` object\nand a `versions` collection based on the `this.attributes.versions` array\nfrom the composite model:\n\n```javascript\ncomposite: {\n  parent: FolderModel,\n  versions: VersionCollection\n}\n```\n\nName the property on the composite model \"parent\", although the backing up\nproperty is called \"parent_expanded\":\n\n```javascript\ncomposite: {\n  parent_expanded: {\n    type: FolderModel,\n    property: 'parent'\n  }\n}\n```\n\nEnsure that the \"slave\" model has always a property `child_id` with the value\nof the `id` property of the \"master\" model:\n\n```javascript\ncomposite: {\n  parent: {\n    type: FolderModel,\n    parse: function (attributes, options) {\n             var id = this.get('id');\n             if (id != null) {\n               attributes || (attributes = {});\n               attributes.child_id = id;\n             }\n             return attributes;\n           }\n  }\n}\n```\n\nUse the `reset` method to populate the \"slave\" collection instead of the\n`add` method, which is used by default:\n\n```javascript\ncomposite: {\n  versions: {\n    type: VersionCollection,\n    method: 'reset'\n  }\n}\n```\n\n## What Is It For?\n\n* Forward changes (main model attributes -\u003e child model or collection):\n  + Create a property on the main model with the child model or collection\n    automatically\n  + If the root attribute on the main model, which backs up the child model\n    or collection, changes by calling the `set` method, propagate the change\n    to the child model or collection\n* You need to fetch or constantly re-fetch the main model and have the\n  listeners (views) notified about changes in the child models or collections.\n\nYou can get the up-to-date content of all nested models and collections by\ncalling `toJSON` of the master model.\n\n## What Is It Not For?\n\n* Backward changes (child model or collection: -\u003e main model attributes):\n  + If an attribute of the child model or a model in the child collection\n    changes, propagate the change to the object under the root attribute,\n    which backs up the child model or collection\n\nInstead of updating `attributes` of the master model directly, you can\nconsider calling `set/unset/clear/add/remove/reset` methods of nested models\nand collections. You will be able to get the up-to-date content of all nested\nmodels and collections by calling `toJSON` of the master model.\n\n## Installation\n\nMake sure that you have [NodeJS] \u003e= 6 installed.  You can use either `npm`\nor `bower` to install this package and its dependencies.\n\nWith [NPM]:\n\n```shell\nnpm install backbone.composite-model\n```\n\nWith [Bower]:\n\n```shell\nbower install backbone.composite-model\n```\n\n## Build\n\nMake sure that you have [NodeJS] \u003e= 6 installed.  Clone the Github\nrepository to a local directory, enter it and install the package\ndependencies (including the development dependencies) by `npm`:\n\n```shell\ngit clone https://github.com/prantlf/backbone.composite-model.git\ncd backbone.composite-model\nnpm install\n```\n\nExamples and tests will be functional now.\n\n## Contributing\n\nIn lieu of a formal styleguide, take care to maintain the existing coding\nstyle.  Add unit tests for any new or changed functionality.\n\nBefore you start, make sure that you have satisfied native dependencies\nof the [node-canvas](https://github.com/Automattic/node-canvas) module,\nwhich are described for every operating system at the [documentation wiki\nof the project](https://github.com/Automattic/node-canvas/wiki/_pages).\n\n\nFirst fork this repository and clone your fork locally instead of cloning\nthe original.  See the \"Build\" chapter above for more details about how to\nclone it and install the build dependencies.\n\nBefore you commit, update minified files and source maps, re-generate\ndocumentation and check if tests succeed:\n\n```shell\nnpm run-script build\nnpm run-script doc\nnpm test\n```\n\nCommit your changes to a separtate branch, so that you can create a pull\nrequest for it:\n\n```shell\ngit checkout -b \u003cbranch name\u003e\ngit commit -a\ngit push origin \u003cbranch name\u003e\n```\n\n## License\n\nCopyright (c) 2015-2022 Ferdinand Prantl\n\nLicensed under the MIT license.\n\n[Backbone]: http://backbonejs.org/\n[`Backbone.Model`]: http://backbonejs.org/#Model\n[`Backbone.Collection`]: http://backbonejs.org/#Collection\n[Bower]: http://bower.io/\n[NodeJS]: http://nodejs.org/\n[NPM]: https://www.npmjs.com/\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprantlf%2Fbackbone.composite-model","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprantlf%2Fbackbone.composite-model","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprantlf%2Fbackbone.composite-model/lists"}