Quick Start Guide

View Source

Get up and running with LiveTable in 5 minutes! This guide assumes you've already completed the installation.

What We'll Build

We'll create a product catalog table for an e-commerce application with:

  • Product listing with sorting and search
  • Price range filtering
  • Stock status filtering
  • Export functionality

Step 1: Create Your Schema

First, let's define a simple Product schema:

# lib/your_app/catalog/product.ex
defmodule YourApp.Catalog.Product do
  use Ecto.Schema
  
  schema "products" do
    field :name, :string
    field :description, :string
    field :price, :decimal
    field :stock_quantity, :integer
    field :active, :boolean, default: true
    field :sku, :string
    
    timestamps()
  end
end

Step 2: Create a Simple LiveView

Create your products listing LiveView for a single table:

# lib/your_app_web/live/product_live/index.ex
defmodule YourAppWeb.ProductLive.Index do
  use YourAppWeb, :live_view
  use LiveTable.LiveResource, schema: YourApp.Catalog.Product
  
  # Define your table columns
  def fields do
    [
      id: %{
        label: "ID",
        sortable: true
      },
      name: %{
        label: "Product Name",
        sortable: true,
        searchable: true
      },
      sku: %{
        label: "SKU",
        sortable: true,
        searchable: true
      },
      price: %{
        label: "Price",
        sortable: true,
        renderer: &format_price/1
      },
      stock_quantity: %{
        label: "Stock",
        sortable: true
      },
      active: %{
        label: "Status",
        sortable: true,
        renderer: &format_status/1
      }
    ]
  end
  
  # Define your filters
  def filters do
    [
      # Boolean filter for active products
      active_only: Boolean.new(:active, "active", %{
        label: "Active Products Only",
        condition: dynamic([p], p.active == true)
      }),
      
      # Range filter for price
      price_range: Range.new(:price, "price_range", %{
        type: :number,
        label: "Price Range",
        unit: "$",
        min: 0,
        max: 1000,
        step: 10
      }),
      
      # Boolean filter for low stock
      low_stock: Boolean.new(:stock_quantity, "low_stock", %{
        label: "Low Stock (< 10)",
        condition: dynamic([p], p.stock_quantity < 10)
      })
    ]
  end
  
  # Custom renderers for better display
  defp format_price(price) do
    assigns = %{price: price}
    ~H"""
    <span class="font-mono text-green-600">
      $<%= :erlang.float_to_binary(@price, decimals: 2) %>
    </span>
    """
  end
  
  defp format_status(active) do
    assigns = %{active: active}
    ~H"""
    <span class={[
      "px-2 py-1 text-xs font-medium rounded-full",
      if(@active, do: "bg-green-100 text-green-700", else: "bg-red-100 text-red-700")
    ]}>
      <%= if @active, do: "Active", else: "Inactive" %>
    </span>
    """
  end
end

Step 3: Create the Template

Create the template file:

# lib/your_app_web/live/product_live/index.html.heex
<div class="px-4 py-6 sm:px-0">
  <div class="mb-6">
    <h1 class="text-2xl font-bold text-gray-900">Product Catalog</h1>
    <p class="mt-2 text-sm text-gray-600">
      Manage your product inventory with advanced filtering and search.
    </p>
  </div>

  <.live_table
    fields={fields()}
    filters={filters()}
    options={@options}
    streams={@streams}
  />
</div>

Step 4: Add Routes

Add the route to your router:

# lib/your_app_web/router.ex
scope "/", YourAppWeb do
  pipe_through :browser
  
  live "/products", ProductLive.Index, :index
end

Step 5: Seed Some Data

Create some sample data to test with:

# priv/repo/seeds.exs
alias YourApp.Repo
alias YourApp.Catalog.Product

# Create products
products = [
  %Product{
    name: "iPhone 15 Pro",
    description: "Latest Apple smartphone",
    price: Decimal.new("999.99"),
    stock_quantity: 25,
    active: true,
    sku: "IPHONE15PRO"
  },
  %Product{
    name: "MacBook Air M2",
    description: "Apple laptop with M2 chip",
    price: Decimal.new("1199.99"),
    stock_quantity: 8,
    active: true,
    sku: "MACBOOKAIR"
  },
  %Product{
    name: "Wireless Mouse",
    description: "Ergonomic wireless mouse",
    price: Decimal.new("45.99"),
    stock_quantity: 50,
    active: true,
    sku: "WMOUSE01"
  },
  %Product{
    name: "USB Cable",
    description: "High-quality USB-C cable",
    price: Decimal.new("24.99"),
    stock_quantity: 3,
    active: true,
    sku: "USBCABLE"
  },
  %Product{
    name: "Discontinued Phone",
    description: "No longer available",
    price: Decimal.new("299.99"),
    stock_quantity: 0,
    active: false,
    sku: "OLDPHONE"
  }
]

Enum.each(products, &Repo.insert!/1)

Run the seeds:

mix run priv/repo/seeds.exs

Step 6: Test Your Table

Start your server and visit your new table:

mix phx.server

Navigate to http://localhost:4000/products and you should see:

A fully functional data table with your products ✅ Sortable columns - click any column header to sort ✅ Multi-column sorting - hold Shift and click multiple headers ✅ Search functionality - type in the search box to filter products ✅ Advanced filters - toggle active products, adjust price range ✅ Pagination - if you have many products ✅ Export buttons - download CSV or PDF reports

Advanced Example: Custom Query with Joins

For more complex scenarios, you can use custom queries:

# lib/your_app_web/live/order_report_live/index.ex
defmodule YourAppWeb.OrderReportLive.Index do
  use YourAppWeb, :live_view
  use LiveTable.LiveResource

  def mount(_params, _session, socket) do
    # Assign your custom data provider function
    socket = assign(socket, :data_provider, {YourApp.Orders, :list_with_products, []})
    {:ok, socket}
  end

  def fields do
    [
      order_id: %{label: "Order #", sortable: true},
      customer_email: %{label: "Customer", sortable: true, searchable: true},
      total_amount: %{label: "Total", sortable: true},
      # Reference the alias used in your custom query
      product_name: %{
        label: "Product", 
        sortable: true, 
        searchable: true,
        assoc: {:order_items, :product_name}
      },
      order_date: %{label: "Date", sortable: true}
    ]
  end

  def filters do
    [
      status: Select.new({:orders, :status}, "status", %{
        label: "Order Status",
        options: [
          %{label: "Pending", value: ["pending"]},
          %{label: "Shipped", value: ["shipped"]},
          %{label: "Delivered", value: ["delivered"]}
        ]
      }),
      
      amount_range: Range.new(:total_amount, "amount_range", %{
        type: :number,
        label: "Order Amount",
        min: 0,
        max: 5000
      })
    ]
  end
end

And the corresponding context function:

# lib/your_app/orders.ex
defmodule YourApp.Orders do
  import Ecto.Query
  alias YourApp.Repo

  def list_with_products do
    from o in YourApp.Order,
      join: c in YourApp.Customer, on: o.customer_id == c.id,
      join: oi in YourApp.OrderItem, on: oi.order_id == o.id, as: :order_items,
      join: p in YourApp.Product, on: oi.product_id == p.id,
      select: %{
        order_id: o.id,
        customer_email: c.email,
        total_amount: o.total_amount,
        product_name: p.name,  # This field key must match your fields definition
        order_date: o.inserted_at
      }
  end
end

What's Next?

Congratulations! You now have a fully functional LiveTable. Here are some next steps:

Customize the Appearance

def table_options do
  %{
    pagination: %{
      sizes: [5, 10, 25, 50]
    },
    sorting: %{
      default_sort: [name: :asc]  # Only works for single table queries - field must exist in schema
    }
  }
end

Note: Default sort only works with simple schema fields for single table queries. For custom queries with joins, LiveTable doesn't currently support default sorting on joined fields.

Add Custom Actions

# In your fields definition
actions: %{
  label: "Actions",
  sortable: false,
  renderer: &render_actions/1
}

defp render_actions(product) do
  assigns = %{product: product}
  ~H"""
  <div class="flex gap-2">
    <.link 
      navigate={~p"/products/#{@product.id}"} 
      class="text-blue-600 hover:text-blue-800 text-sm"
    >
      Edit
    </.link>
    <button 
      phx-click="delete" 
      phx-value-id={@product.id}
      class="text-red-600 hover:text-red-800 text-sm"
      data-confirm="Are you sure?"
    >
      Delete
    </button>
  </div>
  """
end

Enable Card View

def table_options do
  %{
    mode: :card,
    card_component: &product_card/1
  }
end

defp product_card(assigns) do
  ~H"""
  <div class="bg-white rounded-lg shadow p-6">
    <h3 class="font-semibold text-lg"><%= @record.name %></h3>
    <p class="text-gray-600 text-sm"><%= @record.description %></p>
    <div class="mt-4 flex justify-between items-center">
      <span class="text-lg font-bold text-green-600">
        $<%= @record.price %>
      </span>
      <span class="text-sm text-gray-500">
        Stock: <%= @record.stock_quantity %>
      </span>
    </div>
  </div>
  """
end

Troubleshooting

Table not showing data?

  • Check that your schema is correct
  • Verify you have data in your database
  • Ensure the LiveView is properly mounted

Filters not working?

  • Verify filter field names match your schema
  • Check dynamic query syntax in filter conditions
  • Ensure Boolean/Range/Select are properly imported

Sorting not working on custom queries?

  • Make sure field keys in fields/0 match the select keys in your query
  • For joined fields, use assoc: {:alias_name, :field} where alias_name matches your query alias

Styling looks wrong?

  • Ensure Tailwind CSS is properly configured
  • Check that LiveTable CSS is imported
  • Verify Tailwind is processing LiveTable classes

Learn More