Unpoly: Passing Data to Compilers

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-data for objects, arrays, and nested data
  • Always call .to_json on Ruby objects for up-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 (use up-data instead)
Felix Eschey