Default components

In the framework many components for standard elements and situations are implemented. Their implementation is in Akili.components object.

Loops

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

    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 } => ${ this.loopKey} => ${ 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 key.

  • scope.loopIndex - index of the current loop iteration
  • scope.loopKey - key of the current loop iteration. If loop main data is array then loopKey === loopIndex.
  • scope.loopValue - value of the current loop iteration

Component for is already integrated into many standard elements that require 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 has 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 } => ${ this.__parent.loopValue } => ${ this.__parent.__parent.loopValue }
        </loop>
      </div>
    </loop>
  </for>
</my-component>

If you do not specify loop component inside for it will be 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>

Conditional statements

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

    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 all that is inside the conditions is just hidden, display: none. If you need to remove hidden items from DOM use boolean attribute recreate on 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 attribute state. You can add attributes specifying the query. They correspond 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 url attribute. In this case it will be like router.location function.

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

So if you just want to follow the link without using routing, use the standard attribute href.

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

Html templates

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

Template from the url file will be inside include element.

Select

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

    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 attribute value.

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

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

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

If you change scope selectValue, the selected option will be the corresponding value of this variable. To get a change of the option 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. You can change option with value attribute changing, but when value is changed by user choosing, you will update scope value with the event.

Note also that we have written into the variable not event.target.value. Although in this example this option is also valid. The matter is that the select has the opportunity to be multiple. To be able to work with several selected options, we added a new property content, which 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 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 selected boolean attribute to make option as selected in some cases:

  • After the component compilation
  • When select in attribute data is changed

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"
/>

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

Radio group

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

    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 do not forget to put the input with type=radio inside radio.

Input

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

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

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

Or double binding equivalent for that:

<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>

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. If you use src, then before the compilation of the component you will make an unnecessary call with the expression instead of the necessary value. Because it is system attribute.