Hologram Documentation

repository·dev·Indexed 23 days ago

https://github.com/bartblast/hologram

A declarative component system for building interactive UIs entirely in Elixir. Hologram compiles Elixir code to JavaScript, enabling modern frontend capabilities without a separate JavaScript framework. It includes a component system with state and action management via Hologram.Component, an Erlang-compatible time system implementation in JavaScript, and a Last Writer Wins (LWW) CRDT Map for distributed state synchronization.

Tokens
8.7K
Snippets
4
Records
69
Agent score
80%

What's inside Hologram

  1. What is Hologram?

    dev
    Hologram is a declarative component system that allows you to build rich, interactive user interfaces entirely in Elixir. It eliminates the need for manual JavaScript framework management by intelligently compiling your client-side Elixir code into JavaScript, providing modern frontend capabilities while keeping your logic within the Elixir ecosystem.
  2. Use $click_outside for dismissible UI

    dev

    The $click_outside event fires when a click occurs anywhere outside the bound element and its descendants. This is ideal for implementing dropdowns, popovers, modals, and menus.

    Usage Pattern: Typically, the element containing the $click_outside binding is rendered conditionally (e.g., if @is_open). This ensures the listener is only active while the UI component is visible.

  3. Implement Middleware for authentication and logic

    dev

    Middleware is reusable server-side logic that runs before a page renders (init/3) or before commands execute. It is not Phoenix Plug.

    Middleware Types

    1. Inline: Define a public function on the page/component and attach it with middleware :name.
    2. Leaf: A module using use Hologram.Middleware that implements def call(server, opts).
    3. Composite: A module using use Hologram.Middleware that declares a sub-chain using middleware ... lines (omits call/2).

    Key Behaviors

    • Attachment: Use middleware SomeModule or middleware :some_function on a page or component.
    • Flow Control: To stop the chain (e.g., for unauthorized access), set a status using put_status(server, :forbidden), put_status(server, 403), or put_redirect(server, TargetPage). There is no halt; the status is checked after the middleware returns.
    • Data Passing: Use put_stash(server, :key, value) to pass data downstream to init/3 or commands.
    • Authentication: Use put_user_id(server, user_id) to log in and delete_user_id(server) to log out.
  4. Understand the Erlang Time System implementation in Hologram

    dev

    Hologram implements an Erlang-compatible time system in JavaScript. The core relationship is modeled as:

    Erlang System Time = Erlang Monotonic Time + Time Offset

    In this implementation, because JavaScript does not have Erlang's specific time correction mechanisms, :erlang.system_time/0 and :os.system_time/0 both map directly to Date.now(). To maintain mathematical consistency, the time_offset is derived as os.system_time - monotonic_time.

  5. Use Realtime for server-to-client pushes

    dev

    Realtime allows the server to push actions to clients. A broadcast triggers the client's action/3 handler.

    Use these inside init/3 or command/3. These are transactional and roll back if the handler fails.

    • put_subscription(server, channel): Subscribes the current component to a channel.
    • delete_subscription(server, channel): Removes a subscription.
    • put_broadcast(server, channel, action, params): Queues a broadcast.
    • put_broadcast_except(server, channel, action, params, {:instance, id}): Broadcasts to everyone except a specific instance.

    Outside Handlers (Escape Hatch)

    Use Hologram.Realtime functions for background jobs or GenServers. These fire immediately and do not roll back.

    • broadcast_action, subscribe, unsubscribe.
    • subscribe/unsubscribe require an explicit cid (e.g., "page").

    Channel Structure

    Channels are structured values, not strings. Examples:

    • Bare atom: :notifications
    • Tuple: {:room, 42} or {:doc, "abc-123", "v2"}
    • Identity channels: {:instance, id}, {:session, id}, {:user, id}.
  6. Share data using Context

    dev

    Context allows you to share data down the component tree without prop drilling. It is not a global store; data is only available to descendant components.

    • Setting Context: Use put_context(component, :key, value) in actions or init/3 functions. To avoid conflicts, use namespaced keys: put_context(component, {MyModule, :key}, value).
    • Accessing Context: Access values via props using the from_context option: prop :user, :map, from_context: :current_user.
    • Best Practice: Use props for direct children; use context for deeply nested data.
    prop :user, :map, from_context: :current_user
  7. Understand the Hologram Architecture

    dev

    Hologram is a full-stack isomorphic Elixir web framework that compiles Elixir to JavaScript for the browser. It is fundamentally different from Phoenix LiveView.

    Core Building Blocks

    • Pages: Route entry points. They are always stateful and initialized on the server.
    • Components: Reusable UI elements. They can be stateless or stateful.

    Execution Model

    • Actions: Run on the client (browser). Use these for state updates, navigation, and triggering commands.
    • Commands: Run on the server. Use these for database access, API calls, and session/cookie management.
    • State: Lives in the browser, enabling instant UI updates without network round-trips.

    Communication

    Client-server communication happens automatically over HTTP/2 persistent connections. Hologram automatically determines which code runs on the client vs server and compiles the client portions to JavaScript.

  8. Configure Layouts

    dev

    Layouts in Hologram are regular components that use use Hologram.Component. There is no special layout macro.

    Mandatory Requirements

    1. Runtime Component: A layout template must include <Hologram.UI.Runtime /> inside the <head> tag.
    2. Content Slot: A layout template must include <slot /> where the page content will be inserted.

    Targeting and Props

    • The layout's component ID (cid) is always "layout". Use target: "layout" to target actions at it.
    • Pass props to layouts via layout MyApp.MainLayout, prop: value in the Page definition, or via put_state/2 in the page's init/3.
  9. Use the ~HOLO Template Syntax

    dev

    Hologram uses the ~HOLO sigil for templates. Do not use HEEx (~H) syntax.

    Syntax Rules

    • Interpolation: Use curly braces {expression}. Do not use <%= expression %>.
    • Variables: Access props and state with @var syntax: {@name}, {@count}.
    • Component Nodes: Use module names: <MyComponent prop="value" />. Do not use <.my_component>.
    • Conditionals: Use {%if condition}...{/if} or {%if condition}...{%else}...{/if}.
    • Iteration: Use {%for item <- @items}...{/for}.
    • Raw Output: Use {%raw}...{/raw} to skip processing.
    • Escaping: Use \{literal\} to escape curly braces.
    • Spreads: Use ...{@expr} to spread maps or keyword lists as attributes or props (e.g., <div ...{@html_attrs}>).

    Attribute Behavior

    • If an attribute expression evaluates to nil or false, the attribute is not rendered.
    • On HTML elements, spread keys are dasherized (user_id becomes user-id). Nested maps/keyword lists use dash-joined names (data: [user_id: 1] becomes data-user-id="1").
    • On components, keys match declared prop names verbatim.
    • Event bindings (prefixed with $) cannot be spread; they must be written as literal attributes.
  10. Navigate between pages

    dev

    Use the Hologram.UI.Link component for all navigation. Do not use <.link navigate={...}> or live_redirect.

    • Standard link: <Link to={MyPage}>text</Link>
    • Link with parameters: <Link to={MyPage, id: 123}>text</Link>
    • Programmatic navigation: From within an action, use put_page(component, MyPage) or put_page(component, MyPage, id: 123).
  11. Bind global events with <window> and <document>

    dev

    To listen for global events (like keyboard shortcuts or window resizing) that are not tied to a specific element, use the <window> or <document> tags. These tags render nothing but attach listeners to the global window or document objects.

    • <window>: Use for window-level events like $resize or $scroll.
    • <document>: Use for document-level events like tab visibility.
    • Keyboard shortcuts: Since keyboard events bubble, either tag works for global shortcuts.

    Constraints:

    • These tags accept only event bindings; any other attributes will cause a build failure.
    • The listener only exists while the tag is rendered (e.g., inside a conditional).
    <window $key_down.ctrl+k="open_palette" />