Quick guide for frequently used compiler selector patterns of Unpoly.
1. BEM Component Pattern
When: Reusable UI components with multiple child elements
Examples: toggleable.js, collapsible.js, searchable_select.js
up.compiler('.toggleable', (toggleable) => {
const checkbox = toggleable.querySelector('.toggleable--checkbox')
const content = toggleable.querySelector('.toggleable--content')
// ...
})
%td.toggleable.-inverted.-ignore-when-not-empty
.toggleable--content
= form.text_field :something
= form.check_box :option, class: 'toggleable--checkbox'
Naming:
- Container:
.component-name - Children:
.component-name--element - Modifiers:
.-modifier
Pros: Clear ownership, reusable, no namespace conflicts, modular
Cons: Verbose HTML, requires BEM knowledge
Preferred for ViewComponent
For views components we mainly rely on this pattern.
2. Simple Class Pattern
When: One-off, view-specific behavior
Examples: project_role.js, create_invoice_from_suggestion.js
up.compiler('.project-role', (projectRole) => {
const deleteButton = projectRole.querySelector('#delete')
// ...
})
%tr.project_role
= form.text_field :description
%td= tag.a delete_icon, id: 'delete'
Pros: Simple, quick to implement, natural HTML
Cons: Namespace collisions, fragile, unclear ownership
3. Attribute Pattern
When: Form field enhancements, configuration-driven components, semantic for elements that do not rely on any classes for styling
Examples: date_picker.js, session_check.js, planner.js, tooltips.js
// Simple boolean flag
up.compiler('[date-picker]', (element) => {
const picker = flatpickr(element, { /* config */ })
return () => picker.destroy()
})
// With configuration data (via up-data)
up.compiler('[planner]', (container, { type, view, icons }) => {
window.Planner = new window.planners[type](JSON.parse(view), icons)
})
// Simple attribute
= form.text_field :start_date, 'date-picker' => true
// With configuration (see unpoly-data-passing.md for details)
%table.calendar{ planner: true, 'up-data': { type: @angle, view: @data }.to_json }
Pros: Semantic separation, configurable, HTML5 standard
Cons: Limited to single element, attribute clutter, no nesting convention
See Unpoly: Passing Data to Unpoly Compilers for detailed guide on passing configuration data
4. Generic Element Pattern
When: Universal enhancements for all elements of a type
Examples: form.js, input_type_number.js
up.compiler('form', (form) => {
// Runs on EVERY form
})
up.compiler('textarea', (textarea) => {
// Runs on EVERY textarea
})
Pros: No markup changes, consistent UX
Cons: Performance overhead, potential side effects, hard to customize