Scope

In akili, the properties of a component class and properties displayed in templates are separated. The scope of the component is responsible for displaying an information in the template.

class MyComponent extends Akili.Component {
  constructor(el, scope) {
    super(el, scope);
    scope.example = 'Example!';

    console.log(this.scope === scope); // true
  }
}
<my-component>${ this.example }</my-component>

So inside the element above, we'll see Example! instead of the expression, after compilation. As you can see scope in the component is the same object as this in the template.

The scope content

In the scope you can store any type of javascript variables, except objects with circular references. So you can also store functions in the scope and call them at any time.

class MyComponent extends Akili.Component {
  constructor(...args) {
    super(...args);

    this.scope.test = (str) => {
      return str + ' example';
    }
  }
}
<my-component>${ this.test('my') }</my-component>

Scopes hierarchy

The component structure of the framework has a hierarchical structure like DOM. So each scope is inherited from the parent scope. This happens through the prototype mechanism.

class ParentComponent extends Akili.Component {
  constructor(...args) {
    super(...args);

    this.scope.example = 'Example';
    this.scope.unique = 'unique!';
  }
}

class ChildComponent extends Akili.Component {
  constructor(...args) {
    super(...args);

    this.scope.example = 'ExampleTwo';
  }
}
<parent-component>
  <child-component>
    ${ this.example } is ${ this.unique }
  </child-component>
</parent-component>

Inside the element above, we'll see ExampleTwo is unique! But you also can get access to the parent component scope.

<parent-component>
  <child-component>
    ${ this.__parent.example } is ${ this.unique }
  </child-component>
</parent-component>

There will be Example is unique!

Proxy

Scope of the component is javascript Proxy object. By default all plain objects in the scope will be proxy too. If you want the nested objects to stay clean you should set Akili.options.nestedWatching = false before framework initialization. But in this case, in order for the changes to be synchronized with the template, you will have to overwrite the entire root object completely.

You can't use the same object in different scope variables. Every proxy object will create a copy of the target object and all nested objects.

class MyComponent extends Akili.Component {
  created() {
    let obj = {};

    this.scope.x = obj;
    this.scope.y = this.scope.x;

    console.log(this.scope.x === obj); // false;
    console.log(this.scope.y === obj); // false;    
    console.log(this.scope.x === this.scope.y); // false;
  }
}
If Akili.options.nestedWatching = true (it is default mode)
class MyComponent extends Akili.Component {
  constructor(...args) {
    super(...args);
    this.scope.example = ['ex', 'am', 'pl', 'e'];
  }

  compiled() {
    this.scope.example[0] = 's';
  }
}
<my-component>${ this.example.join('') }</my-component>

The expression result will be sample;

If Akili.options.nestedWatching = false
class MyComponent extends Akili.Component {
  constructor(...args) {
    super(...args);
    this.scope.example = ['ex', 'am', 'pl', 'e'];
  }

  compiled() {
    this.scope.example[0] = 's';
  }
}

The expression result will be example;

class MyComponent extends Akili.Component {
  constructor(...args) {
    super(...args);
    this.scope.example = ['ex', 'am', 'pl', 'e'];
  }

  compiled() {
    this.scope.example = ['s', ...scope.example.slice(1)];
  }
}

The expression result will be sample.

Also you can set this option for a specific component.

class MyComponent extends Akili.Component {
  static nestedWatching = false;
}

Parsing

By default, for parsing in the framework we use javascript eval function. You can change the parsing function by rewriting the Component.parse method. To parse some piece of code in html, you need to write an expression in the following form:

${ your javascript expression }

Such expressions can be written anywhere in your templates, including element attributes. if you want to return just an object don't forget the round brackets (for default eval parser):

<div data="${ ({key: 'value'}) }"></div>

Custom scope

If you need to set your own scope to the component you can use scope property.

import Scope from 'akili/src/scope';
import lodash from 'lodash';

class MyScope extends Scope {
  init() {
    this.example = 'Hello';
  }
}

class MyComponent extends Akili.Component {
  static scope = MyScope;

  created() {
    this.scope.init();
  }
}
<my-component>${ this.example }</my-component>

But you can extend the scope with some limitations. If you need to use some auxiliary variable in your functions, you have to start with two characters _ or property might be just an underscore. Otherwise, this variable, like any other in the scope, will be monitored.

import lodash from 'lodash';

class MyScope extends Akili.Scope {
  get _() {
    return lodash;
  }
}
<my-component>${ this._.toUpper(this.example) }</my-component>