{"id":18745640,"url":"https://github.com/jsstuff/xmo","last_synced_at":"2025-07-24T03:05:23.696Z","repository":{"id":16865739,"uuid":"19625987","full_name":"jsstuff/xmo","owner":"jsstuff","description":"Extensible Meta Objects (xmo.js) is a lightweight class library","archived":false,"fork":false,"pushed_at":"2018-10-20T19:10:12.000Z","size":27,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-11T04:43:15.431Z","etag":null,"topics":["class","inheritance","javascript","mixins","object"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jsstuff.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-05-09T21:18:36.000Z","updated_at":"2022-12-05T13:52:59.000Z","dependencies_parsed_at":"2022-07-16T18:30:31.153Z","dependency_job_id":null,"html_url":"https://github.com/jsstuff/xmo","commit_stats":null,"previous_names":["jshq/qclass","exjs/exclass","exjs/xmo"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jsstuff/xmo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsstuff%2Fxmo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsstuff%2Fxmo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsstuff%2Fxmo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsstuff%2Fxmo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jsstuff","download_url":"https://codeload.github.com/jsstuff/xmo/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsstuff%2Fxmo/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266786798,"owners_count":23983871,"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-24T02:00:09.469Z","response_time":99,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"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":["class","inheritance","javascript","mixins","object"],"created_at":"2024-11-07T16:18:58.437Z","updated_at":"2025-07-24T03:05:23.658Z","avatar_url":"https://github.com/jsstuff.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"xmo.js\n======\n\nExtensible Meta Objects (xmo.js) is a lightweight class library for creating JS classes and mixins.\n\n  * [Official Repository (jsstuff/xmo)](https://github.com/jsstuff/xmo)\n  * [Public Domain (https://unlicense.org)](https://unlicense.org)\n\nIntroduction\n------------\n\n`xmo.js` is a lightweight javascript library for creating JS classes and mixins. Unlike many other libraries that try to solve the same problem `xmo.js` was designed to be extensible and only provides the mechanism to create a new class or mixin with a possibility to register user-defined extensions and init handlers.\n\nThere are many UI toolkits and JS frameworks that provide some kind of class subsystem that adds properties, events, and other features to the traditional javascript class. This approach makes the class-subsystem dependent on the framework and makes it unusable outside of it.\n\nThe approach used by `xmo.js` is different. It allows to register init handlers and extensions to the class itself so any class that inherits it would provide the same features as the base class. This means that many existing class frameworks could be theoretically implemented on top of `xmo.js` itself.\n\nSo how to create a JavaScript Class? Just import `xmo` library (or include it if you work on client-side) and use `xmo` as a function to create your own class. The function accepts only one argument `def`, which is used to define members, static properties, init handlers, and extensions; it will return a new `Class` object that can be instantiated by a `new` keyword. A simple example is shown below:\n\n```js\n// Create a new class `Class` that doesn't inherit from any object. It\n// inherits internally from a pure JavaScript `Object`, which most JS objects\n// do. A `constructor` property defines the constructor and other properties\n// define class members (that will be added to the prototype object).\nconst Class = xmo({\n  constructor() {\n    this.text = \"Hi!\"\n  },\n\n  toString() {\n    return this.text;\n  }\n});\n```\n\nThe `constructor` property defines class constructor and other properties define class members. The `Class` can be instantiated simply by using `new` operator as demonstrated in the following example:\n\n```js\n// Create an instance of `Class` defined in previous example.\nconst instance = new Class();\n\n// `instance` is now an instance of `Class`, we can check if the inheritance\n// is working as expected by calling `toString` method, which should return\n// \"Hi!\". Another expected behavior is using `instanceof` keyword which should\n// return `true` if tested against `Class`.\nconsole.log(instance.toString());        // Outputs `Hi!`.\nconsole.log(instance instanceof Class);  // Outputs `true`.\nconsole.log(instance instanceof Object); // Outputs `true`.\n```\n\nInheritance\n-----------\n\nBase class can be specified by `$extend` property:\n\n```js\n// `Point` is a base class.\nconst Point = xmo({\n  constructor(x, y) {\n    this.x = x;\n    this.y = y;\n  },\n\n  translate(x, y) {\n    this.x += x;\n    this.y += y;\n  },\n\n  toString() {\n    return `Point { x: ${this.x}, y: ${this.y}}`;\n  }\n});\n\n// `Circle` extends `Point`.\nconst Circle = xmo({\n  $extend: Point,\n\n  constructor(x, y, radius) {\n    // Has to call superclass constructor.\n    Point.call(this, x, y);\n    this.radius = radius;\n  },\n\n  // Overrides `toString` of `Point`.\n  toString() {\n    return `Circle { x: ${this.x}, y: ${this.y}, radius: ${this.radius} }`\n  }\n});\n\n// Create instances of `Point` and `Circle` classes.\nconst point = new Point(1, 1);\nconst circle = new Circle(10, 10, 5);\n\nconsole.log(point.toString());         // Outputs `Point { x: 1, y: 1 }`.\nconsole.log(circle.toString());        // Outputs `Circle { x: 10, y: 10, radius: 5 }`.\n\n// `point` is an instance of `Point`, but not `Circle`.\nconsole.log(point instanceof Point);   // Outputs `true`.\nconsole.log(point instanceof Circle);  // Outputs `false`.\n\n// `circle` is an instance of both `Point` and `Circle`.\nconsole.log(circle instanceof Point);  // Outputs `true`.\nconsole.log(circle instanceof Circle); // Outputs `true`.\n```\n\nStatic Properties\n-----------------\n\nStatic (Class) members can be defined by `$statics` property.\n\n```js\nconst Class = xmo({\n  constructor() {\n    this.status = Class.Ready;\n  },\n\n  $statics: {\n    Ready: 0,\n    Running: 1\n  }\n});\n\nconsole.log(Class.Ready);     // Outputs `0`.\nconsole.log(Class.Running);   // Outputs `1`.\n\nconst instance = new Class();\n\nconsole.log(instance.status); // Outputs `0`.\nconsole.log(instance.Ready);  // Outputs `undefined` (not a member of `instance`).\n```\n\nExtensions\n----------\n\nMany JS frameworks, especially those designed for UI tookits, provide a fixed set of extensions to the object model they use. For example Qooxdoo supports mixins, interfaces, and allows to define properties and events. The purpose of `xmo.js` is not to provide all imaginable features, but to provide a foundation to add extensions to a particular class that will be then included by all classes that inherit it. This allows to extend the class-system at runtime and to support virtually anything user needs.\n\nThere are at the moment 2 concepts supported by `xmo.js`:\n\n  1. Init handlers defined by `$preInit` and `$postInit` properties.\n  2. Extensions defined by `$extensions` property.\n\nA new class is always created by the following steps:\n\n  1. Inherit init handlers and extensions.\n  2. Call `$preInit` handlers.\n  3. Add new members to the class (handles all defined `$extensions` as well)\n  4. Call `$postInit` handlers.\n\nThe following example uses `$extensions` property to define a `$property` extension:\n\n```js\nconst Point = xmo({\n  constructor(x, y) {\n    this.x = x;\n    this.y = y;\n  },\n\n  $extensions: {\n    // Define extension `$properties`.\n    //\n    // This function will be called every time when `$properties` is used in\n    // class definition that directly or indirectly inherits `Point`. It is\n    // also called if `Point` itself uses `$properties` extension.\n    //\n    // `this` - Class object     (`Point` in our case).\n    // `k`    - Property key     (\"$properties\" string).\n    // `v`    - Property value   (`$properties` content).\n    $properties(k, v) {\n      // Iterate over all keys in `$properties`.\n      Object.keys(v).forEach(function(name) {\n        const upper = name.charAt(0).toUpperCase() + name.substr(1);\n\n        // Create getter and setter for a given `name`.\n        this.prototype[`get${upper}`] = function() { return this[name]; };\n        this.prototype[`set${upper}`] = function(value) { this[name] = value; };\n      }, this /* binds `this` to the callback. */);\n    }\n  },\n\n  // In our case this will use the defined `$properties` extension.\n  $properties: {\n    x: true,\n    y: true\n  }\n});\n\n// Create an instance of `Point` and call the generated functions.\nconst point = new Point(1, 2);\n\nconsole.log(point.getX()); // Outputs `1`.\nconsole.log(point.getY()); // Outputs `2`.\n\npoint.setX(10);\npoint.setY(20);\n\nconsole.log(point.getX()); // Outputs `10`.\nconsole.log(point.getY()); // Outputs `20`.\n```\n\nPreInit \u0026 PostInit Handlers\n---------------------------\n\nExtensions define a function that is called if a given `key` is present in class definition. Since this concept is fine and generally useful, sometimes it's handy to be able to call a handler before and/or after the class setup regardless of which properties are present in class definitions `def` object. Both `$preInit` and `$postInit` handlers are supported and can be used to add a single or multiple handlers at once.\n\nThe following example does the same thing as `$properties` extension, but uses using `$postInit` handler instead:\n\n```js\nconst Point = xmo({\n  constructor(x, y) {\n    this.x = x;\n    this.y = y;\n  },\n\n  // Add a preInit handler.\n  $preInit(def) {\n    // Does nothing here, just to document the syntax.\n  },\n\n  // Add a postInit handler, called once on Point and all classes that inherit it.\n  //\n  // `this` - Class object (`Point` in our case).\n  // `def`  - The whole `def` object passed to `xmo(...)`.\n  $postInit(def) {\n    if (!def.$properties)\n      return;\n\n    // Iterate over all keys in `$properties`.\n    Object.keys(def.$properties).forEach(function(name) {\n      const upper = name.charAt(0).toUpperCase() + name.substr(1);\n\n      // Create getter and setter for a given `key`.\n      this.prototype[`get${upper}`] = function() { return this[name]; };\n      this.prototype[`set${upper}`] = function(value) { this[name] = value; };\n    }, this /* binds `this` to the callback. */);\n  },\n\n  // This is not necessary. Null extensions are only used to make\n  // a certain property ignored (won't be copied to the prototype).\n  $extensions: {\n    $properties: null\n  },\n\n  // Will be used by the hook defined above.\n  $properties: {\n    x: true,\n    y: true\n  }\n});\n\n// Create an instance of `Point` and use functions created by the\n// `property` extension.\nconst point = new Point(1, 2);\n\nconsole.log(point.getX()); // Outputs `1`.\nconsole.log(point.getY()); // Outputs `2`.\n\npoint.setX(10);\npoint.setY(20);\n\nconsole.log(point.getX()); // Outputs `10`.\nconsole.log(point.getY()); // Outputs `20`.\n```\n\nInit handlers are very similar to extensions, however, they don't need any properties to be defined and are always called once per class. Init handlers are in general more powerful, because they can use any property or multiple properties to do add stuff to the class itself.\n\nMixins\n------\n\nA mixin is set of functions that can be included in another class or mixin. Mixins are defined by using `xmo.mixin(def)`, where `def` is similar definition compatible to `xmo.js` itself, but without `constructor` support (mixins can't be instantiated). Mixins also understand `$extensions` and `$preinit/$postInit` handlers, so it's possible to define these in mixin that is then included in other classes.\n\n```js\n// Create a mixin that provides `translate(x, y)` function.\nconst MTranslate = xmo.mixin({\n  translate(x, y) {\n    this.x += x;\n    this.y += y;\n    return this;\n  }\n});\n\n// Create a Point class that includes MTranslate mixin.\nconst Point = xmo({\n  $mixins: [MTranslate],\n\n  constructor(x, y) {\n    this.x = x;\n    this.y = y;\n  },\n\n  toString() {\n    return `[${this.x}, ${this.y}]`;\n  }\n});\n\n// Create a Rect class that includes MTranslate mixin.\nconst Rect = xmo({\n  $mixins: [MTranslate],\n\n  constructor(x, y, w, h) {\n    this.x = x;\n    this.y = y;\n    this.w = w;\n    this.h = h;\n  },\n\n  toString() {\n    return `[${this.x}, ${this.y}, ${this.w}, ${this.h}]`;\n  }\n});\n\n// The translate() functions are provided to both classes.\nconst p = new Point(0, 0);\nconst r = new Rect(0, 0, 33, 67);\n\np.translate(1, 2);\nr.translate(1, 2);\n\nconsole.log(p.toString()); // Outputs `[1, 2]`.\nconsole.log(r.toString()); // Outputs `[1, 2, 33, 67]`.\n```\n\nCombining more mixins to a single mixin:\n\n```js\n// Create two mixins MTranslate and MScale.\nconst MTranslate = xmo.mixin({\n  translate(x, y) {\n    this.x += x;\n    this.y += y;\n    return this;\n  }\n});\n\nconst MScale = xmo.mixin({\n  scale(x, y) {\n    if (y == null)\n      y = x;\n\n    this.x *= x;\n    this.y *= y;\n    return this;\n  }\n});\n\n// If a combined mixin is needed, it can be created simply by\n// including MTranslate and MScale into another mixin.\nconst MCombined = xmo.mixin({\n  $mixins: [MTranslate, MScale]\n});\n```\n\nMeta Information\n----------------\n\nEach class created by `xmo.js` contains a non-enumerable property called MetaInfo. It contains essential information about the class itself (and inheritance) and can be used to store additional information required by extensions.\n\nLet's demonstrate the basics of MetaInfo:\n\n```js\nconst Class = xmo({\n  $preInit() {\n    console.log(\"PreInit()\");\n  },\n\n  $postInit() {\n    console.log(\"PostInit()\");\n  },\n\n  $extensions: {\n    $ignoredField: null,\n\n    $customField(k, v) {\n      console.log(`CustomField(): '${k}' with data ${JSON.stringify(v)}`);\n    }\n  },\n\n  $customField: {\n    test: []\n  },\n\n  $statics: {\n    SomeConst: 0\n  }\n});\n\n// Firstly, try to instantiate the class:\n//   PreInit()\n//   CustomField(): '$customField' with data { test: [] }\n//   PostInit()\nconst instance = new Class();\n\n// Access MetaInfo of the class.\n//   (Alternatively `instance.constructor.$metaInfo`)\nconst MetaInfo = Class.$metaInfo;\n\n// Boolean value indicating a mixin:\n//   false\nconsole.log(MetaInfo.isMixin);\n\n// Super class:\n//   null (would link to super class if the class was inherited)\nconsole.log(MetaInfo.super);\n\n// Map of ignored properties:\n//   { $ignoredField: true, $customField: true }\nconsole.log(MetaInfo.ignored);\n\n// Map of all static properties:\n//   { SomeConst: true }\nconsole.log(MetaInfo.statics);\n\n// PreInit handlers:\n//   [function(...)]\nconsole.log(MetaInfo.preInit);\n\n// PostInit handlers:\n//   [function(...)]\nconsole.log(MetaInfo.postInit);\n\n// Extensions:\n//   { $customField: function(...) }\nconsole.log(MetaInfo.extensions);\n\n//\n```\n\nIt's important to mention that all members of MetaClass are frozen (immutable) and cannot be modified after the class was created (after all postInit handlers were called). MetaInfo can only be changed during class creation by init handlers and class extensions. Use `getMutable()` member function to make a property of MetaInfo temporarily mutable.\n\nThe following example shows how to add a custom reflection information to the MetaInfo:\n\n```js\n// Creates some base class that defines a property system.\nconst Base = xmo({\n  $preInit() {\n    const meta = this.$metaInfo;\n\n    // Add `properties` property to MetaInfo object.\n    if (!meta.properties)\n      meta.properties = Object.create(null);\n  },\n\n  $extensions: {\n    $properties(k, v) {\n      const defProperties = v;\n      const metaProperties = this.$metaInfo.getMutable(\"properties\");\n\n      // Simplest way, production code should deal with redefinitions, etc...\n      Object.assign(metaProperties, defProperties);\n    }\n  }\n});\n\n// Create two classes that add uses the extension we just created.\nconst Point2D = xmo({\n  $extend: Base,\n  $properties: {\n    x: { type: \"number\" },\n    y: { type: \"number\" }\n  }\n});\n\nconst Point3D = xmo({\n  $extend: Point2D,\n  $properties: {\n    z: { type: \"number\" }\n  }\n});\n\n// Access the MetaInfo of Point2D:\n//   { x: { type: \"number\" }\n//     y: { type: \"number\" } }\nconsole.log(JSON.stringify(Point2D.$metaInfo.properties));\n\n// Access the MetaInfo of Point3D:\n//   { x: { type: \"number\" },\n//     y: { type: \"number\" },\n//     z: { type: \"number\" } }\nconsole.log(JSON.stringify(Point3D.$metaInfo.properties));\n```\n\nNOTE: Alternatively a Mixin can be used instead of Base class.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsstuff%2Fxmo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjsstuff%2Fxmo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsstuff%2Fxmo/lists"}