Default components

It is implemented a lot of components for the system elements. They are in the Akili.components.

Loops

class MyComponent extends Akili.Component {
  created() {
    this.scope.data = [];

    for (let i = 1; i <= 10; i++) {
      this.scope.data.push({ title: 'value' + i });
    }
  }
}
<my-component>
  <for in="${ this.data }">
    <loop>${ this.loopIndex } <br> ${ this.loopKey} <br> ${ this.loopValue.title }</loop>
  </for>
</my-component>

for component data might be an object or an array. In the example above, component loop will be created 10 times with its own scope for each instance. Every instance has loopValue which is the value of the current data item.

  • scope.loopIndex - index of the current item
  • scope.loopKey - key of the current item.
  • scope.loopValue - value of the current item

Component for is already integrated into many system elements that require the similar logic.

<my-component>
  <ul in="${ this.data }">
    <li>${ this.loopValue }</li>
  </ul>
</my-component>
<my-component>
  <ol in="${ this.data }">
    <li>${ this.loopValue }</li>
  </ol>
</my-component>
<my-component>
  <table>
    <thead in="${ this.data }">
      <tr in="${ [1, 2, 3] }" class="${ this.__parent.loopValue.title }">
        <th>${ this.loopValue }</th>
      </tr>
    </thead>
    <tbody in="${ this.data }">
      <tr in="${ [1, 2, 3] }">
        <td>${ this.loopValue }</td>
      </tr>
    </tbody>
    <tfoot in="${ this.data }">
      <tr in="${ [1, 2, 3] }">
        <td>${ this.loopValue }</td>
      </tr>
    </tfoot>
  </table>
</my-component>

In the last example you can see that some elements might simultaneously be for and loop components. For example, it is tr component. Component loop provides this feature.

<my-component>
  <for in="${ this.data }">      
    <loop in="${ [1, 2, 3] }">
      <div component="loop" in="${ [1, 2, 3, 4, 5, 6] }">
        <loop>
          ${ this.loopValue } <br> ${ this.__parent.loopValue } <br> ${ this.__parent.__parent.loopValue }
        </loop>
      </div>
    </loop>
  </for>
</my-component>

If you don't specify the loop component inside the for, it will happen automatically.

<my-component>
  <div component="for" in="${ this.data }">
    <div>${ this.loopValue }</div>
  </div>
</my-component>

However, in an unobvious situation you have to specify which element is an iterator.

<my-component>
  <ul in="${ this.data }">
    <li>Manual value at the start</li>
    <li component="loop">${ this.loopValue }</li>
    <li>Manual value at the end</li>
  </ul>
</my-component>

Large amounts of data can slow down your app. In this case, you can set the rendering by chunks.

<for in="${ this.data }" chunks="500">
  <loop>${ this.loopValue }</loop>
</for>

Compiling each element of the loop is an asynchronous operation. To be sure that the list is already compiled, you can use on-out event.

<for in="${ this.data }" on-out="${ console.log(event.detail) }">
  <loop>${ this.loopValue }</loop>
</for>

Conditional statements

class MyComponent extends Akili.Component {
  created() {
    this.scope.value = 0;
  }
}
<my-component>
  <if is="${ this.value === 0 }">0</if>
  <else-if is="${ this.value === 1 }">1</else-if>
  <else>2</else>
</my-component>

The components of conditional statements must be next to each other in obvious order. By default, everything inside the conditions is just hidden (display: none). If you need to remove hidden items from the DOM use boolean attribute recreate on the if component.

<my-component>
  <if recreate is="${ this.value === 0 }">0</if>
  <else>2</else>
</my-component>

Also you can use hidden attribute to hide some element.

<my-component>
  <div hidden="${ this.value !== 0 }">0</div>
</my-component>
Akili.services.router.add('app', '/app/:page', {
  templateUrl: '/templates/app.html'
});

class MyComponent extends Akili.Component {
  created() {
    this.scope.link = 'http://akilijs.com';
  }
}

The first way to go to the necessary route is click to a component with the attribute state. You can add attributes that is corresponded to the parameters of router.state function.

<my-component>
  <a state="app" params="${ {page: 'home'} }" query="${ {p: 1} }" hash="start">go to app</a>
  <route></route>
</my-component>

The second way is using the url attribute. In this case, it is like the router.location function.

<my-component>
  <a url="/app/home?p=1#start" options="${ {reload: true} }">go to the app</a>
  <route></route>
</my-component>

And if you just want to follow the link without using routing use the system attribute href.

<my-component>
  <a href="${ this.link }">go</a>
  <route></route>
</my-component>

To check the link status there are two properties:

  • isActiveState - true if the current state is the same as the link state.
  • inActiveState - true if the link state is the part of the current state.

<a state="app" class="${ utils.class({active: this.isActiveState}) }">go to app</a>

Html templates

<include
  url="/templates/app.html"
  on-load="${ // loaded }"
  on-error="${ // error }"
  cache="500"
></include>

The url template will be inside the include element after the loading. We also enable the cache here.

<include html="html <b>text</b> ${ this.varHtmlString }"></include>

Or you can append an html piece inside of include element, passing the value in html attribute. You can't use url and html attributes together.

<include inject="${ 'my-input' }">
  <input type="text"/>
</include>

Also, include element with inject attribute allows you to inject any component by dynamic name. We will get after:

<include inject="my-input">
  <input component="my-input" type="text" />
</include>

As you remember, component attribute is necessary to load a component ignoring html tag of the element. But it is a static attribute, sometimes you need to use include with inject to make it dynamic.

Select

class MyComponent extends Akili.Component {
  created() {
    this.scope.data = [];

    for (let i = 1; i <= 10; i++) {
      this.scope.data.push({ title: 'value' + i });
    }
  }
}
<my-component>
  <select in="${ this.data }">
    <option value="${ this.loopValue.title }">${ this.loopValue.title }</option>
  </select>
</my-component>

If the option value is the same as its content, you can skip the attribute value.

<my-component>
  <select in="${ this.data }">
    <option>${ this.loopValue.title }</option>
  </select>
</my-component>

To change the selected option in any time use the attribute value for the select.

<my-component>
  <select in="${ this.data }" value="${ this.selectValue }">
    <option>${ this.loopValue.title }</option>
  </select>
</my-component>

If we change scope selectValue the selected option will be the corresponding. To get the changes use on-change event.

<my-component>
  <select
    in="${ this.data }"
    value="${ this.selectValue }"
    on-change="${ this.selectValue = event.target.content }"
  >
    <option>${ this.loopValue.title }</option>
  </select>
</my-component>

This is a kind of replacement for double binding.

Note that we used event.target.content instead of event.target.value. The matter is that the select might be a multiple. To be able to work with several selected options we added a new property content to the element that can be an array.

<my-component>
  <select
    in="${ this.data }"
    on-change="${ console.log(event.target.content) // 'value1' }"
  >
    <option>${ this.loopValue.title }</option>
  </select>
</my-component>
<my-component>
  <select
    in="${ this.data }"
    on-change="${ console.log(event.target.value) // 'value1' }"
  >
    <option>${ this.loopValue.title }</option>
  </select>
</my-component>
<my-component>
  <select
    multiple
    in="${ this.data }"
    on-change="${ console.log(event.target.content) // ['value1'] }"
  >
    <option>${ this.loopValue.title }</option>
  </select>
</my-component>
<my-component>
  <select
    multiple
    in="${ this.data }"
    on-change="${ console.log(event.target.value) // 'value1' }"
  >
    <option>${ this.loopValue.title }</option>
  </select>
</my-component>

Attribute multiple is a boolean attribute.

<my-component>
  <select
    in="${ this.data }"
    on-change="${ console.log(event.target.content) // 'value3' }"
  >
    <option selected="${ this.loopKey == 2 }">${ this.loopValue.title }</option>
  </select>
</my-component>

You also can use the selected boolean attribute to make option selected in some cases:

  • After the component compilation
  • After the in change

Checkbox and radio

<input
  type="checkbox"
  checked="${ this.isChecked }"
  on-change="${ this.isChecked = event.target.checked }"
  value="1"
/>
<input
  type="radio"
  checked="${ this.isChecked }"
  on-change="${ this.isChecked = event.target.checked }"
  value="1"
/>

The attribute checked is a boolean. If we change the scope property isChecked to true, the checkbox will be checked, otherwise contrary.

Radio group

class MyComponent extends Akili.Component {
  created() {
    this.scope.data = [];

    for (let i = 1; i <= 10; i++) {
      this.scope.data.push({ title: 'value' + i });
    }
  }
}
<radio 
  name="radio-group"
  in="${ this.data }"
  value="${ this.radioValue }"
  on-radio="${ this.radioValue = event.detail }"
>
  <radio-button value="${ this.loopValue.title }">${ this.loopValue.title }</radio-button>
</radio>

If no option is selected, then the scope radioValue will be null. If you need a more detailed setting of the radio buttons you can write it differently:

<radio
  in="${ this.data }"
  name="radio-group"
>
  <input type="radio" value="${ this.loopValue.title }" />
</radio>
<radio
  in="${ this.data }"
  name="radio-group"
>
  <label>
    <input type="radio" value="${ this.loopValue.title }" />
    ${ this.loopValue.title}
  </label>
</radio>

In this cases don't forget to put the input with type=radio inside the radio.

Input

<input
  value="${ this.value }"
  on-input="${ this.value = event.target.value }"
/>

If you use the input value to filter something you can optimize the process via the debounce event.

<input
  value="${ this.value }"
  on-debounce="${ this.value = event.target.value }"
/>

It creates a delay and make the event to occur some time after the last keystroke. By default, it is 500 ms. You can change the value by passing debounce attribute.

<input
  value="${ this.value }"
  debounce="300"
  on-debounce="${ this.value = event.target.value }"
/>

Also you can use focus attribute here to set or remove the focus from the element. All the text fields have this feature.

<input focus="${ true }"/>

Or double binding equivalent:

<input
  focus="${ this.focus }"
  on-focus="${ this.focus = true }"
  on-blur="${ this.focus = false }"
/>

Textarea

<textarea
  value="${ this.value }"
  on-input="${ this.value = event.target.value }"
></textarea>
<textarea
  value="${ this.value }"
  on-debounce="${ this.value = event.target.value }"
></textarea>

Contenteditable

<content
  editable="${ true }"
  value="${ this.value }"
  on-input="${ this.value = event.target.innerHTML }"
></content>
<div
  contenteditable="${ true }"
  value="${ this.value }"
  on-input="${ this.value = event.target.innerHTML }"
></div>

Image, Iframe, Embed, Audio, Video, Track, Source, Object

<img url="${ this.url }"/>
<iframe url="${ this.url }"></iframe>
<embed url="${ this.url }"></embed>
<audio url="${ this.url }"></audio>
<video url="${ this.url }"></video>
<track url="${ this.url }"></track>
<source url="${ this.url }"></source>
<object url="${ this.url }"></object>

All this components have attribute url to set src (or variations, depends on the element). If you are going to fill src attribute then before the compilation you will make the browser to do unnecessary request with the expression instead of the necessary value.

You can use hidden-error attribute to hide the image instead of fail icon showing.

<img url="${ this.url }" hidden-error="true"/>

To prevent loading if the element is not in the viewport, use loading as viewport. This value unlike the standard lazy will cancel requests that have already started, but the user took it out of the viewport.

<img url="${ this.url }" loading="viewport"/>

To load resources by chunks, use loading as chunk. You can specify chunk-name and chunk-size to set the group name and the size of one chunk. By default, the name is main and the size is 1. Group name is necessary to separate different type of resources if you want to control them separately.

<img url="${ this.url }" loading="chunk" chunk-name="cats" chunk-size="3" />

Also you can specify urlset to change srcset and so on.

<img urlset="${ this.url }" />

The listed features apply not only to images, but to all types of elements above to one degree or another. Some features may not be supported by some browsers because they are non-standard / experimental.