Frontend framework based on WebComponents with full support of TypeScript.
Components are classes that represents WebComponents. To define your component use a Component
decorator with tag name of component as argument:
import { ComponentBase, Component, Ref, html } from 'bubblejs-core';
@Component('my-component')
class MyComponent extends ComponentBase {
@Ref()
public count: number = 0;
template() {
return html`
<button @click="${this.onClick}">
Clicked ${this.count} times
</button>
`;
}
onClick() {
this.count++;
}
// Lifecycle hooks
onBeforeMount() {
console.log('Before mounted');
}
onMount() {
console.log('Mounted');
}
onAdopt() {
console.log('Adopted');
}
onDestroy() {
console.log('Destroyed');
}
styles() {
return /*css*/`
button {
padding: 12px 20px;
background: #4938ff;
color: white;
border: none;
border-radius: 8px;
outline: none;
cursor: pointer;
font-size: 16px;
transition: 0.3s ease-in-out;
}
button:hover {
background: #6456fb;
}
`;
}
}
<my-component></my-component>
When you define a component class, you should define a template()
method, that returns HTML template that will be rendered.
The styles()
method is optional and used to define CSS of your component.
Refs are simply reactive data variables, that can be used inside component. To define a ref use a Ref
decorator:
import { ComponentBase, Component, Ref, html } from 'bubblejs-core';
@Component('my-component')
class MyComponent extends ComponentBase {
@Ref()
public count: number = 0;
template() {
return html`
<span>
Count is ${ this.count }
</span>
`;
}
onMount() {
setInterval(() => {
this.count++
}, 100);
}
}
<my-component></my-component>
Refs can be accessed via this
context inside template
, lifecycle hooks, event listeners, methods.
For more complex scenarios Ref
has callbacks that you can use:
init
(Called before mount, when value of ref should be initialized)get
(Called when someone is trying to get ref value)set
(Called when someone is trying to change ref value)
import { ComponentBase, Component, Ref, html } from 'bubblejs-core';
@Component('my-component')
class MyComponent extends ComponentBase {
@Ref({
init(key) => console.log('Initialized ref'),
get(key) => console.log('Trying to get ref value'),
set(key, newVal, oldVal) => console.log('Trying to change ref value'),
})
public count: number = 0;
template() {
return html`
<span>
Count is ${ this.count }
</span>
`;
}
onMount() {
setInterval(() => {
this.count++
}, 100);
}
}
...
Props allows to define acceptable properties of your component. To define a component prop use a Prop
decorator:
import { ComponentBase, Component, Prop, html } from 'bubblejs-core';
@Component('my-component')
class MyComponent extends ComponentBase {
@Prop()
public text: string = '';
template() {
return html`
<span>${ this.text }</span>
`;
}
}
<my-component text="Hello world"></my-component>
Notice: you can pass arrays/objects/functions as prop value also, but only inside another component template.
Props can be accessed via this
context inside template
, lifecycle hooks, event listeners, methods.
Elements are simply refs that stores html elements. You can use them to automatically get html element from your template and work with it.
import { ComponentBase, Component, Element, html } from 'bubblejs-core';
@Component('my-component')
class MyComponent extends ComponentBase {
@Element('.some-input')
public inputEl?: HTMLInputElement;
template() {
return html`
<div class="some-container">
<input class="some-input">
</div>
`;
}
onMount() {
if(this.inputEl) {
this.inputEl.value = 'hello world!';
}
}
}
Events allows you to emit custom events up to parent component.
You just have to call this.emit(eventName, ...args)
method to emit your event.
import { ComponentBase, Component, html } from 'bubblejs-core';
@Component('my-child')
class MyChild extends ComponentBase {
template() {
return html`
<button @click="${this.onClick}">
Button!
</button>
`;
}
onClick(e: Event) {
e.preventDefault();
// Emit custom event
this.emit('some-event');
}
}
@Component('my-parent')
class MyParent extends ComponentBase {
template() {
return html`
<mychild @some-event="${() => console.log('fired!')}"></mychild>
`;
}
}
Notice: emit
method is calls native dispatchEvent
, that means you can also
use native addEventListener
to catch your component's custom event.
in progress.
in progress.
For conditional rendering framework is provides special reserved directives --if
and --else
. You should pass boolean value, if value will be true
, the element will be rendered, otherwise it will render the --else
element.
import { ComponentBase, Component, Ref, html } from './lib/index';
@Component('my-component')
class MyComponent extends ComponentBase {
@Ref()
public doRender: boolean = false;
template() {
return html`
<p --if="${this.doRender}">
Hello world!
</p>
<p --else>
Bye world!
</p>
`;
}
}
After render:
<my-component>
<p>Bye world!</p>
</my-component>
As you apply conditions to render or not elements, you can apply conditions to set attribute or not. Framework provides special syntax for conditional attributes: attrName:if="${true | false}"
. If value will be true, the element will have attrName=""
attribute, otherwise attribute will be not added.
import { ComponentBase, Component, Ref, html } from './lib/index';
@Component('my-component')
class MyComponent extends ComponentBase {
@Ref()
public isRequired: boolean = false;
@Ref()
public isDisabled: boolean = true;
template() {
return html`
<input required:if="${this.isRequired}" disabled:if="${this.isDisabled}">
`;
}
}
After render:
<my-component>
<input disabled="">
</my-component>
Loops can be helpful when you need to render a list of something. To create a loop you can use a native array .map
method.
import { ComponentBase, Component, Ref, html } from 'bubblejs-core';
@Component('some-list')
class SomeList extends ComponentBase {
@Ref()
public items: string[] = [
'hello',
'world',
'guys'
];
template() {
return html`
<div class="list">
${this.items.map((item) => html`
<div class="list__item">
<p>${item}</p>
</div>
`)}
</div>
`;
}
}
Notice: you can use alternative ways to do a loops, but in any way the returned value should be of type HtmlTemplate[]
.
Framework also provides a built-in lightweight routing system.
Firstly you have to use bubble-router
component which is renders
current view inside:
<bubble-router></bubble-router>
After that you can define views in your code using View
decorator:
import { ComponentBase, View, Ref, html } from 'bubblejs-core';
@View('/')
class IndexView extends ComponentBase {
@Ref()
public subject = 'world';
template() {
return html`
<p>Hello ${subject}!</p>
`;
}
}
As you see, views are basically components but defined in different way.