
Welcome to saladbar, a library of functions built for composition that take a functional approach to working with the DOM.
Why the name saladbar? This library is intended to be used by combining smaller functions to build complete programs. That sounds a lot like how a saladbar works. Also, I like food, and I think library names should be fun.
Usage
npm install --save saladbar
Saladbar is bundled to work with EcmaScript version 5.
For older environments you may need to polyfill one or more of the following
functions: Object.assign
and Array.isArray
.
CommonJS Module
var { hasClass } = require('saladbar');
var hasClassTest = hasClass('test');
hasClassTest('.default')
.map(console.log);
.leftMap(console.error)
EcmaScript Module
The package.json
sets a module
-field for build-tools like Rollup or Webpack.
import { hasClass } from 'saladbar';
const hasClassTest = hasClass('test');
hasClassTest('.default')
.map(console.log);
.leftMap(console.error)
Global Bundle (CDN)
Saladbar is hosted in full with all of its dependencies at: https://cdn.rawgit.com/wking-io/saladbar/8618f175/packages/saladbar/lib/umd/saladbar.min.js
This script will add saladbar
to the global scope.
Overview
Disclaimer: This library revolves around functional programming concepts. I highly recommend Professor Frisby’s Mostly Adequate Guide To Functional Programming as an introduction. It covers everything you need to get up an going with the concepts practiced in this library.
The goal of this library is to provide a set of focused, composable functions that cover common DOM interactions. Before diving into the API for each of the individual functions there are some universal guarantees and features for every function that I want to cover.
All functions are curried by default
A curried function is a function that when called with fewer arguments than expected, returns a new function that takes the remaining arguments. If you are familiar with currying you know the power this gives you when you are composing functions. If you are not familiar with it, here are some examples that show it usefulness.
You can partially apply some functions to make a more focused utility
const write = setProp('innerHTML');
const toggleOpen = toggleClass('open');
You can partially apply functions that take more than one argument so that they can be composed like LEGO blocks
const changeTheme = color =>
compose(
write(`Wow, look I am ${color}`),
addClass(`theme-${color}`),
setData('theme', color)
);
const changeThemeToBlue = changeTheme('blue');
const changeThemeToGreen = changeTheme('green');
const changeThemeToOrange = changeTheme('orange');
changeThemeToBlue('.page-wrapper');
All functions take the target element(s) last
This is a common and powerful technique to make function composition as easy as possible. In function composition each function in the chain takes the return value of the previous function. So, by passing the target element(s) as the last param we can more easily chain together DOM tranformations on said element(s).
const addStuff = compose(
setAttr('aria-hidden', 'false'),
addClass('show-element'),
setStyle('display', 'block')
);
addStuff('.element');
Since the functions might be used at any part of a composition chain all functions in the library allow the final argument to be any of the following cases:
- If passed a CSS Selector the function will automatically fetch that element from the DOM using the
dom
function.
- If passed an element or elements gotten by running
querySelector
or querySelectorAll
they will be wrapped in an Either.
- If passed an Either they will be checked to make sure it contains an element then it will move on.
All functions return an Either Monad
This is the big one. Every function in this library returns an Either. Every function also handles composing these Either wrapped results by default so that you do not have to worry about how each function composes to the next.
Why this approach? There are two main benefits.
No more runtime errors with “undefined is not a function” issues. The Either Type is defined by its ability to evaluate actions and capture their results as either a Left(failure) or a Right(success). Then on subsequent actions to that result, functions only run over the success values. This means if there is an error in your runtime the rest of the actions are ignored and that error is capture for you to handle yourself. It will not crash or show during evaluation.
Guaranteed laws that every value adheres to. The Either Type used in this library adheres to the following algebraic data types as outlined in the Fantasy-Land Spec:
- Functor
- Monad
- Applicative
- Chain
This means that there are mathematical laws that define how these values can be used and combined. Any implementation that follows these laws is guaranteed to work the exact same.
For a detailed overview of what this means I will point you again to Professor Frisby’s Mostly Adequate Guide To Functional Programming specifically chapters 8-12. Also this series that breaks down the Fantasy-Land Spec and defines it in plain language.
If you are not familiar with some of the functional programming techniques seen above check out these awesome resources:
Examples
Play around with the API that solve the problems below.
Documentation
Table Of Contents
Grabbing elements from the DOM
Setter Functions
Getter Functions
Predicate Functions
Event Functions
Utility Functions
Type signatures
Hindley-Milner type signatures are used to document functions. You
might encounter some additional syntax that we use to describe JavaScript
specific stuff, like functions that take
multiple arguments at once.
Types
You'll find that some signatures refer to concrete types, such as Either
.
This is reference of the types used throughout the documentation:
- Either - Instances of Either provided by
data.either
.
- Pair a b - An array with exactly two elements:
[a, b]
.
- Error - An object with a key of error and a string value that represents the error received.
- DOM Element - Since functions in this library accept multiple types of DOM Element representations (see Overview section above) this type represents any of those accepted values.
- DOM - Global representing the browser Document
- Selector - Valid CSS Selector as defined in the spec.
Grabbing elements from the DOM
dom
dom :: (Selector, DOM Element) -> Either Error DOM Element
Returns first element that matches the passed in Selector. If no element is found with that Selector the function will return an Error
. You can optionally pass another element to be used as the root of the query similiar to how you can runquerySelector
method on any element in the DOM.
const title = dom('.pick-me');
title.map(console.log);
domAll
domAll :: (Selector, DOM Element) -> Either Error [ DOM Element ]
Returns an array of all elements that match the passed in Selector. If no elements are found with that Selector the function will return an Error
. You can optionally pass another element to be used as the root for the query similar to how you can run querySelectorAll
method on any element in the DOM.
const listItems = domAll('.item');
title.map(console.log);
findParent
findParent :: pred -> DOM Element -> Either Error DOM Element
Returns parent element that matches the predicate. If no parent is found the body
element will be returned. If you pass an array of elements to the function each element will be mapped over and replaced with the parent element found by the predicate for that element.
const getSingleWrapper = findParent(hasClass('wrapper'));
getSingleWrapper('.pick-me').map(console.log);
const getAllWrapper = compose(findParent(hasClass('wrapper')), domAll);
getAllWrapper('.pick-me').map(console.log);
Setter Functions
addClass
addClass :: String -> DOM Element -> Future Error DOM Element
Adds class/classes to passed in element(s). You can pass in either a single class or an array of classes to be added to a single element or an array of elements.
const addClassGreen = addClass('green');
addClassGreen('.pick-me').map(console.log);
const addColorClasses = addClass(['green', 'blue']);
addColorClasses('.pick-me').map(console.log);
const addClassGreenAll = compose(addClass('green'), domAll);
addClassGreenAll('.pick-me').map(console.log);
const addColorClassesAll = compose(addClass(['green', 'blue']), domAll);
addColorClassesAll('.pick-me').map(console.log);
removeClass
`removeClass :: String -> DOM Element -> Future Error DOM Element```
Removes class/classes from passed in element(s). You can pass in either a single class or an array of classes to be removed from a single element or an array of elements.
const removeClassGreen = removeClass('green');
removeClassGreen('.pick-me').map(console.log);
const removeColorClasses = removeClass(['green', 'blue']);
removeColorClasses('.pick-me').map(console.log);
const removeClassGreenAll = compose(removeClass('green'), domAll);
removeClassGreenAll('.pick-me').map(console.log);
const removeColorClassesAll = compose(removeClass(['green', 'blue']), domAll);
removeColorClassesAll('.pick-me').map(console.log);
replaceClass
replaceClass :: String -> String -> DOM Element -> Either Error DOM Element
Replaces one class with another on passed in element(s). You replace a class on a single element or an array of elements.
const replaceClassGreen = replaceClass('green', 'blue');
replaceClassGreen('.pick-me').map(console.log);
const replaceClassGreenAll = compose(replaceClass('green', 'blue'), domAll);
replaceClassGreenAll('.pick-me').map(console.log);
setAttr
setAttr :: String -> String -> DOM Element -> Either Error DOM Element
Sets the value of the specified attribute on the passed in element(s).
const expandFirst = setAttr('aria-expanded', 'true');
expandFirst('.pick-me').map(console.log);
const expandAll = compose(setAttr('aria-expanded', 'true'), domAll);
expandAll('.pick-me').map(console.log);
setData
setData :: String -> String -> DOM Element -> Either Error DOM Element
Sets the value of the specified data-attribute on the passed in element(s). Sets values using the dataset property on DOM Elements so the data attribute you are setting will follow the naming rules outlined here: HTMLElement.dataset
const changeFirst = setData('example', 'after');
changeFirst('.pick-me').map(console.log);
const changeAll = compose(setData('example', 'after'), domAll);
changeAll('.pick-me').map(console.log);
setProp
setProp :: String -> String -> DOM Element -> Either Error DOM Element
Sets the value of the specified property on the passed in element(s).
const writeFirst = setProp('innerHTML', 'New Title');
writeFirst('.pick-me').map(console.log);
const writeAll = compose(setProp('innerHTML', 'New Title'), domAll);
writeAll('.pick-me').map(console.log);
setStyle
setStyle :: String -> String -> DOM Element -> Either Error DOM Element
Sets the value of the specified style property on the passed in element(s).
const highlightFirst = setStyle('color', '#6A5ACD');
highlightFirst('.pick-me').map(console.log);
const highlightAll = compose(setStyle('color', '#6A5ACD'), domAll);
highlightAll('.pick-me').map(console.log);
toggleClass
toggleClass :: String -> DOM Element -> Either Error DOM Element
Toggles class from passed in element(s). If the class exists on the element it is removed. If the class doesn't exist on the element it is added
const toggleClassBlue = toggleClass('blue');
toggleClassBlue('.pick-me').map(console.log);
const toggleClassBlueAll = compose(removeClass('blue'), domAll);
toggleClassBlueAll('.pick-me').map(console.log);
Getter Functions
getAttr
getAttr :: String → DOM Element → Either Error String
Returns the value of the passed in attribute. If attribute does not exist it returns an error.
const getFirst = getAttr('aria-expanded');
getFirst('.pick-me').map(console.log);
const getAll = compose(getAttr('aria-expanded'), domAll);
getAll('.pick-me').map(console.log);
getClass
getClass :: Int → DOM Element → Either Error String
Returns the value of class at passed in index. If there is no class at the index it returns an error.
const getFirst = getClass(1);
getFirst('.pick-me').map(console.log);
const getAll = compose(getClass(1), domAll);
getAll('.pick-me').map(console.log);
getClasses
getClasses :: DOM Element → Either Error [String]
Returns all classes on passed in element(s). If there are no classes it returns an error.
const getFirst = getClasses(1);
getFirst('.pick-me').map(console.log);
const getAll = compose(getClasses(1), domAll);
getAll('.pick-me').map(console.log);
getData
getData :: String → DOM Element → Either Error String
Returns the value of the passed in data-attribute. If data-attribute does not exist it returns an error. Gets value using the dataset property on DOM Elements so the data attribute you are getting will follow the naming rules outlined here: HTMLElement.dataset
const getFirst = getData('example');
getFirst('.pick-me').map(console.log);
const getAll = compose(setData('example'), domAll);
getAll('.pick-me').map(console.log);
getProp
getProp :: String → DOM Element → Either Error String
Returns the value of the passed in property. If property does not exist it returns an error.
const getFirst = getProp('innerHTML');
getFirst('.pick-me').map(console.log);
const getAll = compose(getProp('innerHTML'), domAll);
getAll('.pick-me').map(console.log);
getPosition
getPosition :: String → DOM Element → Either Error { bottom: Int, left: Int, right: Int, top: Int }
Returns an object representing the position of the passed in element(s) using the getBoundingClientRect
method. It cherry picks the top, right, bottom, and left positionsa from the generated DOMRect
instance.
getPosition('.pick-me').map(console.log);
const getAll = compose(getPosition, domAll);
getAll('.pick-me').map(console.log);
getStyle
getStyle :: String → DOM Element → Either Error String
Returns the value of the passed in style property. If style property does not exist it returns an error. All styles fetched are generated by calling the window.getGeneratedStyles
method.
const getFirst = getStyle('color');
getFirst('.pick-me').map(console.log);
const getAll = compose(getStyle('color'), domAll);
getAll('.pick-me').map(console.log);
serialize
serialize :: String → DOM Element → Either Error { name: value }
Returns object representing name/value pairs for all fields that are children of the passed in form.
serialize('.pick-me').map(console.log);
Predicate Functions
hasAttr
hasAttr :: String → DOM Element → Either Error Bool
Returns boolean indicating if an attribute exists on the passed in element(s).
const hasFirst = hasAttr('aria-expanded');
hasFirst('.pick-me').map(console.log);
const hasAll = compose(hasAttr('aria-expanded'), domAll);
hasAll('.pick-me').map(console.log);
hasClass
hasClass :: String → DOM Element → Either Error Bool
Returns boolean indicating if a class exists on the passed in element(s).
const hasFirst = hasClass('wrapper');
hasFirst('.pick-me').map(console.log);
const hasAll = compose(hasClass('wrapper'), domAll);
hasAll('.pick-me').map(console.log);
hasData
hasData :: String → DOM Element → Either Error Bool
Returns boolean indicating if a data-attribute exists on the passed in element(s).
const hasFirst = hasData('example');
hasFirst('.pick-me').map(console.log);
const hasAll = compose(hasData('example'), domAll);
hasAll('.pick-me').map(console.log);
hasProp
hasProp :: String → DOM Element → Either Error Bool
Returns boolean indicating if a propert exists on the passed in element(s).
const hasFirst = hasProp('innerHTML');
hasFirst('.pick-me').map(console.log);
const hasAll = compose(hasProp('innerHTML'), domAll);
hasAll('.pick-me').map(console.log);
hasStyle
hasStyle :: String → DOM Element → Either Error Bool
Returns boolean indicating if a style property exists on the passed in element(s).
const hasFirst = hasStyle('color');
hasFirst('.pick-me').map(console.log);
const hasAll = compose(hasStyle('color'), domAll);
hasAll('.pick-me').map(console.log);
isAttr
isAttr :: String → String → DOM Element → Either Error Bool
Returns boolean indicating if the passed in value matches the current value of an attribute on the passed in element(s).
const isFirst = isAttr('aria-expanded', 'false');
isFirst('.pick-me').map(console.log);
const isAll = compose(isAttr('aria-expanded', 'false'), domAll);
isAll('.pick-me').map(console.log);
isData
isData :: String → String → DOM Element → Either Error Bool
Returns boolean indicating if the passed in value matches the current value of a data-attribute on the passed in element(s).
const isFirst = isData('example', 'before');
isFirst('.pick-me').map(console.log);
const isAll = compose(isData('example', 'before'), domAll);
isAll('.pick-me').map(console.log);
isProp
isProp :: String → String → DOM Element → Either Error Bool
Returns boolean indicating if the passed in value matches the current value of a property on the passed in element(s).
const isFirst = isProp('innerHTML', 'Title');
isFirst('.pick-me').map(console.log);
const isAll = compose(isProp('innerHTML', 'Title'), domAll);
isAll('.pick-me').map(console.log);
isStyle
isStyle :: String → String → DOM Element → Either Error Bool
Returns boolean indicating if the passed in value matches the current value of an a style property on the passed in element(s).
const isFirst = isStyle('color', '#6A5ACD');
isFirst('.pick-me').map(console.log);
const isAll = compose(isStyle('color', '#6A5ACD'), domAll);
isAll('.pick-me').map(console.log);
Event Functions
on
on :: String → (event → Either Error a) → DOM Element → DOM Element
Add an event listener to passed in element(s) by passing in the event you want to listen for and the callback function you want to run.
const expand = setAttr('aria-expanded', 'true');
const expandOnClick = sel => e => expand(sel);
on('click', expandOnClick('.pick-me'), '[data-expand-wrapper]');
Utility Functions
toBool
toBool :: String → Either Error Bool
Function that takes the string representation of true
and false
boolean values and returns the actual boolean value. This is useful when you need an boolean when getting the value of an element attribute.
Note: Function takes both string versions of booleans and Either wrapped versions.
toBool('true').map(console.log));
toBool('false').map(console.log));
toBool(1).map(console.log));
toBool(Either.of('true')).map(console.log));
toBool(Either.of('false')).map(console.log));
toBool(Either.of(1)).map(console.log));
identity
identity :: a → a
Function that just returns whatever is passed as the input.
console.log(identity(4));
Build Your Own
If you are looking for how to wrap the core package with your own Data Type that info is located in the packages own README.
License
Apache-2.0