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. Variable is set to null on destroying.
>
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 return destroy function.
>
function draw(element) {
...
return () => { ... code for destroying ... };
}
>
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 />
You can use manual event delegation, modifier root
.
@click|root={click}>>
Block of template can be rendered by condition. There is shortcut {:elif cond}
for {:else if cond}
.
{#if condition}
One content
{:else if anotherCondition}
One more
{: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}
>
You can add section {:else}
to display block when list is empty.
{#each items as item}
>{item.name}>
{:else}
No items
{/each}
Examples:
{#each items as item}...{/each}
{#each items as item, index}...{/each}
{#each items as item, index (item.id)}...{/each}
{#each items as item, index (index)}...{/each}
{#each number as value}...{/each}
Destructuring:
{#each items as {id, name, key1, key2} }...{/each}
{#each items as [a, b]}...{/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 />
"Keep-alive" preserves DOM elements instead of removing them and attaches them back on condition. examples 1, examples 2, examples 3
{#keep key={key}}
/>
{/keep}
{#each list as it}
type="radio" name={value} value={it} />
{/each}
Binding select-input, examples: Simple usage, Array of strings, Array of objects
:value>
{#each list as it}
value={it}> {it.name}>
{/each}
>
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.
If you need to detect changes inside of an object (deep-checking), then you can use modifier "deep" or option "deepCheckingProps".
value={expression} {name} {...obj} />
value|deep={expression} />
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. Also you can export a function, it will be available in instance for parent component example
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 >
You can use <malina:self />
to bind a new instance of current component, example how to call itself recursively.
>
export const value = 5;
>
>
{value}
{#if value > 0}
value={value-1} />
{/if}
>
To mount an app to DOM you can use mount(DOMElement, applicationConstructor, options)
or more light version mountStatic
if you don't need an ability to unmount it (like root regular application).
import {mount} from 'malinajs';
import App from './App.xht';
mount(document.body, App);
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 built functions $onMount(fn)
, $onDestroy(fn)
. You can use $onDestroy
on different levels of code.
You can delay removing DOM on destroying if you return a promise, example with component, example with action, example + async/await
>
called_at_start();
$onMount(() => {...});
$onDestroy(() => {...});
>
#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';";