Filters Cheatsheet

View Source

Filter Types Overview

Available Filters

TypeModuleUse Case
BooleanLiveTable.BooleanToggle on/off conditions
RangeLiveTable.RangeNumeric/date ranges
SelectLiveTable.SelectDropdown selection
TransformerLiveTable.TransformerCustom query logic

Boolean Filter

Basic Usage

def filters do
  [
    in_stock: Boolean.new(:quantity, "in_stock", %{
      label: "In Stock Only",
      condition: dynamic([p], p.quantity > 0)
    })
  ]
end

Options

OptionTypeDescription
labelstringDisplay label
conditiondynamicEcto dynamic condition
defaultbooleanDefault state

Examples

# Active records
active: Boolean.new(:active, "active", %{
  label: "Active Only",
  condition: dynamic([r], r.active == true)
})

# Published items
published: Boolean.new(:published_at, "published", %{
  label: "Published",
  condition: dynamic([r], not is_nil(r.published_at))
})

# Recent items (last 7 days)
recent: Boolean.new(:inserted_at, "recent", %{
  label: "Last 7 Days",
  condition: dynamic([r], 
    r.inserted_at >= ago(7, "day"))
})

Range Filter

Basic Usage

def filters do
  [
    price_range: Range.new(:price, "price_range", %{
      type: :number,
      label: "Price Range",
      min: 0,
      max: 1000,
      step: 10
    })
  ]
end

Options

OptionTypeDescription
labelstringDisplay label
typeatom:number, :date, :datetime
minnumber/dateMinimum value
maxnumber/dateMaximum value
stepnumberIncrement step

Examples

# Number range
price: Range.new(:price, "price", %{
  type: :number,
  label: "Price",
  min: 0,
  max: 10000,
  step: 100
})

# Date range
created: Range.new(:inserted_at, "created", %{
  type: :date,
  label: "Created Date"
})

# Quantity range
stock: Range.new(:quantity, "stock", %{
  type: :number,
  label: "Stock Level",
  min: 0,
  max: 1000
})

Select Filter

Basic Usage

def filters do
  [
    status: Select.new(:status, "status", %{
      label: "Status",
      options: [
        %{label: "Active", value: ["active"]},
        %{label: "Pending", value: ["pending"]},
        %{label: "Archived", value: ["archived"]}
      ]
    })
  ]
end

Options

OptionTypeDescription
labelstringDisplay label
optionslistAvailable choices
multiplebooleanAllow multi-select
searchablebooleanEnable search in dropdown

Examples

# Single select
category: Select.new(:category_id, "category", %{
  label: "Category",
  options: [
    %{label: "Electronics", value: [1]},
    %{label: "Clothing", value: [2]},
    %{label: "Books", value: [3]}
  ]
})

# Multiple select
tags: Select.new(:tag, "tags", %{
  label: "Tags",
  multiple: true,
  options: [
    %{label: "Featured", value: ["featured"]},
    %{label: "Sale", value: ["sale"]},
    %{label: "New", value: ["new"]}
  ]
})

# Dynamic options
status: Select.new(:status, "status", %{
  label: "Status",
  options: Enum.map(statuses(), fn s ->
    %{label: String.capitalize(s), value: [s]}
  end)
})

For Joined Tables

# Use tuple for joined field
category: Select.new({:categories, :name}, "category", %{
  label: "Category",
  options: [
    %{label: "Electronics", value: ["Electronics"]},
    %{label: "Clothing", value: ["Clothing"]}
  ]
})

Transformer

Basic Usage

def filters do
  [
    custom: Transformer.new("custom_filter", %{
      query_transformer: &apply_custom_filter/2
    })
  ]
end

defp apply_custom_filter(query, filter_data) do
  case filter_data do
    %{"field" => value} when value != "" ->
      from q in query, where: q.field == ^value
    _ ->
      query
  end
end

Function Signature

def my_transformer(query, filter_data) do
  # query: Current Ecto query
  # filter_data: Map from URL params
  # Returns: Modified query
  query
end

Options

OptionTypeDescription
query_transformerfunction/2Transform function

Alternative: {Module, :function} tuple

Examples

# Join and aggregate
sales_filter: Transformer.new("sales", %{
  query_transformer: &filter_by_sales/2
})

defp filter_by_sales(query, %{"min" => min}) 
     when min != "" do
  from p in query,
    join: s in Sale, on: s.product_id == p.id,
    group_by: p.id,
    having: sum(s.amount) >= ^String.to_integer(min)
end
defp filter_by_sales(query, _), do: query

# Complex conditions
date_filter: Transformer.new("dates", %{
  query_transformer: &filter_dates/2
})

defp filter_dates(query, data) do
  query
  |> maybe_filter_start(data["start"])
  |> maybe_filter_end(data["end"])
end

defp maybe_filter_start(q, nil), do: q
defp maybe_filter_start(q, ""), do: q
defp maybe_filter_start(q, date) do
  from r in q, where: r.date >= ^date
end

Filter Patterns

Combining Filters

def filters do
  [
    # Boolean toggles
    active: Boolean.new(:active, "active", %{
      label: "Active Only",
      condition: dynamic([r], r.active == true)
    }),
    
    # Range filters
    price: Range.new(:price, "price", %{
      type: :number,
      label: "Price Range"
    }),
    
    # Select dropdown
    category: Select.new(:category, "category", %{
      label: "Category",
      options: category_options()
    }),
    
    # Custom transformer
    search: Transformer.new("advanced", %{
      query_transformer: &advanced_search/2
    })
  ]
end

Accessing Filter State

# In template, check applied filters
@options["filters"]["status"]

# In transformer, access all filter data
defp my_transformer(query, filter_data) do
  IO.inspect(filter_data)  # Debug
  query
end

Quick Reference

Filter Field Syntax

SyntaxUse Case
:fieldSimple schema field
{:alias, :field}Joined table field

Common Gotchas

  • Boolean condition must be an Ecto dynamic
  • Select value must be a list: ["value"] not "value"
  • Transformer must always return a query
  • For joins, use {:alias, :field} tuple