Custom Filters
Adding Custom Filters
With Environment
const env = new Environment({ templates: './views', filters: { // Simple filter double: (value: number) => value * 2,
// Filter with argument repeat: (value: string, times: number = 2) => value.repeat(times),
// Multiple arguments replace_all: (value: string, search: string, replace: string) => value.split(search).join(replace), }})With addFilter Method
const env = new Environment({ templates: './views' })
env.addFilter('currency', (value: number, symbol = '$') => { return `${symbol}${value.toFixed(2)}`})
env.addFilter('truncate_middle', (value: string, length: number) => { if (value.length <= length) return value const half = Math.floor((length - 3) / 2) return value.slice(0, half) + '...' + value.slice(-half)})With render() Options
import { render } from 'binja'
const html = await render('{{ price|currency }}', { price: 42.5 }, { filters: { currency: (value: number) => `$${value.toFixed(2)}` } })Usage in Templates
{{ 5|double }} {# Output: 10 #}{{ "hi"|repeat:3 }} {# Output: hihihi #}{{ price|currency }} {# Output: $42.50 #}{{ price|currency:"€" }} {# Output: €42.50 #}Filter Function Signature
type FilterFunction = (value: any, ...args: any[]) => any- First argument is always the input value
- Additional arguments come from template syntax
- Return value becomes the filter output
Examples
String Filters
env.addFilter('initials', (name: string) => { return name .split(' ') .map(word => word[0].toUpperCase()) .join('')})
env.addFilter('mask_email', (email: string) => { const [user, domain] = email.split('@') return user[0] + '***@' + domain}){{ "John Doe"|initials }} {# JD #}{{ "john@example.com"|mask_email }} {# j***@example.com #}Number Filters
env.addFilter('percentage', (value: number, decimals = 0) => { return (value * 100).toFixed(decimals) + '%'})
env.addFilter('ordinal', (n: number) => { const s = ['th', 'st', 'nd', 'rd'] const v = n % 100 return n + (s[(v - 20) % 10] || s[v] || s[0])}){{ 0.75|percentage }} {# 75% #}{{ 0.756|percentage:1 }} {# 75.6% #}{{ 1|ordinal }} {# 1st #}{{ 23|ordinal }} {# 23rd #}Array Filters
env.addFilter('shuffle', (arr: any[]) => { const result = [...arr] for (let i = result.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)) ;[result[i], result[j]] = [result[j], result[i]] } return result})
env.addFilter('pluck', (arr: object[], key: string) => { return arr.map(item => item[key])}){{ items|shuffle|first }}{{ users|pluck:"name"|join:", " }}Date Filters
env.addFilter('relative_time', (date: Date) => { const now = new Date() const diff = now.getTime() - date.getTime() const seconds = Math.floor(diff / 1000) const minutes = Math.floor(seconds / 60) const hours = Math.floor(minutes / 60) const days = Math.floor(hours / 24)
if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago` if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago` if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''} ago` return 'just now'}){{ post.created_at|relative_time }} {# 2 hours ago #}Async Filters
env.addFilter('translate', async (text: string, lang: string) => { const response = await fetch(`/api/translate?text=${text}&lang=${lang}`) const data = await response.json() return data.translated}){{ "Hello"|translate:"es" }} {# Hola #}Best Practices
- Keep filters pure - No side effects
- Handle edge cases - null, undefined, empty strings
- Use TypeScript - Type your filter functions
- Document filters - Comment usage and parameters
- Test filters - Write unit tests
// Good: handles edge casesenv.addFilter('safe_upper', (value: any) => { if (value == null) return '' return String(value).toUpperCase()})