Lodash-compatible utility functions with 120+ methods for arrays, objects, strings, and more.
_ or Domma.utils
Manipulate and transform arrays with powerful utility functions.
// Chunk array into groups
_.chunk([1, 2, 3, 4, 5], 2) // [[1, 2], [3, 4], [5]]
// Remove falsy values
_.compact([0, 1, false, 2, '', 3]) // [1, 2, 3]
// Array difference
_.difference([1, 2, 3], [2, 3]) // [1]
// Flatten nested arrays
_.flatten([[1, 2], [3, [4]]]) // [1, 2, 3, [4]]
_.flattenDeep([[1, [2, [3]]]]) // [1, 2, 3]
// Get unique values
_.uniq([1, 2, 2, 3, 3, 3]) // [1, 2, 3]
_.uniqBy([{x:1}, {x:1}, {x:2}], 'x') // [{x:1}, {x:2}]
// Combine arrays
_.union([1, 2], [2, 3]) // [1, 2, 3]
_.intersection([1, 2], [2, 3]) // [2]
// Array to object
_.zipObject(['a', 'b'], [1, 2]) // {a: 1, b: 2}
_.fromPairs([['a', 1], ['b', 2]]) // {a: 1, b: 2}
Iterate, search, and transform collections (arrays and objects).
const users = [
{ name: 'Alice', age: 25, dept: 'Engineering' },
{ name: 'Bob', age: 30, dept: 'Sales' },
{ name: 'Carol', age: 25, dept: 'Engineering' }
];
// Group by property
_.groupBy(users, 'dept')
// { Engineering: [...], Sales: [...] }
// Sort by property
_.sortBy(users, 'age')
_.orderBy(users, ['dept', 'name'], ['asc', 'desc'])
// Filter and find
_.filter(users, { dept: 'Engineering' })
_.find(users, { name: 'Bob' })
// Key by unique property
_.keyBy(users, 'name')
// { Alice: {...}, Bob: {...}, Carol: {...} }
// Partition by condition
_.partition(users, u => u.age > 25)
// [[Bob], [Alice, Carol]]
// Count by property
_.countBy(users, 'dept')
// { Engineering: 2, Sales: 1 }
Work with objects - access, transform, and manipulate properties.
const obj = {
user: { name: 'Alice', address: { city: 'NYC' } },
settings: { theme: 'dark' }
};
// Safe property access
_.get(obj, 'user.name') // 'Alice'
_.get(obj, 'user.phone', 'N/A') // 'N/A' (default)
_.get(obj, 'user.address.city') // 'NYC'
// Set nested property
_.set(obj, 'user.address.zip', '10001')
// Check if path exists
_.has(obj, 'user.name') // true
_.has(obj, 'user.email') // false
// Pick/omit properties
_.pick(obj.user, ['name']) // { name: 'Alice' }
_.omit(obj.user, ['address']) // { name: 'Alice' }
// Merge objects
_.merge({a: 1}, {b: 2}, {a: 3}) // {a: 3, b: 2}
_.defaults({a: 1}, {a: 2, b: 2}) // {a: 1, b: 2}
// Transform keys/values
_.mapKeys({a: 1, b: 2}, (v, k) => k.toUpperCase()) // {A: 1, B: 2}
_.mapValues({a: 1, b: 2}, v => v * 2) // {a: 2, b: 4}
// Invert object
_.invert({a: 1, b: 2}) // {1: 'a', 2: 'b'}
Control function execution with debounce, throttle, memoize, and more.
// Debounce - wait for pause in calls
const search = _.debounce((query) => {
console.log('Searching:', query);
}, 300);
input.addEventListener('input', e => search(e.target.value));
// Throttle - limit call frequency
const scroll = _.throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 100);
window.addEventListener('scroll', scroll);
// Memoize - cache results
const expensive = _.memoize((n) => {
console.log('Computing...');
return n * n;
});
expensive(5); // Computing... 25
expensive(5); // 25 (cached)
// Once - only call once
const init = _.once(() => console.log('Initialized!'));
init(); // Initialized!
init(); // (nothing)
// Curry - partial application
const add = _.curry((a, b, c) => a + b + c);
add(1)(2)(3); // 6
add(1, 2)(3); // 6
add(1)(2, 3); // 6
Transform and manipulate strings.
// Case conversion
_.camelCase('hello world') // 'helloWorld'
_.kebabCase('helloWorld') // 'hello-world'
_.snakeCase('Hello World') // 'hello_world'
_.startCase('helloWorld') // 'Hello World'
// Capitalize
_.capitalize('hello') // 'Hello'
_.upperFirst('hello') // 'Hello'
_.lowerFirst('HELLO') // 'hELLO'
// Padding
_.pad('abc', 8) // ' abc '
_.padStart('5', 3, '0') // '005'
_.padEnd('5', 3, '0') // '500'
// Truncate
_.truncate('Hello World', { length: 8 }) // 'Hello...'
// Template
const tpl = _.template('Hello, <%= name %>!');
tpl({ name: 'Alice' }) // 'Hello, Alice!'
// Escape HTML
_.escape('') // '<div>'
// Words
_.words('fred, barney, & pebbles') // ['fred', 'barney', 'pebbles']
Try It
Click a button to transform the string
Template Engine
Mustache-style templates with loops, conditionals, partials, and automatic HTML
escaping.
template()
render()
Basic Usage
// Compile and reuse
const tmpl = _.template('Hello {{name}}!');
tmpl({ name: 'World' }); // 'Hello World!'
// One-shot render
_.render('Welcome, {{user.name}}!', { user: { name: 'Alice' } });
// 'Welcome, Alice!'
// HTML is escaped by default
_.render('{{content}}
', { content: '' });
// '<script>
'
// Use triple braces for raw output
_.render('{{{html}}}', { html: 'Bold' });
// 'Bold'
Conditionals
// {{#if}}...{{/if}}
_.render('{{#if active}}Online{{/if}}', { active: true });
// 'Online'
// {{#if}}...{{else}}...{{/if}}
_.render('{{#if premium}}Pro{{else}}Free{{/if}}', { premium: false });
// 'Free'
// {{#unless}}...{{/unless}}
_.render('{{#unless verified}}Please verify{{/unless}}', { verified: false });
// 'Please verify'
Loops
// {{#each}}...{{/each}}
_.render('{{#each items}}{{name}} {{/each}}', {
items: [{ name: 'Apple' }, { name: 'Banana' }]
});
// 'Apple Banana '
// Primitive arrays use {{.}}
_.render('{{#each tags}}#{{.}} {{/each}}', { tags: ['js', 'css', 'html'] });
// '#js #css #html '
// Access loop metadata
_.render('{{#each items}}{{@index}}: {{name}}{{/each}}', { items: [...] });
// '0: Apple1: Banana'
Context & Partials
// {{#with}}...{{/with}} - shift context
_.render('{{#with user}}{{firstName}} {{lastName}}{{/with}}', {
user: { firstName: 'John', lastName: 'Doe' }
});
// 'John Doe'
// {{> partial}} - include named partials
const opts = { partials: { header: '{{title}}
' } };
_.render('{{> header}}', { title: 'Welcome' }, opts);
// 'Welcome
'
Try It
Click 'Render' to see the output
Lang (Type) Methods
Type checking, comparison, and cloning utilities.
castArray()
clone()
cloneDeep()
conformsTo()
eq()
gt()
gte()
isArray()
isBoolean()
isDate()
isElement()
isEmpty()
isEqual()
isEqualWith()
isError()
isFinite()
isFunction()
isInteger()
isLength()
isMatch()
isNaN()
isNil()
isNull()
isNumber()
isObject()
isPlainObject()
isRegExp()
isString()
isSymbol()
isUndefined()
lt()
lte()
castArray()
parseInt()
toArray()
toFinite()
toInteger()
toLength()
toNumber()
toPlainObject()
toSafeInteger()
toString()
// Type checking
_.isArray([1, 2]) // true
_.isObject({}) // true
_.isPlainObject({}) // true
_.isFunction(() => {})
_.isString('hello') // true
_.isNumber(123) // true
_.isNil(null) // true
_.isEmpty([]) // true
_.isEmpty({}) // true
// Deep equality
_.isEqual({a: 1}, {a: 1}) // true
_.isEqual([1, 2], [1, 2]) // true
// Cloning
const obj = { a: { b: 1 } };
const shallow = _.clone(obj); // Shallow copy
const deep = _.cloneDeep(obj); // Deep copy
// Object matching
_.isMatch({a: 1, b: 2}, {a: 1}) // true
// Conversions
_.parseInt('42') // 42
_.parseInt('08', 10) // 8
_.toNumber('3.14') // 3.14
_.toInteger('3.7') // 3
_.toFinite(Infinity) // 1.7976931348623157e+308
_.toSafeInteger(3.7) // 3
_.toArray('abc') // ['a', 'b', 'c']
_.toString([1, 2, 3]) // '1,2,3'
_.castArray(1) // [1]
_.castArray([1]) // [1]
Try It
Click a button to see result
Maths Methods
Mathematical operations and aggregations.
add()
ceil()
divide()
floor()
max()
maxBy()
mean()
meanBy()
min()
minBy()
multiply()
round()
subtract()
sum()
sumBy()
clamp()
inRange()
random()
// Basic maths
_.add(6, 4) // 10
_.subtract(6, 4) // 2
_.multiply(6, 4) // 24
_.divide(6, 4) // 1.5
// Rounding
_.round(4.567, 2) // 4.57
_.floor(4.567) // 4
_.ceil(4.123) // 5
// Aggregations
_.sum([1, 2, 3, 4]) // 10
_.mean([1, 2, 3, 4]) // 2.5
_.max([1, 2, 3, 4]) // 4
_.min([1, 2, 3, 4]) // 1
// With iteratee
const items = [{n: 1}, {n: 2}, {n: 3}];
_.sumBy(items, 'n') // 6
_.maxBy(items, 'n') // {n: 3}
_.minBy(items, 'n') // {n: 1}
_.meanBy(items, 'n') // 2
// Number utilities
_.clamp(10, 0, 5) // 5 (clamp to range)
_.inRange(3, 2, 4) // true
_.random(0, 100) // Random number 0-100
_.random(1.5, 5.5, true) // Random float
Try It
Click a button to see result
Real-World Patterns & Config Integration
Combine utility functions with Domma's config engine for cleaner, more maintainable code in real-world
scenarios.
Data Transformation Pipeline
// Transform API data for display using utility pipeline
const transformUserData = _.flow([
// 1. Filter active users only
(users) => _.filter(users, { status: 'active' }),
// 2. Sort by name
(users) => _.sortBy(users, 'name'),
// 3. Group by department
(users) => _.groupBy(users, 'department'),
// 4. Map to summary objects
(grouped) => _.mapValues(grouped, (users, dept) => ({
department: dept,
count: users.length,
users: _.map(users, u => _.pick(u, ['id', 'name', 'email']))
}))
]);
// Usage with $.setup()
$.setup({
'#user-dashboard': {
events: {
ready: async (e, $el) => {
const rawUsers = await Domma.http.get('/api/users');
const transformed = transformUserData(rawUsers);
// Render grouped data
_.forEach(transformed, (group, dept) => {
$el.append(`
${dept} (${group.count})
${_.map(group.users, u => `${u.name}`).join('')}
`);
});
}
}
}
});
Form Validation & Data Processing
// Centralized form validation with utils
const validators = {
email: (value) => /^[^S@]+@[^S@]+\.[^S@]+$/.test(value),
required: (value) => !_.isEmpty(_.trim(value)),
minLength: (min) => (value) => _.trim(value).length >= min,
alphanumeric: (value) => /^[a-zA-Z0-9]+$/.test(value)
};
$.setup({
'#signup-form': {
initial: {
data: { errors: {} }
},
events: {
submit: (e, $el) => {
e.preventDefault();
// Get form data
const formData = {
username: $el.find('[name="username"]').val(),
email: $el.find('[name="email"]').val(),
password: $el.find('[name="password"]').val()
};
// Validate with utils
const errors = {};
if (!validators.required(formData.username)) {
errors.username = 'Username is required';
} else if (!validators.alphanumeric(formData.username)) {
errors.username = 'Username must be alphanumeric';
}
if (!validators.required(formData.email)) {
errors.email = 'Email is required';
} else if (!validators.email(formData.email)) {
errors.email = 'Invalid email address';
}
if (!validators.minLength(8)(formData.password)) {
errors.password = 'Password must be at least 8 characters';
}
// Store errors
$el.data('errors', errors);
if (_.isEmpty(errors)) {
// Clean data before submission
const cleanData = _.mapValues(formData, _.trim);
console.log('Valid:', cleanData);
Domma.elements.toast.success('Form submitted!');
} else {
// Display errors
_.forEach(errors, (msg, field) => {
$el.find(`[name="${field}"]`)
.addClass('error')
.next('.error-msg').text(msg);
});
Domma.elements.toast.error(`${_.size(errors)} validation errors`);
}
},
'blur input': _.debounce((e, $el) => {
// Validate on blur (debounced)
const field = $(e.target).attr('name');
const value = $(e.target).val();
// Field-specific validation
let isValid = true;
if (field === 'email') {
isValid = validators.email(value);
} else if (field === 'username') {
isValid = validators.alphanumeric(value);
}
$(e.target).toggleClass('error', !isValid);
}, 300)
}
}
});
Smart Search with Debounce & Filtering
// Search implementation with utils
$.setup({
'#product-search': {
initial: {
data: {
products: [],
filteredProducts: [],
filters: { category: null, minPrice: 0, maxPrice: Infinity }
}
},
events: {
ready: async (e, $el) => {
// Load products
const products = await Domma.http.get('/api/products');
$el.data('products', products);
$el.data('filteredProducts', products);
renderProducts($el, products);
},
'input #search-box': _.debounce((e, $el) => {
const query = $(e.target).val().toLowerCase();
const products = $el.data('products');
const filters = $el.data('filters');
// Multi-criteria search with utils
const filtered = _.flow([
// Text search
(items) => query ? _.filter(items, item =>
_.toLower(item.name).includes(query) ||
_.toLower(item.description).includes(query)
) : items,
// Category filter
(items) => filters.category
? _.filter(items, { category: filters.category })
: items,
// Price range
(items) => _.filter(items, item =>
item.price >= filters.minPrice &&
item.price <= filters.maxPrice
),
// Sort by relevance (name match first)
(items) => _.sortBy(items, [
item => !_.toLower(item.name).includes(query),
'price'
])
])(products);
$el.data('filteredProducts', filtered);
renderProducts($el, filtered);
// Show count
$('#result-count').text(`${filtered.length} results`);
}, 300),
'change [name="category"]': (e, $el) => {
const category = $(e.target).val();
const filters = $el.data('filters');
filters.category = category || null;
$el.find('#search-box').trigger('input');
}
}
}
});
function renderProducts($container, products) {
const html = _.map(products, product => `
${_.escape(product.name)}
${_.truncate(product.description, { length: 100 })}
$${_.round(product.price, 2)}
`).join('');
$container.find('#results').html(html || 'No products found
');
}
Data Aggregation Dashboard
// Analytics dashboard with util aggregations
$.setup({
'#analytics-dashboard': {
events: {
ready: async (e, $el) => {
const sales = await Domma.http.get('/api/sales');
// Aggregate data with utils
const stats = {
// Total revenue
totalRevenue: _.sumBy(sales, 'amount'),
// Average order value
avgOrderValue: _.meanBy(sales, 'amount'),
// Sales by product
byProduct: _.chain(sales)
.groupBy('product')
.mapValues(items => ({
count: items.length,
revenue: _.sumBy(items, 'amount')
}))
.value(),
// Top 5 products
topProducts: _.chain(sales)
.groupBy('product')
.map((items, product) => ({
product,
revenue: _.sumBy(items, 'amount')
}))
.orderBy(['revenue'], ['desc'])
.take(5)
.value(),
// Sales by date
dailySales: _.chain(sales)
.groupBy(sale => D(sale.date).format('YYYY-MM-DD'))
.mapValues(items => _.sumBy(items, 'amount'))
.toPairs()
.orderBy([0], ['desc'])
.value()
};
// Render stats
$el.find('#total-revenue').text(`$${_.round(stats.totalRevenue, 2)}`);
$el.find('#avg-order').text(`$${_.round(stats.avgOrderValue, 2)}`);
// Render top products
const productsHTML = _.map(stats.topProducts, (item, index) => `
#${index + 1}: ${item.product} - $${_.round(item.revenue, 2)}
`).join('');
$el.find('#top-products').html(productsHTML);
}
}
}
});
Benefits of Utils + Config Pattern
- Declarative - Clear intent with functional transformations
- Composable - Chain utilities for complex operations
- Testable - Pure functions easy to unit test
- Performant - Debounce, memoize, and optimize expensive operations
- Maintainable - Centralized logic with $.setup()
Imperative vs Functional Utils Pattern
Imperative (Verbose)
// Filter, sort, group manually
const active = [];
for (const user of users) {
if (user.status === 'active') {
active.push(user);
}
}
active.sort((a, b) =>
a.name.localeCompare(b.name)
);
const byDept = {};
for (const user of active) {
if (!byDept[user.dept]) {
byDept[user.dept] = [];
}
byDept[user.dept].push(user);
}
// Hard to read, error-prone
// Mutable operations
Functional with Utils (Clean)
// Pipeline with utils
const byDept = _.flow([
users => _.filter(users, {
status: 'active'
}),
users => _.sortBy(users, 'name'),
users => _.groupBy(users, 'dept')
])(users);
// Clear transformation steps
// Immutable operations
// Chainable and composable
// Easy to test each step
Utility Methods
General utility functions.
attempt()
cond()
conforms()
constant()
defaultTo()
flow()
flowRight()
identity()
iteratee()
matches()
matchesProperty()
method()
methodOf()
noop()
nthArg()
over()
overEvery()
overSome()
property()
propertyOf()
range()
rangeRight()
stubArray()
stubFalse()
stubObject()
stubString()
stubTrue()
times()
toPath()
uniqueId()
// Generate ranges
_.range(5) // [0, 1, 2, 3, 4]
_.range(1, 5) // [1, 2, 3, 4]
_.range(0, 10, 2) // [0, 2, 4, 6, 8]
// Unique IDs
_.uniqueId() // '1'
_.uniqueId('user_') // 'user_2'
// Run function N times
_.times(3, i => i * 2) // [0, 2, 4]
// Default values
_.defaultTo(null, 10) // 10
_.defaultTo(1, 10) // 1
// Function composition
const addOne = x => x + 1;
const double = x => x * 2;
const composed = _.flow([addOne, double]);
composed(5) // 12 (5+1=6, 6*2=12)
// Property accessor
const getName = _.property('name');
getName({ name: 'Alice' }) // 'Alice'
// Identity
_.identity('hello') // 'hello'
// Noop
_.noop() // undefined (do nothing)
Try It
Click a button to see result