Quick reference for passing data from Rails to JavaScript via Unpoly compilers.
Haml Attribute Syntax
# Ising hash rockets and string symbols (method calls)
= form.text_field :name, 'date-picker': true
# Curly braces or brackets (elements) 
%div.container{ id: 'main', 'data-value': '123' }
Simple Values: data-* Attributes 
Use for: Scalar values (IDs, strings, booleans)
%span.user{ 'data-age': '18', 'data-first-name': 'Bob' }
up.compiler('.user', (element, data) => {
  console.log(data.age)        // "18" (always string!)
  console.log(data.firstName)  // "Bob" (camelCase from dash-case)
})
⚠️ Important: Data attributes are always strings. Use type conversion if needed.
Complex Data: up-data
 
Use for: Objects, arrays, nested data
Object Syntax
%span.user{ 'up-data': { name: 'Bob', age: 18 }.to_json }
up.compiler('.user', (element, data) => {
  console.log(data.name)  // "Bob"
  console.log(data.age)   // 18 (actual number)
})
Info
Unpoly accepts any format fulfilling relaxed JSON Show archive.org snapshot .
Array Syntax
.google-map{ 'up-data': [
  { lat: 48.36, lng: 10.99, title: 'Friedberg' },
  { lat: 48.75, lng: 11.45, title: 'Ingolstadt' }
].to_json }
up.compiler('.google-map', (element, pins) => {
  pins.forEach(pin => {
    console.log(pin.lat, pin.lng, pin.title)
  })
})
ES6 Destructuring
%table.calendar{ planner: true, 'up-data': { type: @angle, view: @view }.to_json }
up.compiler('[planner]', (element, { type, view }) => {
  // Destructure properties directly from second parameter
})
Real-world example:
%table.calendar{ 
  planner: true, 
  'up-data': { 
    type: @angle, 
    address: @address.to_h, 
    icons: icons 
  }.to_json 
}
up.compiler('[planner]', (container, { type, address, icons }) => {
  // You can now access any properties on address, e.g. address.street as Unpoly parses the JSON to an object
  window.Planner = new window.planners[type](address), icons)
})
Merging data-* and up-data
 
When both exist, they merge into a single object:
%span.user{ 'data-name': 'Bob', 'up-data': { age: 18 }.to_json }
up.compiler('.user', (element, data) => {
  console.log(data.name)  // "Bob" (from data-name)
  console.log(data.age)   // 18 (from up-data)
})
Event Handlers
Data is also passed to up.on() handlers as the third parameter:
%span.user{ 'up-data': { age: 18, name: 'Bob' }.to_json } Bob
up.on('click', '.user', (event, element, data) => {
  console.log(`${data.name} is ${data.age} years old`)
})
Arbitrary Attributes
Access any HTML attribute with standard methods or Unpoly helpers:
%span.user{ name: 'Bob', age: '18', active: 'true' }
up.compiler('.user', (element) => {
  element.getAttribute('name')               // "Bob"
  up.element.numberAttr(element, 'age')      // 18 (number)
  up.element.booleanAttr(element, 'active')  // true (boolean)
})
Available helpers:
- up.element.booleanAttr(element, attr)
- up.element.numberAttr(element, attr)
- up.element.jsonAttr(element, attr)
Quick Reference
| Use Case | Haml | JavaScript Access | 
|---|---|---|
| Simple string | { 'data-id': @id } | data.id(string) | 
| Multiple strings | { 'data-id': @id, 'data-name': @n } | data.id,data.name | 
| Dash-case attr | { 'data-first-name': 'Bob' } | data.firstName(camelCase) | 
| Complex object | { 'up-data': {...}.to_json } | (el, data) => | 
| Complex + destructure | { 'up-data': {...}.to_json } | (el, { key1, key2 }) => | 
| Type conversion | { 'count': '5' } | up.element.numberAttr(el, 'count') | 
Best Practices
✅ Do:
- Use data-*for simple scalar values
- Use up-datafor objects, arrays, and nested data
- Always call .to_jsonon Ruby objects forup-data
- Use ES6 destructuring: (el, { prop1, prop2 })
- Remember data-*values are always strings
- Use up.element.*Attr()helpers for type conversion
❌ Don't:
- Pass sensitive data (visible in browser source)
- Forget that data-*values are strings (not numbers/booleans)
- Use complex JSON in data-*attributes (useup-datainstead)
Posted by Felix Eschey to makandra dev (2025-10-17 10:01)