You can bind JavaScript expression in html-template.
>
{name}
{getValue('title')}
{a} + {b} = {a + b}
{'text with "quotes"' + `123`}
{`js template ${variable}`}
>
You can get reference to element/component using #ref
and save it to a variable.
>
let ref;
let component;
>
#ref />
#component />
For binding to attributes and properties you can write attribute={value}
, if attribute and variable names the same you can use shortcut {attribute}
, for binding you can use JavaScript expression.
value="{value}" />
value={value} />
{value} />
disabled={!clickable}>Click >
Also you can spread an object to attributes.
>
let props = {
value: 'Hello',
title: 'Some title'
};
>
{...props} />
For 2-way binding you can use bind:value={value}
or shortcut :value={value}
, when property name the same as variable name you can omit it, e.g. :value
>
let name = '';
let checked = false;
>
type="text" :value={name} />
type="checkbox" :checked />
To set class and style you can use class:className={condition}
, style:styleName={value}
.
class:blue={value} class:red={!value}>by class name>
class="{value?'red':'blue'}">attribute class >
style="color: {value?'green':'red'};">style as string >
style:color={color}
style:border-color="red"
style:border="1px solid {color}">
Style as prop/template
>
>
.blue {};
.red {};
>
You can run some JavaScript code on element, use:action={param}
or shortcut *action={param}
, without arguments it looks like *action
.
Also you can run some JavaScript code where $element
is available - *{$element.focus()}
.
>
function draw(element) {
...
}
>
*draw>>
*{$element.focus()} />
If you need more control you can pass arguments and subscribe for updates and destroy event.
>
function draw(element, a, b) {
...
return {
update: (a, b) => {},
destroy: () => {}
}
}
>
*draw={a, b}>>
You can insert casual JS in template, a current DOM-node is available as $element
.
example
{* const name = 'random code' }
{* $element.style.color = 'red' }>
{name}
>
To listen events you can use on:eventName={expression}
or shortcut @eventName={expression}
.
For expression you can use function name @click={handler}
or @click:handler
, function declaration @click={(e) => handler(e)}
or js-expression @click={handler($event)}
.
>
const click = (event) => {};
>
@click>Click>
@click:click>Click>
@click={click}>Click>
@click={(e) => click(e)}>Click>
@click={click($event, $element)}>Click>
Also you can use modificators for elements: preventDefault
or prevent
, stopPropagation
or stop
for keyboard events: enter
, tab
, esc
, space
, up
, down
, left
, right
, delete
,
meta keys: ctrl
, alt
, shift
, meta
type="text" @keyup|enter:handler />
Also you can forward events to a parent component, @@eventName
to forward one type of event, @@
to forward all events from current element, the same works for components.
@@click>Click>
type="text" @@ />
@@click />
Block of template can be rendered by condition.
{#if condition}
One content
{:else}
Another content
{/if}
Iterating over list, you can pass a key which lets to associate item with DOM element, by default key is chosen depends on content. example shows difference depends of used key.
>
{#each items as item}
>{item.name}>
{/each}
>
Examples:
{#each items as item}...{/each}
{#each items as item, index}...{/each}
{#each items as {id, name, key1, key2} }...{/each}
{#each items as item, index (item.id)}...{/each}
{#each items as item, index (index)}...{/each}
{#each number as value}...{/each}
You can declare some fragment to reuse it a few times other places. Also it can be declared anywhere, e.g. inside of the #each
, so it will have all scoped variables.
example
>
{#fragment:button text}
class="col-sm-6 smallpad">
@@click class="btn">{text}>
>
{/fragment}
text='Left' @click:left />
text='Right' @click:right />
>
>
let tree = [{
name: 'OS',
children: [{
name: 'Linux',
children: [{name: 'Ubuntu'}, {name: 'Debian'}, {name: 'RedHat'}]
}, {
name: 'MacOS X'
}]
}];
const click = (name) => {console.log(name)};
>
list={tree}>
{#fragment:draw list}
>
{#each list as it}
@click|stopPropagation={click(it.name)}>
{it.name}
{#if it.children}
list={it.children} />
{/if}
>
{/each}
>
{/fragment}
Also you can pass slot to fragment, example
{#fragment:field}
class="field">
/>
>
{/fragment}
>
>Click>
>
>
type="text" />
>
You can await a promise to display placeholder etc.
>
{#await promise}
Loading...
{:then value}
Data: {value}
{:catch error}
Loading error: {error}
{/await}
>
Possible options:
{#await expression}...{:then name}...{:catch name}...{/await}
{#await expression}...{:then name}...{/await}
{#await expression then name}...{/await}
To render some HTML, you can use {@html expression}
.
>
{@html post.content}
>
Portal lets you render a template outside of component, argument mount
== 'document.body' by default.
example
mount={document.body}>
class="popup">
>Popup>
>Some text>
>
>
These special elements lets you handle head/body/window: example
>
>Set title>
rel="stylesheet" href="theme.css" />
>
@keydown|esc:escape />
@scroll />
A component can have script block, style block and rest content becomes a template
>
...
>
... content ...
>
...
>
You can import a component import Component from './Component.html'
, and use it in template as <Component />
, a component should start with capital letter.
>
import Component from './Component.html';
>
/>
You can pass some arguments into a component, it's a 1-way binding.
Also you can spread an object to pass all values as arguments.
value={expression} {name} {...obj} />
You can use keyword export
to mark a variable as property, so it will receive a value from parent component and will be updated on changes.
A parent can pass more arguments than number of props in a component, so all arguments will be available in $props
, and all arguments which are not property are in $attributes
.
>
export let name = 'default';
$props
$attributes
>
{...$props} />
{...$attributes} />
Syntax for 2-way binding is the same as for elements.
:value={variable} />
It's possible to listen an event, forward an event and forward all events. Syntax for events is the same as for elements.
@click:handler />
Also you can emit an custom event, for this you can use a built function $emit(eventName, details)
.
>
$emit('myevent', 'info');
>
You can pass slots to a child component. To pass a default slot:
>
Some content
>
<!-- Child.html -->
>No content>
To pass named slots:
>
{#slot:title}
Some title
{/slot}
Some content
{#slot:footer}
Some footer
{/slot}
>
<!-- Child.html -->
/>
>No content>
>No title>
>No title> or >No title>
A child component can pass a property to parent slot:
>
{#slot prop}
Some content {prop.value} {parentVar}
{/slot}
>
<!-- Child.html -->
{#each items as item}
prop={item}>No content {childVar}>
{/each}
You can call a fragment from child component, it looks like inverted slot, example
<!-- Child -->
{#fragment:title export}
class="header"> {name}>
{/fragment}
<!-- App -->
>
>Header</>
>
It lets you pass controls (classes, events, actions etc) without template to child component. example
<!-- App -->
>
<^root class="border" />
<^name style="font-weight: bold; color: red" />
<^input type="text" *action :value />
>
<!-- Child -->
>
^root>Name ^name>
/>
^input >
Dynamic component let you to attach a component from variable/expression.
>
import Music from './Music.xht';
import Movie from './Movie.xht';
let comp, x;
function toggle() {
comp = comp == Music ? Movie : Music;
x ^= 1;
}
>
/>
{x ? Music : Movie} />
@click:toggle>Switch>
You can pass classes into child components using property class
, to be able to use it in child class you have to place such classes in external
block.
example,
example2
Syntax: class:childClassName="parentClass globalClass"
In the example, classes red italic
is passed as class font
in child component. If class is not passed default styles will be used.
You can pass scoped classes, global classes and use expressions, e.g. class:font="italic {color}"
<!-- App -->
class:font="red italic" />
<!-- Child -->
class="font">Child >
external>
.font {color: blue;} /* style by default */
>
Default class name for child component is main
, and you can define it.
.button :global(.title)
)<!-- App -->
class="red italic" />
<!-- Child -->
class="main">Child >
external main="main">
.main {color: blue;} /* style by default */
>
If class name starts with $
it's marked as external
example
<!-- App -->
class="red italic" />
<!-- Child -->
class="$main">Child >
If you have an instance of component, you can read/write properties directly, a component should have attribute property
.
<!-- Component -->
property>
export let value = 0;
>
<!-- App -->
>
import Component from './Component.html';
let comp;
function increment() {
comp.value++;
}
>
#comp />
If you need to perform some code after mounting or during destroying a component, you can use declare functions onMount
, onDestroy
, or use a built function $onMount(fn)
, $onDestroy(fn)
.
>
called_at_start();
function onMount() {
// called after mounting
$onDestroy(() => {...}); // subscribe on destroying
$onDestroy(() => {...});
}
function onDestroy() {
// called during destroying
}
>
#comp />
In some cases you can turn off change detection, e.g. if a component doesn't expect changes, like component "Icon":
read-only
on script tag, change-detector will be off for this component.export const name
instead export let name
, then some runtime will be omitted for such property.!no-check
in comment tells compiler to skips such js-block for "detections".read-only>
export const value = '';
function foo() {
// !no-check
... some not detected code ...
}
>
Malina.js makes all styles are scoped for a component, it appends a prefix class to styles and elements from template based on selector, so only required elements are marked.
Using keyword :global()
you make certain style as global.
>
span {
/* all <span> elements, only from
current component will be affected */
}
:global(body) {
/* effects on <body> */
}
div :global(span) {
/* effects on all <span> elements
(including child components)
inside of <div> from current component */
}
>
Also you can mark whole style-block as global
global>
span { /* ... */ }
>
Using syntax $:
you can observe changes.
Computed value: $: value = a + b
, value
will be changed when an expression gives another result.
To observe changes in expression and call handler: $: exp, handler()
.
It can contain a few expressions: $: exp1, exp2, a+b, handler()
.
Handler also can be link to function or statement: $: exp, (newValue) => console.log(newValue);
or
$: exp, console.log(exp)
or $: exp, handler
>
let name = '';
$: header = name.toUpperCase();
$: name, () => console.log(name);
$: a + b, onChangeSum
>
type="text" :value={name} />
An arbitrary context object of a component, a content is available in child components. For external libs it must be imported from "malinajs" and used during component initialisation. example
// App.xht
$context.value = 'Test';
// Child.xht
let value = $context.value;
// lib.js
import { $context } from 'malinajs';
let value = $context.value;
Errors which can't be caught by try-catch is handled by Malina.js and displayed to console, but you can override it. example
import {configure} from "malinajs/runtime.js";
configure({
onerror(e) {console.error(e)}
}
You can override config for any folder, you can save malina.config.js
in target folder. It lets you use different config/plugins for different folders/libs.
const sassPlugin = require('../plugins/sass.js');
module.exports = function(option, filename) {
option.passClass = false;
option.immutable = true;
option.plugins = [sassPlugin()];
option.autoimport = name => `import ${name} from './${name}.xht';`;
return option;
}
(name) => "import ${name} from './${name}.xht';";