Unpoly lets you attach structured data to an element, to be consumed by a compiler or event handler.
You may use HTML5 [data-*] attributes
to attach simple string values:
<span class='user' data-age='18' data-name='Bob'>Bob</span>
An object with all data attributes will be passed to your compilers as a second argument:
up.compiler('.user', function(element, data) { // mark: data
console.log(data.age) // result: 18
console.log(data.name) // result: "Bob"
})
Important
Data attributes always have string values. In the example above
data.ageis a string.
Data attributes with multiple, dash-separated words in their name can be accessed with camelCase keys:
<span class='user' data-first-name='Alice' data-last-name='Anderson'>Alice</span>
up.compiler('.user', function(element, data) {
console.log(data.firstName) // result: "Alice"
console.log(data.lastName) // result: "Anderson"
})
[up-data]
HTML5 data attributes cannot express structured data, like an array or object. Also their values are always strings.
For a more powerful alternative you can set the [up-data] attribute to any
relaxed JSON value:
<div class="google-map" up-data="{ pins: [
{ lat: 48.36, lng: 10.99, title: 'Friedberg' },
{ lat: 48.75, lng: 11.45, title: 'Ingolstadt' }
] }"></div>
The JSON will be parsed and passed to your compiler function as a second argument:
up.compiler('.google-map', function(element, pins) {
var map = new google.maps.Map(element)
for (let pin of data.pins) {
var position = new google.maps.LatLng(pin.lat, pin.lng)
new google.maps.Marker({ position, map, title: pin.title })
}
})
Note how [up-data] allows us to a attach a multitude of value types, like arrays (pins), objects (pin) and numbers (ping.lat).
The topmost expression may be any JSON-serializable value, like an object:
<span class="user" up-data="{ name: 'Bob', age: 18 }">Bob</span>
up.compiler('.user', function(element, data) {
console.log(data.name) // result: "Bob"
console.log(data.age) // result: 18
})
If [up-data] is a JSON object, any HTML5 data attributes will be merged into the parsed value:
<span class="user" data-name="Bob" up-data="{ age: 18 }">Bob</span>
up.compiler('.user', function(element, data) {
console.log(data.name) // result: "Bob"
console.log(data.age) // result: 18
})
Your compilers and event handlers may access any HTML attribute
via the standard Element#getAttribute()
method.
Unpoly provides convenience functions to read an element attribute and cast it to a particular type:
up.element.booleanAttr(element, attr)up.element.numberAttr(element, attr)up.element.jsonAttr(element, attr)Here is an example where we use arbitrary HTML attributes to attach data to our element:
<span class='user' name='Bob' age='18'>Bob</span>
up.compiler('.user', function(element) {
console.log(element.getAttribute('name')) // result: "Bob"
console.log(up.element.numberAttr(element, 'age')) // result: 18
})
Any attached data will also be passed to event handler registered with up.on().
For instance, this element has attached data in its [up-data] attribute:
<span class="user" up-data="{ age: 18, name: 'Bob' }">Bob</span>
The data will be passed to your event handler as a third argument:
up.on('click', '.user', function(event, element, data) {
console.log("This is %o who is %o years old", data.name, data.age)
})
Use up.data(element) to retrieve an object with the given element's data:
up.data('.user') // result: { age: 18, name: 'Bob' }
When rendering a single fragment, you can override data keys
from the server HTML. For this use an [up-use-data] attribute or { data } option.
The new fragment will compile with the given data, without requiring an [up-data] attribute in the HTML:
<a href="/score" up-target="#score" up-use-data="{ startScore: 1500 }"> <!-- mark: up-use-data="{ startScore: 1500 }" -->
Load score
</a>
<div id="score">
<!-- chip: Will compile with data { startScore: 1500 } -->
</div>
Use an [up-use-data-map] attribute or { dataMap } option to map selectors to data objects.
When a selector matches any element within an updated fragment, the matching element is compiled with the mapped data:
<a
href="/score"
up-target="#stats"
up-use-data-map="{ '#score': { startScore: 1500 }, '#message': { max: 3 } }"> <!-- mark: up-use-data-map="{ '#score': { startScore: 1500 }, '#message': { max: 3 } }" -->
Load score
</a>
<div id="stats">
<div id="score">
<!-- chip: Will compile with data { startScore: 1500 } -->
</div>
<div id="message">
<!-- chip: Will compile with data { max: 3 } -->
</div>
</div>
You can also map data objects when updating multiple fragments using a comma-separated target:
<a
href="/score"
up-target="#score, #message"
up-use-data-map="{ '#score': { startScore: 1500 }, '#message': { max: 3 } }">
Load score
</a>
When reloading or validating an element,
you may keep an existing data object by passing it as a { data } option.
In the example below, data.counter is increased by 1 for every compiler pass,
regardless of what the server renders into [up-data]:
up.compiler('.element', function(element, data) {
data.counter ??= 1 // set initial state
console.log('Counter is', data.counter) // logs 1, 2, 3, ...
data.counter++
element.addEventListener('click', function() {
up.reload(element, { data })
})
})
As a shortcut may also pass { keepData: true } when reloading.
To keep an entire element, you may also use [up-keep].
The up:fragment:keep event lets you inspect the old and new element
with its old and new data. You may then decide whether to keep the existing element,
swap it with the new version, or just update its data.