cheatsheets/live-table.cheatmd

# LiveTable Cheatsheet

## Setup

### Installation
{: .col-2}

```elixir
# mix.exs
{:live_table, "~> 0.4.0"}
```

```bash
mix deps.get
mix live_table.install
```

### Configuration
{: .col-2}

```elixir
# config/config.exs
config :live_table,
  repo: MyApp.Repo,
  pubsub: MyApp.PubSub
```

## Basic Usage

### Minimal LiveView
{: .col-2}

```elixir
defmodule MyAppWeb.ProductLive.Index do
  use MyAppWeb, :live_view
  use LiveTable.LiveResource, schema: MyApp.Product

  def fields do
    [
      id: %{label: "ID", sortable: true},
      name: %{label: "Name", sortable: true, searchable: true},
      price: %{label: "Price", sortable: true}
    ]
  end

  def filters, do: []
end
```

### Template
{: .col-2}

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

## Fields

### Field Options
{: .col-2}

| Option | Type | Description |
|--------|------|-------------|
| `label` | string | Column header text |
| `sortable` | boolean | Enable column sorting (default: false) |
| `searchable` | boolean | Include in text search |
| `hidden` | boolean | Hide from display (default: false) |
| `renderer` | function/1 or /2 | Custom cell renderer |
| `component` | function/1 | Component with `@value`, `@record` |
| `empty_text` | string | Display when value is nil |
| `computed` | dynamic | Calculated field |
| `assoc` | tuple | For joined fields: `{:alias, :field}` |

### Renderer Examples
{: .col-2}

```elixir
# function/1 - value only
price: %{
  label: "Price",
  renderer: &format_currency/1
}

defp format_currency(amount) do
  assigns = %{amount: amount}
  ~H"$<%= @amount %>"
end

# function/2 - value + record
status: %{
  label: "Status",
  renderer: &render_status/2
}

defp render_status(status, record) do
  assigns = %{status: status, record: record}
  ~H"<%= @status %> - <%= @record.name %>"
end
```

## Table Options

### Common Options
{: .col-2}

```elixir
def table_options do
  %{
    pagination: %{
      enabled: true,
      mode: :buttons,        # or :infinite_scroll (card mode only)
      sizes: [10, 25, 50],
      default_size: 25
    },
    sorting: %{
      enabled: true,
      default_sort: [name: :asc]
    },
    exports: %{
      enabled: true,
      formats: [:csv, :pdf]
    },
    search: %{
      enabled: true,
      debounce: 300,
      placeholder: "Search..."
    },
    mode: :table,            # or :card
    use_streams: true,
    fixed_header: false,
    debug: :off              # :query or :trace
  }
end
```

### Card Mode
{: .col-2}

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

defp product_card(assigns) do
  ~H"""
  <div class="p-4 border rounded">
    <h3><%= @record.name %></h3>
    <p>$<%= @record.price %></p>
  </div>
  """
end
```

### Empty State
{: .col-2}

```elixir
def table_options do
  %{
    empty_state: &custom_empty/1
  }
end

defp custom_empty(assigns) do
  ~H"""
  <div class="text-center py-8">
    No products found
  </div>
  """
end
```

## Actions

### Basic Actions
{: .col-2}

```elixir
def actions do
  %{
    label: "Actions",
    items: [
      edit: &edit_action/1,
      delete: &delete_action/1
    ]
  }
end

defp edit_action(assigns) do
  ~H"""
  <.link navigate={~p"/products/#{@record.id}/edit"}>
    Edit
  </.link>
  """
end
```

### In Template
{: .col-2}

```heex
<.live_table
  fields={fields()}
  filters={filters()}
  options={@options}
  streams={@streams}
  actions={actions()}
/>
```

## Custom Queries

### Data Provider Pattern
{: .col-2}

```elixir
defmodule MyAppWeb.ReportLive.Index do
  use MyAppWeb, :live_view
  use LiveTable.LiveResource  # no schema!

  def mount(_params, _session, socket) do
    socket = assign(socket, :data_provider, 
      {MyApp.Reports, :list_with_joins, []})
    {:ok, socket}
  end

  def fields do
    [
      # Keys must match select clause
      order_id: %{label: "Order", sortable: true},
      customer_name: %{
        label: "Customer",
        sortable: true,
        assoc: {:customers, :name}  # for sorting
      }
    ]
  end
end
```

### Context Function
{: .col-2}

```elixir
def list_with_joins do
  from o in Order,
    join: c in Customer, 
    on: o.customer_id == c.id,
    as: :customers,  # alias for assoc
    select: %{
      order_id: o.id,
      customer_name: c.name
    }
end
```

## Debug Mode

### Enable Debug
{: .col-2}

```elixir
def table_options do
  %{
    debug: :query  # Shows compiled query
    # debug: :trace  # Uses dbg()
    # debug: :off    # Default
  }
end
```

Output appears in terminal (dev only).

## Quick Reference

### Callbacks
{: .col-2}

| Callback | Required | Description |
|----------|----------|-------------|
| `fields/0` | Yes | Column definitions |
| `filters/0` | Yes | Filter definitions |
| `table_options/0` | No | Table configuration |
| `actions/0` | No | Row actions |

### Pagination Modes
{: .col-2}

| Mode | Description |
|------|-------------|
| `:buttons` | Traditional prev/next buttons |
| `:infinite_scroll` | Load more on scroll (card mode only) |