class-component v14.0.0
:pill: Component-based DOM programming
capsid
is a library for component-based DOM programming.
capsid
gives behaviors to the classes of html elements based on the component definitions. See Hello Example :wave: or Clock Example :stopwatch:.
capsid
uses decorators for defining event handlers and event emitters declaratively. See Mirroring Example :butterfly: and Counter Example :level_slider:.
For state management, capsid
has evex, which is the variation of flux design pattern by using DOM events. Please check evex repository for details.
:sparkles: Features
- Component-based DOM programming library
- :leaves: Small. , No dependencies.
- :lollipop: Easy. It uses plain JavaScript (+ ESNext decorators)
- :dragon: Force. It adds behaviors (event handlers and lifecycles) to classes of elements based on component definition.
- :sunny: Simple. 7 APIs & 5 decorators
:wave: Hello Example
The hello example shows the minimal usage of capsid.js:
<script src="https://unpkg.com/capsid"></script>
<script>
class Hello {
__mount__ () {
this.el.textContent = 'Hello, world!'
}
}
capsid.def('hello', Hello)
</script>
<span class="hello"></span>
capsid.def('hello', Hello)
defines hello
component and it initializes <span class="hello"></span>
with hello
component when document is ready. When initializing the component, __mount__
method is called and in this case textContent
of the element becomes Hello, world!
.
:butterfly: Mirroring Example
The mirroring example shows how you can mirror the input to a different dom in capsid.js.
const { on, wired, component } = require("capsid");
@component("mirroring") // This registeres the class as a capsid component
class Mirroring {
@wired(".dest") // This decorator makes the field equivalent of `get dest () { return this.el.querySelector('.dest'); }`
dest;
@wired(".src")
src;
@on("input") // This decorator makes the method into an event listener on the mounted element
onReceiveData() {
this.dest.textContent = this.src.value;
}
}
<div class="mirroring">
<p>
<input class="src" placeholder="Type something here" />
</p>
<p class="dest"></p>
</div>
With the above example, the input value of .src
is copied to .dest
on each input event.
:cd: Install
Via npm
npm install --save capsid
then:
const capsid = require('capsid')
Via file
Download capsid.min.js Then:
<script src="path/to/capsid.js"></script>
In this case, the library exports the global variable capsid
.
capsid.def('my-component', MyComponent)
Initialization
There are 2 ways to initialize components:
- When document is ready (automatic).
- When
capsid.prep()
is called (manual).
All components are initialized automatically when document is ready. You don't need to care about those elements which exist before document is ready. See Hello Example or Clock Example for example.
If you add elements after document is ready (for example, after ajax requests), call capsid.prep()
and that initializes all the components.
const addPartOfPage = async () => {
const { html } = await axios.get('path/to/something.html')
containerElemenent.innerHTML = html
capsid.prep() // <= this initializes all the elements which are not yet initialized.
})
Capsid Lifecycle
φ -> mount -> component -> unmount -> φ
capsid lifecycle events
There are 2 lifecycle events in capsid: mount
and unmount
.
mount
- HTML elements are mounted by the components.
- An element is coupled with the corresponding coelement and they start working together.
- The timing of
mount
is eitherDOMContentLoaded
orcapsid.prep()
.
unmount
- An element is decouple with the coelement.
- All events are removed and coelement is discarded.
- You need to call
unmount(class, element)
to unmount the component.
Explanation of mount
At mount
event, these things happen.
- The component class's
instance
(coelement) is created. instance
.el is set to corresponding dom element.- event listeners defined by
@on
decorators are attached to the dom element. - plugin hooks are invoked if you use any.
- if
instance
has mount method, theninstance.__mount__()
is called.
The above happens in this order. Therefore you can access this.el
and you can invoke the events at this.el
in __mount__
method.
Lifecycle Methods
constructor
The constructor is called at the start of mount
ing. You cannot access this.el
here. If you need to interact with html, __mount__
is more appropriate place.
__mount__
__mount__()
is called at the end of the mount event. When it called, the dom element and event handlers are ready and available through this.el
.
__unmount__
__unmount__()
is called when component is unmounted. If your component put resources on global space, you should discard them here to avoid memory leak.
Coelement
The concept of 'coelement' is explained at Component and Coelement section of the document site.
APIs
const {
def,
prep,
make,
mount,
unmount,
get,
install
} = require('capsid')
def(name, constructor)
- Registers class-component.
prep([name], [element])
- Initialize class-component on the given range.
make(name, element)
- Initializes the element with the component of the given name and return the coelement instance.
mount(Constructor, element)
- Initializes the element with the component of the given class and return the coelement.
unmount(name, element)
- unmount the component from the element by its name.
get(name, element)
- Gets the coelement instance from the given element.
install(capsidModule, options)
- installs the capsid module with the given options.
def(name, constructor)
- @param {string} name The class name of the component
- @param {Function} constructor The constructor of the coelement of the component
This registers constructor
as the constructor of the coelement of the class component of the given name name
. The constructor is called with a jQuery object of the dom as the first parameter and the instance of the coelement is attached to the dom. The instance of coelement can be obtained by calling elem.cc.get(name)
.
Example:
class TodoItem {
// ...behaviours...
}
capsid.def('todo-item', TodoItem)
<li class="todo-item"></li>
prep([name], [element])
- @param {string} name The capsid component name to intialize
- @param {HTMLElement} element The range to initialize
This initializes the capsid components of the given name under the given element. If the element is omitted, it initializes in the entire page. If the name is omitted, then it initializes all the registered class components in the given range.
make(name, element)
- @param {string} name The capsid component name to initialize
- @param {HTMLElement} element The element to initialize
- @return {} created coelement
Initializes the element as the capsid component and returns the coelement instance.
const timer = make('timer', dom)
mount(Constructor, element)
- @param {Function} Constructor The constructor which defines the capsid component
- @param {HTMLElemen} element The element to mount the component
- @return {} The created coelement
Initializes the element with the component of the given class and return the coelement.
class Component {
__mount__ () {
this.el.foo = 1
}
}
const div = document.createElement('div')
capsid.mount(Component, div)
div.foo === 1 # => true
Usually you don't need to use this API. If you're writing library using capsid, you might sometimes need to create an unnamed component and need this API then.
unmount(name, element)
- @param {string} name The component name
- @param {HTMLElement} element The element
Unmounts the component of the given name from the element.
Example:
@component('foo')
class Foo {
@on('input')
remove () {
unmount('foo', this.el)
}
}
The above example unmounts itself when it receives input
event.
get(name, element)
- @param {string} name The capsid component name to get
- @param {HTMLElement} element The element
- @return The coelement instance
Gets the component instance from the element.
const timer = capsid.get('timer', el)
The above gets timer coelement from el
, which is instance of Timer
class.
install(capsidModule[, options])
- @param {CapsidModule} capsidModule The module to install
- @param {Object} options The options to pass to the module
This installs the capsid module.
capsid.install(require('capsid-popper'), { name: 'my-app-popper' })
See capsid-module repository for details.
Decorators
const {
component,
on,
emits,
wired,
notifies
} = require('capsid')
There are 5 types of decorators.
@component(name)
- class decorator
- registers as a capsid components.
@on(event, { at })
- method decorator
- registers as an event listener on the component.
@on.click
is a shorthand for@on('click')
.@on.click.at(selector)
is a shorthand for@on('click', { at: selector })
.
@emits(event)
- method decorator
- makes the decorated method an event emitter.
@wired(selector)
- field decorator
- wires the elements to the decorated field by the given selector.
- optionally
@wired.component(name, [selector])
@wired.all(selector)
@notifies
- method decorator
- makes the decorated method an event broadcaster.
@component(className)
capsid.component(className) is class decorator. With this decorator, you can regiter the js class as class component.
This is a shorthand of capsid.def('component', Component)
.
const { component } = require('capsid')
@component('timer')
class Timer {
...definitions...
}
The above registers Timer
class as timer
component.
@on(eventName)
@on
is a method decorator. With this decorator, you can register the method as the event handler of the element.
const { on } = capsid
class FooButton {
@on('click')
onClick (e) {
...definitions...
}
}
capsid.def('foo-btn', FooButton)
The above binds onClick
method to its element's 'click' event automatically.
The above is equivalent of:
class FooButton {
__mount__ () {
this.el.addEventListener('click', e => {
this.onClick(e)
})
}
onClick (e) {
...definitions...
}
}
capsid.def('foo-btn', FooButton)
@on(name, { at: selector })
@on(name, { at: selector })
is a method decorator. It's similar to @on
, but it only handles the event from selector
in the component.
const { on } = capsid
class Btn {
@on('click', { at: '.btn' })
onBtnClick (e) {
...definitions...
}
}
capsid.def('btn', Btn)
In the above example, onBtnClick
method listens to the click event of the .btn
element in the Btn
's element.
@on.click
@on.click
is a shorthand for @on('click')
.
class Foo {
@on.click
onClick {
// handling of the click of the Foo component
}
}
@on.click.at(selector)
@on.click.at(selector)
is a shorthand for @on('click', { at: selector })
class Foo {
@on.click.at('.edit-button')
onClickAtEditButton () {
// handling of the click of the edit button
}
}
NOTE: You can add this type of short hand by calling on.useHandler(eventName)
.
on.useHandler('change')
class Foo {
@on.change.at('.title-input') // <= This is enabled by the above useHandler call.
onChangeAtTitleInput () {
// handles the change event of title input field.
}
}
@emits(eventName)
@emits(eventName)
triggers the event at the end of the method.
const { emits, def } = require('capsid')
class Manager {
@emits('manager.ended')
start() {
...definitions...
}
}
def('manager', Manager)
In the above example, start
method triggers the manager.ended
event when it finished. The returns value of the method is passed as detail
of the event object. So you can pass the data from children to parents.
If the method returns a promise, then the event is triggered after the promise is resolved.
const { emits, def } = require('capsid')
class Manager {
@emits('manager.ended')
start () {
...definitions...
return promise
}
}
def('manager', Manager)
In the above example, manager.ended
event is triggered after promise
is resolved. The resolved value of the promise is passed as detail
of the event object.
@wired.component(name[, selector])
@wired.component
is a field decorator. If a field is decorated, it points to the component instance of the given name at the element which is found by the given selector.
const { wired, component } = require('capsid')
@component('foo')
class Foo {
@wired.component('bar', '.bar')
bar
processBar () {
this.bar.process()
}
}
@component('bar')
class Bar {
process () {
console.log('processing bar!')
}
}
In the above situation, the field bar
of Foo class is wired to bar
component inside the foo component. With the above settings you can call the following:
const div = document.createElement('div')
const foo = make('foo', div)
foo.processBar()
And this prints processing bar!
.
If selector
is omitted, then .
+ name
is used for the selector. The above foo
component can be written like the below:
@component('foo')
class Foo {
@wired.component('bar')
bar
processBar () {
this.bar.process()
}
}
@wired(selector) field
- @param {string} selector The selector to look up the element in the component
This wires the decorated field to the element selected by the given selector. The wired element is a unusal dom element (HTMLElement), not a capsid component instance.
If the selector matches to the multiple elements, then the first one is used.
@wired.all(selector) field
- @param {string} selector The selector to look up the elements in the component
This wires the decorated field to the all elements selected by the given selector. This is similar to @wired
decorator, but it wires all the elements, not the first one.
@notifies(event, selector)
- @param {string} event The event type
- @param {string} selector The selector to notify events
@notifies
is a method decorator. It adds the function to publishes the event to its descendant elements at the end of the decorated method.
@component('foo')
class Component {
@notifies('user-saved', '.is-user-observer')
saveUser () {
this.save(this.user)
}
}
In the above, when you call saveUser
method, it publishes user-saved
event to its descendant .is-user-observer
elements.
For example, if the dom tree is like the below:
<div class="component">
<input class="is-user-observer">
<label class="is-user-observer"></label>
</div>
When saveUser
is called, then input
and label
elements get user-saved
event and they can react to the change of the data user
.
This decorator is useful for applying flux design pattern to capsid components.
Plugins
Debug plugin
debug plugin
outputs information useful for debugging capsid app.
Install
Via npm:
const capsid = require('capsid')
capsid.install(require('capsid/debug'))
Via CDN:
<script src="https://unpkg.com/capsid"></script>
<script src="https://unpkg.com/capsid/dist/capsid-debug.js"></script>
<script>capsid.install(capsidDebugPlugin)</script>
And you'll get additional debug information in console.
Outside Events Plugin
Install
Via npm:
const capsid = require('capsid')
capsid.install(require('capsid/outside-events'))
Via cdn:
<script src="https://unpkg.com/capsid"></script>
<script src="https://unpkg.com/capsid/dist/capsid-outside-events.js"></script>
<script>
capsid.install(capsidOutsideEventsPlugin)
</script>
With outside-events-plugin
, you can bind methods to events outside of your coponent's element. (This event need to bubble up to document
)
@component('modal')
class Modal {
@on.outside('click')
close () {
this.el.classList.remove('is-shown')
}
open () {
this.el.classList.add('is-shown')
}
}
The above modal
component gets is-shown
class removed from the element when the outside of modal is clicked.
prior art
History
- 2019-06-09 v0.29.2 Throw error when empty selector is given (
@notifies
) - 2018-12-01 v0.29.0 Switch to TypeScript.
- 2018-11-22 v0.28.0 Switch to new decorator. Remove jquery-plugin.
- 2018-08-07 v0.26.1 Fix bug of unmount and on handler.
- 2018-07-12 v0.26.0 Add debug log contents.
- 2018-06-22 v0.25.0 Add
@on.useHandler
. - 2018-06-22 v0.24.0 Add
@on.click.at
. - 2018-05-20 v0.23.5 Fix unmount bug.
- 2018-04-18 v0.23.4 Fix unmount bug.
- 2018-04-10 v0.23.0 Change debug format.
- 2018-04-09 v0.22.0 Rename init to mount.
- 2018-04-08 v0.21.0 Add
unmount
. - 2018-04-04 v0.20.3 Change initialized class name.
- 2018-03-08 v0.20.0 Add install function.
- 2017-12-31 v0.19.0 Add wired, wired.all and wired.component decorators.
- 2017-12-05 v0.18.3 Add an error message.
- 2017-10-12 v0.18.0 Add Outside Events plugin.
- 2017-10-01 v0.17.0 Add Debug plugin.
- 2017-09-09 v0.16.0 Rename
@emit
to@emits
and@pub
to@notifies
- 2017-09-06 v0.15.1 Change component init sequence.
- 2017-09-05 v0.15.0 Add
mount
API. Removeinit
API. - 2017-08-04 v0.14.0 Make
@on
listeners ready at init call. - 2017-08-03 v0.13.0 Add pub decorator.
- 2017-07-15 v0.12.0 Add wire.$el and wire.elAll to jquery plugin.
- 2017-07-13 v0.11.0 Add wire.el and wire.elAll.
- 2017-07-11 v0.10.0 Add emit.first rename emit.last to emit.
- 2017-07-10 v0.9.0 Add on.click shorthand.
- 2017-03-01 v0.8.0 Modify init sequence.
- 2017-02-26 v0.7.0 Add static capsid object to each coelement class.
- 2017-02-26 v0.6.0 static init rule.
- 2017-02-25 v0.5.0 coelem.capsid, initComponent APIs.
- 2017-01-19 v0.3.0 API reorganization.
- 2017-01-19 v0.2.2 Rename to capsid.
- 2017-01-17 v0.1.1 Add plugin system.
History of class-component.js (former project)
- 2017-01-02 v13.0.0 Add init instead of init.
- 2017-01-01 v12.1.1 Fix bug of event bubbling.
- 2017-01-01 v12.1.0 Remove @emit.first. Use native dispatchEvent.
- 2016-12-31 v12.0.0 Remove cc_init feature. Add init feature.
- 2016-09-30 v10.7.1 Refactor @emit.last decorator
- 2016-09-11 v10.7.0 Add @on(event, {at}) @emit.first and @emit.last
- 2016-08-22 v10.6.2 Refactor the entrypoint.
- 2016-08-22 v10.6.1 Improved the event listener registration process.
- 2016-08-20 v10.6.0 Cleaned up some private APIs.
- 2016-08-20 v10.5.0 Cleaned up codebase and made the bundle smaller. Remove some private APIs.
- 2016-08-17 v10.4.1 Made built version smaller.
- 2016-08-16 v10.4.0 Switched to babel-preset-es2015-loose.
- 2016-08-16 v10.3.0 Modified bare @wire decorator.
- 2016-08-02 v10.2.0 Added bare @component decorator.
- 2016-07-21 v10.1.0 Added @wire decorator.
- 2016-06-19 v10.0.0 Removed deprecated decorators
@event
and@trigger
, use@on
and@emit
instead. - 2016-06-09 v9.2.0 Fixed bug of
@emit().last
decorator.
The user projects
The projects which uses capsid.
- todomvc
- TodoMVC in capsid.
- multiflip
- multiflip-bubble
- puncher
- event-hub
- spn
- view-todo
- pairs
- Pairs is the primary user project and inspiration source of capsid.
- capsid is basically created for developing this project.
Notes
The name
capsid /ˈkapsəd/ n.
the protein coat or shell of a virus particle, surrounding the nucleic acid or nucleoprotein core.
The purpose of capsid is to encapsulate the details of its contents just like capsid for virus cells. Its has the same origin capsa ("box" in Latin) as the word capsule.
The name 'coelement'
co-
means the dual or the other aspect of something like cosine
to sine
cotangent
to tangent
etc. Coelement is the other aspect of element
and it works together in the 1-to-1 relationship and in the same lifecycle with the element.
(possibly) similar projects
- DOM99
- RE:DOM
- butterknife
butterknife
is a library for Android development. The syntax and some ideas are similar to capsid.
Examples
- :wave: Hello Example
- :stopwatch: Clock Example
- :level_slider: Counter Example
- :butterfly: Mirroring Example
License
MIT
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago