Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.elasticfunnels.io/llms.txt

Use this file to discover all available pages before exploring further.

This page covers limitations of the frontend template engine (compared to full frameworks like Vue or Alpine) and common pitfalls with fixes.

Quick reference: common pitfalls

SymptomCauseFix
Wrong or double currencyUsing formatPrice on already-formatted valuesUse [[ checkout.total ]], [[ item.price ]] as-is; see Functions
{{ x }} not replacedFrontend engine uses [[ ]] onlyUse [[ expression ]] for frontend; {{ }} is backend
UI doesn’t update after changing dataPath/expression mismatch or data shape issueVerify the changed efScope path matches your binding expression and item/object shape
Single-product block wrong/missingUsing cartItems[0]Use checkout.product_title, checkout.product_image, etc.; see Checkout
Flash of raw [[ ... ]]Content visible before engine runsAdd data-ef-cloak to elements; use CSS to hide [data-ef-cloak]
template-else not workingElse not a sibling of template-ifPut template-else / template-else-if as immediate siblings after template-if
Checkout validation not showingMissing error container or wrong idUse data-checkout-error=“<id>” and data-template-value per Checkout
Bump toggle/style not workingMissing data attributesUse data-bump-code, data-bump-checkbox, data-bump-toggle; see Cart
Select options missing on Safari/iOStemplate-foreach inside selectUse inline data-each on option directly; see Syntax — Inline data-each

Limitations

Not a full framework

  • No virtual DOM — Updates are reactive and path-scoped, but this is not a component framework with diffed virtual DOM.
  • Element-level lifecycle only — You have @mounted, @unmounted, and @visible per element (see Bindings & Events), plus window.ef.template.watch(...) and window.ef.template.computed(...) for scope-driven reactions. There is no full component composition API (no slots, no scoped components, no props).
  • No standalone template-else — A <template-else> must be a sibling of a <template-if> (or follow template-else-if). You cannot have “else only” without an if.

Reactivity model

  • window.efScope is reactive — changing nested keys/arrays updates bound UI automatically.
  • Path-scoped updates still exist — internals and advanced callers can use path-scoped updates for high-frequency flows; most custom code does not need manual notify calls.

Expression evaluation

  • Expressions run in a merged context (scope + loop vars) using with(scope) { return (expression); }. Very complex expressions or ones that rely on strict mode / undeclared variables can be fragile. Prefer simple paths and small expressions.
  • Reserved names in event handlers: $event, event, $el, $target are injected; avoid using them as scope keys if you need to read them from global scope.

Checkout and cart data

  • checkout is only set on checkout pages. Any template that uses checkout.* on a non-checkout page may see undefined. Guard with <template-if data-condition="checkout"> in shared components.
  • query (URL params) is only available if the app sets efScope.query at init (e.g. via syncQueryToScope()). Conditions like query.coupon will not work otherwise.

Common pitfalls

1. Using formatPrice on already-formatted values

Mistake: [[ formatPrice(checkout.total, checkout.currency) ]] or [[ formatPrice(item.price, item.currency) ]]. Why it’s wrong: checkout.total, item.price, item.total, bump.price, line.price are already formatted currency strings. Passing them to formatPrice can produce wrong or duplicated symbols. Fix: Use [[ checkout.total ]], [[ item.price ]], [[ item.total ]], [[ bump.price ]], [[ line.price ]] as-is. Use formatPrice only for raw values (e.g. checkout.total_raw, bump.price_raw) when you need to display a number.

2. Using {{ }} in frontend templates

Mistake: Writing {{ variable }} in HTML that is processed by the frontend engine. Why it’s wrong: {{ }} is reserved for the backend Template Engine. The frontend engine only replaces [[ ]]. Fix: Use [[ expression ]] for frontend token interpolation. For attribute bindings, prefer ef-*/data-ef-* (e.g. ef-text="checkout.total").
Mistake: Changing one path but binding to another (for example mutating checkout.items while template reads cartItems, or expecting item.quantity aliases outside foreach context).Why it breaks: Reactivity runs, but your binding expression may not reference the changed path or expected data shape.Fix: Keep data keys and template expressions aligned (e.g. cartItems with item in cartItems and item.* inside that loop). Inspect window.efScope and Pick Element bindings in the Templates debug tab.
Mistake: In the single-product layout (one product, big image + title), using cartItems[0].name or cartItems[0].image.Why it’s fragile: The app may not guarantee cartItems[0] on single-product checkout; the canonical data for that view is checkout.product_title, checkout.product_image, etc.Fix: Use <template-if data-condition="is_single_product"> and checkout.product_* keys; use cartItems only in the cart block. See Checkout — Order summary.
Mistake: No data-ef-cloak on blocks that contain [[ ... ]] or template-if content, so users see raw brackets or the wrong branch until the processor runs.Fix: Add data-ef-cloak to elements that should be hidden until the template engine has run. The engine removes this attribute after processing. Pair with CSS that hides [data-ef-cloak] to avoid a flash. The page may also need CSS that hides template-foreach, template-if, template-else, template-set so raw custom tags are not visible before processing.
Mistake: Putting <template-else> inside <template-if>, or using a tag like <template-if-else>.Fix: template-else and template-else-if must be siblings of template-if, immediately after it. Structure:
<template-if data-condition="...">...</template-if>
<template-else-if data-condition="...">...</template-else-if>
<template-else>...</template-else>
Mistake: Checkout inputs without data-checkout-error="fieldId" container, or id / data-template-value not matching the field list the backend expects.Fix: Each checkout field needs id, name, data-template-value, and <div class="form-error" data-checkout-error="<id>"></div>. Use exact ids and paths from the spec. See Checkout — Form fields.
Mistake: Wrapping <option> elements in <template-foreach> inside a <select>:
<select name="billing_country">
  <template-foreach data-each="c in checkout.countries">
    <option :value="c.value">[[ c.label ]]</option>
  </template-foreach>
</select>
Why it breaks: Safari and iOS strip unknown/custom elements from <select>. The <template-foreach> tag is removed by the browser before the template engine runs, so no options render.Fix: Use inline data-each directly on the <option> tag:
<select name="billing_country" data-template-value="checkout.customer.billing_country">
  <option data-each="opt in checkout.countries" data-ef-value="opt.value" data-ef-cloak>[[ opt.label ]]</option>
</select>
The server-side processor auto-converts <template-foreach> inside <select> in page templates, but client-side generated HTML (e.g. plugins) must use the inline form directly.
Mistake: Bump checkboxes/labels without data-bump-checkbox, data-bump-toggle, or data-bump-code, so the bump-products plugin cannot toggle or style them.Fix: Use data-bump-code="[[ bump.code ]]" on the card, data-bump-checkbox="[[ bump.code ]]" and value="[[ bump.code ]]" on the checkbox, and data-bump-toggle="[[ bump.code ]]" on the label. Same code on both “add” and “remove” states.

Summary

  • Limitations: No virtual DOM/component lifecycle; template-else needs a sibling template-if; checkout and query are context-specific.
  • Pitfalls: Don’t use formatPrice on formatted values; use [[ ]] not for frontend; keep data paths aligned with bindings; use checkout.* for single-product summary; use data-ef-cloak where needed; keep template-if/else structure and checkout/bump attributes as specified; never use template-foreach inside select — use inline data-each on option instead (Safari/iOS).
Next: Debugging.