npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

kapla

v2.1.7

Published

Tiny JS framework to manage DOM components

Downloads

83

Readme

kapla 👷‍

stability-wip NPM version Coverage Status

Tiny JS framework to manage DOM components

wip (this project is no longer maintained)

Overview

The main goal is to make easier to implement common tasks/features:

  • Component declaration and instanciation
  • Component init and destroy (Barba.js or load more…)
  • Access to $el et $refs
  • Use of data-attribute
  • Events handling with the right context (standard or custom)

Main features :

  • Components autoload + declaration data-component="foo"
  • Component lifecycle : load, init, destroy
  • Easy references:
    • data-component="foo" -> this.$el
    • data-ref="foo.child" -> this.$refs.child
  • Provide simple API to manage dataset:
    • data-foo-prop="value" ->
      • this.data.has('prop') // true
      • this.data.get('prop') // value
      • this.data.set('prop', 'another value') // another value
  • Events binding/unbinding with onClick() {} ou onCustomEvent() {}

Start application

<main class="app"></main>
import {
  Application,
  autoLoad,
} from 'kapla';

import MyComponent from 'kapla-register/MyComponent';

const context = require.context('./kapla', true, /\.js$/);
const app = Application.start(document.querySelector('.app')); // If no element -> document.body

// Auto loading
// Everything inside "context folder" and named in PascalCase
app.load(autoLoad(context));
// Manual registering
app.register('my-component', MyComponent);

Pass "properties" to all components

const app = Application.start(document.body, undefined, {
  prop: 'value',
});

Properties will be accessible in all components through this.prop.

The second paramater is for "custom schema" (more info)

Use components

Basics

<div data-component="foo"></div>
<div data-component="sub--bar-baz"></div>
  • scripts/kapla/Foo.js
  • scripts/kapla/sub/BarBaz.js
import { Component } from 'kapla';

export default class extends Component {
    load() {}
    init() {}
    destroy() {}
}

Component filename must be PascalCase.js for autoload. Same element can be used multiple components: data-component="foo bar baz".

References

<div data-component="foo">
    <button type="submit" data-ref="foo.submit">Submit</button>
</div>
this.$el // DIV
this.$refs.submit // BUTTON

Same element can be ref for multiple components: data-ref="foo.submit bar.button".

Data

<div data-component="foo" data-foo-prop="qux"></div>
this.data.has('prop') // true
this.data.get('prop') // 'qux'
this.data.set('prop', 'quux') // 'quux'

Events

Native

Automatic binding/unbinding through lifecycle (init/destroy).

export default class extends Component {
    onClick(e) {}
    onBlur(e) {}
    …
}
Mixed

Automatic binding/unbinding through lifecycle (init/destroy).

export default class extends Component {
    onEnter(e) {}
    onLeave(e) {}
    onMove(e) {}
    onOver(e) {}
    onOut(e) {}
}
Native/mixed + delegate
export default class extends Component {
    init() {
        this.delegateClick = 'selector'; // CSS selector
        this.delegateClick = this.$refs.child; // HTMLElement
        this.delegateClick = document.querySelectorAll('selector'); // HTMLCollection (or Array of elements)

        this.delegateMove = 'selector';
    }
    onClick(e, target) {} // extra "target" parameter
    onMove(e, target) {}
    …
}
Custom

Need to be 'registered' (before component registration).

import { myCustomEvent } from './my-custom-events';

app.use('myCustomEvent', myCustomEvent);

Then, automatic binding/unbinding through lifecycle (init/destroy).

export default class extends Component {
    onMyCustomEvent(...args) {}
}
CustomEvent examples

Should have bind and unbind methods which receive component and ee as parameters. Can be 'scoped' to component (default) or global (see second example). In this case, you can choose to log the event name when it is emitted… Also, global custom events are binded only when components are listening to them. They are unbinded when no more components are listening to them.

clickOutside.js

import { CustomEvent } from 'kapla';

class MyEvent extends CustomEvent {
  constructor(...args) {
    super(...args);
  }

  bind(component) {
    const { element } = component.context;

    this.eventByElement.set(element, this.callback(component));
    document.addEventListener('click', this.eventByElement.get(element));
  }

  unbind(component) {
    const { element } = component.context;

    document.removeEventListener('click', this.eventByElement.get(element));
  }

  callback(component) { // eslint-disable-line class-methods-use-this
    return function callback(e) {
      if (!component.context.element.contains(e.target)) {
        component.onClickOutside(e);
      }
    };
  }
};

export const clickOutside = new MyEvent('clickOutside');

raf.js

import { CustomEvent } from 'kapla';

class MyEvent extends CustomEvent {
  constructor(...args) {
    super(...args);

    this.scope = 'global';
    this.log = false;
  }

  bind(component, ee) {
    const { element } = component.context;

    this.ee = ee;
    this.eventByElement.set(element, this.callback(component));


    this.ee.on('raf', this.eventByElement.get(element));
    this.onTick = this.onTick.bind(this);
    this.time = window.performance.now();
    this.raf = window.requestAnimationFrame(this.onTick);
  }

  unbind(component, ee) {
    ee.off('raf', this.eventByElement.get(component.context.element));
    window.cancelAnimationFrame(this.raf);
  }

  onTick(now) {
    this.time = now;
    this.delta = (now - this.oldTime) / 1000;
    this.oldTime = now;
    this.ee.emit('raf', this.delta, now);
    this.raf = window.requestAnimationFrame(this.onTick);
  }

  callback(component) { // eslint-disable-line class-methods-use-this
    return function callback(delta, now) {
      component.onRaf(delta, now);
    };
  }
}

export const raf = new MyEvent('raf');
Manual

Native, mixed or custom events can be 'binded' or 'unbinded' manually.

export default class extends Component {
    method() {
        this.bind('click');
        this.bind('enter');
        this.bind('myCustomEvent');
    }
}

Communication between components

You can "subscribe" to another component. It makes communication easier between components:

  • const subscriber = this.subscribe('other-component')

This returns the "subscriber", then you can "listen" for some custom event…

  • Component.js: subscriber.on('some-event', cb)
  • OtherComponent.js: this.emit('some-event'[, args])

NB: .on method returns the "subscriber" and then can be chained (this.subscribe('c').on('foo', cb).on('bar', cb)…).

"NoComponent"

Kapla can also be used with NoComponent aka "component-with-no-DOM-element". In this case, there is no "lifecycle" so you need to init those components "manually".

import { NoComponent } from 'kapla';

export default class MyNoComponentThing extends NoComponent {
    init() {}
    destroy() {}
    onCustomEvent() {}
    …
}

For initilization: app.init('my-no-component-thing', MyNoComponentThing);

As this instance is unique, it is returned by the init() method. To "kill" the instance, just remove() it.

"Mixin"

Last but not least, you can use Kapla [No]Components in "mixin" mode! For that, mixComponent and mixNoComponent are available.

import { mixComponent } from 'kapla';
import Fake from 'test/Fake';

export default class Mix extends mixComponent(Fake) {
  load() {}
  init() {}
  destroy() {}
}
import { mixNoComponent } from 'kapla';
import Fake from 'test/Fake';

export default class MixNo extends mixNoComponent(Fake) {
  init() {}
  destroy() {}
}

As you may pass some parameters to your "superclass", they also are registered/initiated manually. Just pass your arguments behind the constructor:

  • app.register('mix', Mix, 'some', 'params');
  • app.init('mix-no', MixNo, 'some', 'other', 'params');

You can "mix" multiples classes (not tested). Of course, if you use autoload, put those files in another folder… ;)