{"id":16893821,"url":"https://github.com/thierrymichel/kapla","last_synced_at":"2025-03-17T06:31:50.573Z","repository":{"id":57288305,"uuid":"120196612","full_name":"thierrymichel/kapla","owner":"thierrymichel","description":"Tiny JS framework to manage DOM components","archived":false,"fork":false,"pushed_at":"2022-04-20T09:20:35.000Z","size":949,"stargazers_count":60,"open_issues_count":6,"forks_count":2,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-11T10:17:54.019Z","etag":null,"topics":["component","dom","events","framework","javascript","modular","observer","simple","tiny"],"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/thierrymichel.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2018-02-04T15:23:14.000Z","updated_at":"2024-12-29T20:00:41.000Z","dependencies_parsed_at":"2022-09-20T03:42:23.929Z","dependency_job_id":null,"html_url":"https://github.com/thierrymichel/kapla","commit_stats":null,"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thierrymichel%2Fkapla","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thierrymichel%2Fkapla/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thierrymichel%2Fkapla/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thierrymichel%2Fkapla/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thierrymichel","download_url":"https://codeload.github.com/thierrymichel/kapla/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243847061,"owners_count":20357317,"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":["component","dom","events","framework","javascript","modular","observer","simple","tiny"],"created_at":"2024-10-13T17:16:31.972Z","updated_at":"2025-03-17T06:31:49.817Z","avatar_url":"https://github.com/thierrymichel.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# kapla 👷‍\n\n![stability-wip](https://img.shields.io/badge/stability-work_in_progress-lightgrey.svg?style=flat-square)\n[![NPM version](https://img.shields.io/npm/v/kapla.svg?style=flat-square)](https://www.npmjs.com/package/kapla)\n[![Coverage Status](https://img.shields.io/coveralls/thierrymichel/kapla/master.svg?style=flat-square)](https://travis-ci.com/thierrymichel/kapla)\n\n\u003e Tiny JS framework to manage DOM components\n\n## wip (this project is no longer maintained)\n\n### Overview\n\nThe main goal is to make easier to implement common tasks/features:\n\n- Component declaration and instanciation\n- Component init and destroy (Barba.js or load more…)\n- Access to `$el` et `$refs`\n- Use of `data-attribute`\n- Events handling with the right context (standard or custom)\n\nMain features :\n\n- Components `autoload` + declaration `data-component=\"foo\"`\n- Component lifecycle : `load`, `init`, `destroy`\n- Easy references:\n    - `data-component=\"foo\"` -\u003e `this.$el`\n    - `data-ref=\"foo.child\"` -\u003e `this.$refs.child`\n- Provide simple API to manage `dataset`:\n    - `data-foo-prop=\"value\"` -\u003e\n        - `this.data.has('prop')` // true\n        - `this.data.get('prop')` // value\n        - `this.data.set('prop', 'another value')` // another value\n- Events binding/unbinding with `onClick() {}` ou `onCustomEvent() {}`\n\n### Start application\n\n```html\n\u003cmain class=\"app\"\u003e\u003c/main\u003e\n```\n\n```js\nimport {\n  Application,\n  autoLoad,\n} from 'kapla';\n\nimport MyComponent from 'kapla-register/MyComponent';\n\nconst context = require.context('./kapla', true, /\\.js$/);\nconst app = Application.start(document.querySelector('.app')); // If no element -\u003e document.body\n\n// Auto loading\n// Everything inside \"context folder\" and named in PascalCase\napp.load(autoLoad(context));\n// Manual registering\napp.register('my-component', MyComponent);\n```\n\n### Pass \"properties\" to all components\n\n```js\nconst app = Application.start(document.body, undefined, {\n  prop: 'value',\n});\n```\n\nProperties will be accessible in all components through `this.prop`.\n\n\u003e The second paramater is for \"custom schema\" ([more info](src/core/schema.js))\n\n### Use components\n\n#### Basics\n\n```html\n\u003cdiv data-component=\"foo\"\u003e\u003c/div\u003e\n\u003cdiv data-component=\"sub--bar-baz\"\u003e\u003c/div\u003e\n```\n\n- `scripts/kapla/Foo.js`\n- `scripts/kapla/sub/BarBaz.js`\n\n```js\nimport { Component } from 'kapla';\n\nexport default class extends Component {\n    load() {}\n    init() {}\n    destroy() {}\n}\n```\n\n\u003e Component filename must be `PascalCase.js` for autoload.\n\u003e Same element can be used multiple components: `data-component=\"foo bar baz\"`.\n\n#### References\n\n```html\n\u003cdiv data-component=\"foo\"\u003e\n    \u003cbutton type=\"submit\" data-ref=\"foo.submit\"\u003eSubmit\u003c/button\u003e\n\u003c/div\u003e\n```\n\n```js\nthis.$el // DIV\nthis.$refs.submit // BUTTON\n```\n\n\u003e Same element can be ref for multiple components: `data-ref=\"foo.submit bar.button\"`.\n\n#### Data\n\n```html\n\u003cdiv data-component=\"foo\" data-foo-prop=\"qux\"\u003e\u003c/div\u003e\n```\n\n```js\nthis.data.has('prop') // true\nthis.data.get('prop') // 'qux'\nthis.data.set('prop', 'quux') // 'quux'\n```\n\n#### Events\n\n##### Native\n\nAutomatic binding/unbinding through lifecycle (init/destroy).\n\n```js\nexport default class extends Component {\n    onClick(e) {}\n    onBlur(e) {}\n    …\n}\n```\n\n##### Mixed\n\nAutomatic binding/unbinding through lifecycle (init/destroy).\n\n```js\nexport default class extends Component {\n    onEnter(e) {}\n    onLeave(e) {}\n    onMove(e) {}\n    onOver(e) {}\n    onOut(e) {}\n}\n```\n\n##### Native/mixed + delegate\n\n```js\nexport default class extends Component {\n    init() {\n        this.delegateClick = 'selector'; // CSS selector\n        this.delegateClick = this.$refs.child; // HTMLElement\n        this.delegateClick = document.querySelectorAll('selector'); // HTMLCollection (or Array of elements)\n\n        this.delegateMove = 'selector';\n    }\n    onClick(e, target) {} // extra \"target\" parameter\n    onMove(e, target) {}\n    …\n}\n```\n\n##### Custom\n\nNeed to be 'registered' (before component registration).\n\n```js\nimport { myCustomEvent } from './my-custom-events';\n\napp.use('myCustomEvent', myCustomEvent);\n```\n\nThen, automatic binding/unbinding through lifecycle (init/destroy).\n\n```js\nexport default class extends Component {\n    onMyCustomEvent(...args) {}\n}\n```\n\n###### CustomEvent examples\n\nShould have `bind` and `unbind` methods which receive `component` and `ee` as parameters.\nCan be 'scoped' to `component` (default) or `global` (see second example).\nIn this case, you can choose to log the event name when it is emitted…\nAlso, global custom events are binded only when components are listening to them.\nThey are unbinded when no more components are listening to them.\n\n`clickOutside.js`\n\n```js\nimport { CustomEvent } from 'kapla';\n\nclass MyEvent extends CustomEvent {\n  constructor(...args) {\n    super(...args);\n  }\n\n  bind(component) {\n    const { element } = component.context;\n\n    this.eventByElement.set(element, this.callback(component));\n    document.addEventListener('click', this.eventByElement.get(element));\n  }\n\n  unbind(component) {\n    const { element } = component.context;\n\n    document.removeEventListener('click', this.eventByElement.get(element));\n  }\n\n  callback(component) { // eslint-disable-line class-methods-use-this\n    return function callback(e) {\n      if (!component.context.element.contains(e.target)) {\n        component.onClickOutside(e);\n      }\n    };\n  }\n};\n\nexport const clickOutside = new MyEvent('clickOutside');\n```\n\n`raf.js`\n\n```js\nimport { CustomEvent } from 'kapla';\n\nclass MyEvent extends CustomEvent {\n  constructor(...args) {\n    super(...args);\n\n    this.scope = 'global';\n    this.log = false;\n  }\n\n  bind(component, ee) {\n    const { element } = component.context;\n\n    this.ee = ee;\n    this.eventByElement.set(element, this.callback(component));\n\n\n    this.ee.on('raf', this.eventByElement.get(element));\n    this.onTick = this.onTick.bind(this);\n    this.time = window.performance.now();\n    this.raf = window.requestAnimationFrame(this.onTick);\n  }\n\n  unbind(component, ee) {\n    ee.off('raf', this.eventByElement.get(component.context.element));\n    window.cancelAnimationFrame(this.raf);\n  }\n\n  onTick(now) {\n    this.time = now;\n    this.delta = (now - this.oldTime) / 1000;\n    this.oldTime = now;\n    this.ee.emit('raf', this.delta, now);\n    this.raf = window.requestAnimationFrame(this.onTick);\n  }\n\n  callback(component) { // eslint-disable-line class-methods-use-this\n    return function callback(delta, now) {\n      component.onRaf(delta, now);\n    };\n  }\n}\n\nexport const raf = new MyEvent('raf');\n```\n\n##### Manual\n\nNative, mixed or custom events can be 'binded' or 'unbinded' manually.\n\n```js\nexport default class extends Component {\n    method() {\n        this.bind('click');\n        this.bind('enter');\n        this.bind('myCustomEvent');\n    }\n}\n```\n\n#### Communication between components\n\nYou can \"subscribe\" to another component. It makes communication easier between components:\n\n- `const subscriber = this.subscribe('other-component')`\n\nThis returns the \"subscriber\", then you can \"listen\" for some custom event…\n\n- __Component.js__: `subscriber.on('some-event', cb)`\n- __OtherComponent.js__: `this.emit('some-event'[, args])`\n\n\u003e NB: `.on` method returns the \"subscriber\" and then can be chained (`this.subscribe('c').on('foo', cb).on('bar', cb)…`).\n\n#### \"NoComponent\"\n\nKapla can also be used with `NoComponent` aka \"component-with-no-DOM-element\".\nIn this case, there is no \"lifecycle\" so you need to `init` those components \"manually\".\n\n```js\nimport { NoComponent } from 'kapla';\n\nexport default class MyNoComponentThing extends NoComponent {\n    init() {}\n    destroy() {}\n    onCustomEvent() {}\n    …\n}\n```\n\nFor initilization: `app.init('my-no-component-thing', MyNoComponentThing);`\n\n\u003e As this instance is unique, it is returned by the `init()` method.\n\u003e To \"kill\" the instance, just `remove()` it.\n\n#### \"Mixin\"\n\nLast but not least, you can use Kapla [No]Components in \"mixin\" mode!\nFor that, `mixComponent` and `mixNoComponent` are available.\n\n```js\nimport { mixComponent } from 'kapla';\nimport Fake from 'test/Fake';\n\nexport default class Mix extends mixComponent(Fake) {\n  load() {}\n  init() {}\n  destroy() {}\n}\n```\n\n```js\nimport { mixNoComponent } from 'kapla';\nimport Fake from 'test/Fake';\n\nexport default class MixNo extends mixNoComponent(Fake) {\n  init() {}\n  destroy() {}\n}\n```\n\nAs you may pass some parameters to your \"superclass\", they also are registered/initiated manually.\nJust pass your arguments behind the constructor:\n\n- `app.register('mix', Mix, 'some', 'params');`\n- `app.init('mix-no', MixNo, 'some', 'other', 'params');`\n\n\u003e You can \"mix\" multiples classes (not tested).\n\u003e Of course, if you use `autoload`, put those files in another folder… ;)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthierrymichel%2Fkapla","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthierrymichel%2Fkapla","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthierrymichel%2Fkapla/lists"}