app declaration
include the file! either download the file from
here
and include in your own assets, or source it from
https://dragonwocky.me/sear/sear.min.js
. To
access a specific version, use
https://dragonwocky.me/sear/dist/sear.<version-number>.js`
(replacing <version-number>
with e.g.
0.5.5
).
<script src="https://dragonwocky.me/sear/sear.min.js"></script>
initialise.
const app = new Sear({
/*
* whatever element you want to restrict this to
* [type] single ID as a string
* [default] document.body
*/
el: '#app',
format: {
/*
* persist app data (see below: client) to localStorage under this name
* [default] none (doesn't persist)
*/
name: 'Sear/Demo',
/*
* version of the data structure
* [type] string or number
* [default] none (won't check)
*/
version: 1,
/*
* run this handler if version (above) doesn't match
* with the persisted data structure version
* [args] old (outdated data found in localStorage), version (of outdated data)
* [default] return {}; (starts afresh)
*/
handler(old, version) {
return {};
}
},
/*
* any reactive data properties (inc. computed properties)
*/
// data will return to defaults on page reload
data: {
example: 0,
// client data will be persisted to and fetched from localStorage
// -> only works if format.name is defined
// -> (otherwise will act like normal data)
client: {}
},
// any functions watching for changes to data
watch: {}
});
// returns the following
{ example: 0, client: {} };
properties
basic properties can be objects, arrays, dates, strings, numbers, and boolean values.
data.client: {
selected: 'purple',
icecream: {
flavours: ['berry', 'watermelon', '<b>tiramisu<b>']
}
}
these can be updated like any normal object property, or added in later.
app['client'].user.selected = 'green';
app['client'].icecream.new = '';
app['client'].paragraph = `this is <b>bold</b>.
this is <i>italicised</i>.
but i? i am <s>struck out</s>.`;
computed properties are functions that can return a dynamic value and can
access fellow properties via
this
. these must not be declared as arrow
functions! (arrow functions break persistence and accessing
this
)
to access a computed value, useapp['client'].value
rather thanapp['client'].value()
.
data: {
redselected() {
return this['client'].selected === 'red';
},
colour () {
return 'color: ' + this['client'].selected;
}
}
watchers
a watcher function will be evaluated whenever its corresponding data prop is changed. an argument is passed to it with the previous value of that data.
due to the way the observation system works, watchers for properties within objects are declared by'parent.child'(prev) {}
rather thanparent: { child(prev) {} }
. this allows for watching objects and their properties separately.
watch: {
'client.selected'(prev) {
console.log(
`[watcher] selected colour has changed from ${prev} to ${this['client'].selected}`
);
},
'client.icecream.flavours'(prev) {
const flavours = [...this['client'].icecream.flavours]
.filter(flavour => {
if (prev.includes(flavour)) {
prev.splice(prev.indexOf(flavour), 1);
return false;
}
return true;
});
if (flavours.length) {
console.log(`[watcher] flavour(s) added: <<${flavours.join('>> <<')}>>`);
} else if (prev.length)
console.log(`[watcher] flavour(s) removed: <<${prev.join('>> <<')}>>`);
}
}
:html
sync the contents of the bound element to a data property. contents will be displayed as HTML.
not recommended (unless for e.g. displaying parsed markdown). dynamically rendering untrusted or user-input content could lead to XSS attacks (or other unintended consequences).
<p :html="client.paragraph"><p>
:text
sync the contents of the bound element to a data property. contents will be displayed as plain text.
<p :text="client.paragraph"><p>
{{ moustache }}
text binding can also be done via
{{ moustache }}
tags.
use only in text nodes. use in attributes etc. will make weird things happen.
<p>{{ client.paragraph }}<p>
{{ client.paragraph }}
:value
sync the value of the bound input to a data property. works for any input (e.g. text, checkbox, select, you name it).
use only in text nodes. use in attributes etc. will make weird things happen.
<textarea :value="client.paragraph"></textarea>
<select :value="client.selected">
<option value="red">red</option>
<option value="blue">blue</option>
<option value="green">green</option>
<option value="purple">purple</option>
</select>
unbound
use if you want something to be initially populated with a value, but not reactively updated.
<p>
<b>initial paragraph (as of page load):</b>
<span :text="client.paragraph" :unbound></span>
</p>
initial paragraph (as of page load):
:pre
use if you want to preserve everything within that tag - content will not be modified/updated in any way.
<p :pre>these {{ tags }} will <span :text="client.paragraph">not</span> be parsed!</p>
these {{ tags }} will not be parsed!
:each
repeat an element for each value of an array. assign this attribute to a container, and the first child will be the one repeated within the container.
to access the relevant index/id (starting from 1) use
[[:each:id]]
and to access the current
value use [[:each:value]]
. if the value
should be parsed as html (e.g. for displaying markdown), use
[[:each:value:html]]
.
<div :each="client.icecream.flavours">
<p><i>[[:each:id]]</i>. [[:each:value]]</p>
<button onclick="app['client'].icecream.flavours.splice([[:each:id]] - 1, 1)">
delete
</button>
</div>
icecream flavours
[[:each:value]]
[[:each:id]]. [[:each:value]]
[[:each:value:html]]
[[:each:id]]. [[:each:value:html]]
yes, the example above uses more html than in the snippet. this is for css reasons, it doesn't change anything functionality-wise. also, for the "add" button/input.if (app['client'].icecream.new) { app['client'].icecream.flavours = [app['client'].icecream.new, ...app['client'].icecream.flavours]; app['client'].icecream.new = ''; }
:if / :else
with :if
, the element will only be present
on the page if the relevant prop has a truthy value.
with :else
, the element will only be
present on the page if the relevant prop has a falsy value.
<p>
<b>is red selected?</b>
<span :if="redselected">yes it is</span>
<span :else="redselected">no it isn't</span>
</p>
uses the<select>
from the:value
example above.
is red selected? yes it is no it isn't
:bind:attr
use to reactively update/bind any attribute value.
values are separated by spaces, written as
condition=response
. if there is no
response
, it will be assumed to be the
value of condition
.
if the attribute is boolean, then a truthy
condition
will result in the presence of
the attribute. a falsy condition
will
result in the removal of the attribute. to inverse this, do
condition=false
.
this is only 1 way (it does not sync). for thechecked
attribute of checkboxes, use:value
instead.
<p>
<input type="checkbox" :bind:disabled="redselected=false" disabled />
<span>(will be disabled if red is not selected)</span>
</p>
<p :bind:style="colour">i'm {{ client.selected }}<
uses the<select>
from the:value
example above.
(will be disabled if red is not selected)
i'm {{ client.selected }}