Best practice

Component as a module

First of all, you can use our way for your components encapsulation.

import Akili from 'akili';
import Component from 'akili/src/component';
import router from 'akili/src/services/router';

class App extends Component {
  static define() {
    Akili.component('app', this);

    router.add('app', '/app', {
      title: 'My application',
      component: this
    }
  }

  compiled() {
    // do something
  }
}
import App from './app';

App.define();

document.addEventListener('DOMContentLoaded', () => {
  Akili.init().catch((err) => console.error(err));
});

This allows the component author to specify the default actions for his component: how to name it, what routing to add, etc. At the same time, the user who imports this component will be able to decide for himself whether to use the author settings or apply his own.

How to write your own library

Let your library consist of two components and a service.

import Component from 'akili/src/component';

// First component
export class MyComponent extends Component {
  static define() {
    Akili.component('my-component', this);
    Akili.component('my-component-part', this.MyComponentPart);
  }

  created() {
    this.el.addEventListener('click', this.setColor.bind(this));
  }

  setColor() {
    this.child(c => c instanceof MyComponentPart).el.style.color = MyService.getColor();
  }
}

// Second component
export class MyComponentPart extends Component {
  created() {
    this.scope.title = 'Hello world!'
  }
}

// Service
export const MyService = {
  color: 'blue',
  setColor(color) {
    this.color = color;
  },
  getColor() {
    return this.color;
  }
}

MyComponent.MyComponentPart = MyComponentPart;

Akili.defaults(() => {
  Akili.components.MyComponent = MyComponent;
  Akili.services.MyService = MyService;
});

The library using with building system:

import Akili from 'akili';
import { MyComponent, MyService } from 'my-component';

MyComponent.define();
MyService.setColor('red');

document.addEventListener('DOMContentLoaded', () => {
  Akili.init().catch((err) => console.error(err));
});
<my-component>
  <my-component-part>
    ${ this.title }
  </my-component-part>
</my-component>

The library using without building system:

Akili.components.MyComponent.define();
Akili.services.MyService.setColor('red');

document.addEventListener('DOMContentLoaded', () => {
  Akili.init().catch((err) => console.error(err));
});
<script src="akili.min.js">
<script src="my-component.js">
<my-component>
  <my-component-part>
    ${ this.title }
  </my-component-part>
</my-component>

Akili.defaults is a special function where you can define anything you need. More about the module system of the framework is here.

Encapsulation through attributes

It is best to pass data to the component through its attributes. This allows it to be independent of external conditions.

Not always good
class FirstComponent extends Akili.Component {
  created() {
    this.scope.title = 'Hello';
  }
}

class SecondComponent extends Akili.Component {}
<first-component>
  <second-component>
    ${ this.title + 'World' }
  </second-component>
</first-component>
Good
class FirstComponent extends Akili.Component {
  created() {
    this.scope.title = 'Hello';
  }
}

class SecondComponent extends Akili.Component {
  compiled() {
    this.attr('title', 'myTitle'); // link with the scope property
    this.attr('title', val => console.log(val)); // or handle it with the callback
  }
}
<first-component>
  <second-component title="${ this.title }">
    ${ this.myTitle }
  </second-component>
</first-component>

Of course, there might be situations when the parent and child components are interrelated. In this case, the first example is also suitable.

Expressions in templates

Refer to all nested properties of the object, checking first the existence of the object itself.

Not always good
<my-component>
  ${ this.data.user.id }
</my-component>
Good
<my-component>
  ${ this.data && this.data.user && this.data.user.id }
</my-component>

If there is no object, an error will be thrown in the first example.

Custom event names

Not Good
class MyComponent extends Akili.Component {
  static events = ['change', 'click'];
}
Good
class MyComponent extends Akili.Component {
  static events = ['my-change', 'my-click'];
}

Events change, click and some other are also system events. Besides they are bubbling. It means that sometimes we will get extra events, if some element inside it generates the same event.

class MyComponent extends Akili.Component {
  static events = ['change'];

  created() {
    this.scope.x = 0;
    this.scope.y = 0;
  }

  compiled() {
    this.attrs.onChange.trigger();
  }
}
<my-component on-change="${ this.y++ }">
  <input type="checkbox" on-change="${ this.x++ }"/>
</my-component>

If you click on the checkbox you will have x == 1, y == 2.