Skip to content

Latest commit

 

History

History
374 lines (312 loc) · 8.2 KB

README.md

File metadata and controls

374 lines (312 loc) · 8.2 KB

BubbleJS

Frontend framework based on WebComponents with full support of TypeScript.

Features

Components

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

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);
  }
}

WatchRefs

...

Props

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

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

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.

Slots

in progress.

Binding

in progress.

Conditional elements

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>

Conditional attributes

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

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[].

Routing

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.

Directives

Stores

Caching