JSUA Style
Querying
You can query an element or an array of elements:
var element = document.createElement("div");
element.id = "one";
query(element).each(el => console.log(`Selected "${el.id}"`));
// => Selected "one"
var elementArray = [element];
query(elementArray).each(el => console.log(`Selected "${el.id}"`));
// => Selected "one"
You can convert a selection to an array:
var element = document.createElement("div");
element.id = "one";
var a = query(element).toArray();
console.log(`a[0] "${a[0].id}"`);
// => Selected "one"
You can select from the query using CSS selectors or predicate selectors:
var element = document.createElement("div");
element.id = "one";
query(element).select("#one").each(el => console.log(`Selected "${el.id}"`));
// => Selected "one"
query(element).select(el => el.id === "one").each(el => console.log(`Selected "${el.id}"`));
// => Selected "one"
You can filter the current selection using CSS selectors or predicate selectors:
var element = document.createElement("div");
element.id = "one";
query(element).filter("#one").each(el => console.log(`Selected "${el.id}"`));
// => Selected "one"
query(element).filter(el => el.id === "one").each(el => console.log(`Selected "${el.id}"`));
// => Selected "one"
In most cases, rather than chaining a filter
function to a query, it makes sense to do
a simple filter
operation (or many consecutive filter
operations) while maintaining the original
context. This can be done with the standalone filter
function.
var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
`;
query(parent).map(mappers.children()).each([
filter("#one", el => console.log(`Selected "${el.id}"`)),
filter("#two", el => console.log(`Selected "${el.id}"`)),
filter("#three", el => console.log(`Selected "${el.id}"`))
]);
// => Selected "one"
// => Selected "two"
// => Selected "three"
query(parent.firstElementChild).map(el => el.parent).each(el => console.log(`Selected "${el.id}"`));
// => Selected "parent"
Selectors
In addition to standard CSS selectors for select
or filter
operations, jsua-style
includes the following predicate
selectors.
firstChild
Select all elements that are the first child matching the specified selector.
var parent = document.createElement("div");
parent.innerHTML = `
<pre id="one"></pre>
<div id="two"></div>
<div id="three">
<div id="four"></div>
</div>
`;
query(parent).select(firstChild("div")).each(el => console.log(`Selected "${el.id}"`));
// => Selected "two"
// => Selected "four"
lastChild
Select all elements that are the last child matching the specified selector.
var parent = document.createElement("div");
parent.innerHTML = `
<pre id="one"></pre>
<div id="two"></div>
<div id="three">
<div id="four"></div>
</div>
<pre id="five"></pre>
`;
query(parent).select(lastChild("div")).each(el => console.log(`Selected "${el.id}"`));
// => Selected "three"
// => Selected "four"
nthChild
Select all child elements at a specified index after filtering by the specified selector.
var parent = document.createElement("div");
parent.innerHTML = `
<pre id="one"></pre>
<div id="two"></div>
<div id="three">
<div id="four"></div>
</div>
<pre id="five"></pre>
`;
query(parent).select(nthChild(0, "div")).each(el => console.log(`Selected "${el.id}"`));
// => Selected "two"
// => Selected "four"
Note that
firstChild
,lastChild
, andnthChild
are similar to, but more flexible than, CSS selectors:nth-of-type
,:first-of-type
, and:last-of-type
.
first
Select only the first element in a selection set.
var parent = document.createElement("div");
parent.innerHTML = `
<pre id="one"></pre>
<div id="two"></div>
<div id="three">
<div id="four"></div>
</div>
<pre id="five"></pre>
`;
query(parent).select(first("pre")).each(el => console.log(`Selected "${el.id}"`));
// => Selected "one"
has
Select only elements that map to a set of related elements.
var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<pre id="one"></pre>
<div id="two">
<pre></pre>
</div>
<div id="three">
<div></div>
<div></div>
</div>
<pre id="four"></pre>
`;
query(parent).select(has(mappers.children("div"))).each(el => console.log(`Selected "${el.id}"`));
// => Selected "parent"
// => Selected "three"
hasOne
Select only elements that map to a single related element.
var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<pre id="one"></pre>
<div id="two">
<pre></pre>
</div>
<div id="three">
<div></div>
</div>
<pre id="four"></pre>
`;
query(parent).select(hasOne(mappers.children("div"))).each(el => console.log(`Selected "${el.id}"`));
// => Selected "three"
Styling
You can apply style to each item in a selection:
var element = document.createElement("div");
query(element).each(el => el.style.backgroundColor = "red");
Mapping
You can map to one or more related elements:
var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
`;
query(parent).map(el => el.children).each(el => console.log(`Selected "${el.id}"`));
// => Selected "one"
// => Selected "two"
// => Selected "three"
query(parent.firstElementChild).map(el => el.parent).each(el => console.log(`Selected "${el.id}"`));
// => Selected "parent"
In most cases, rather than chaining a map
function to a query, it makes sense to do
a simple map
operation (or many consecutive map
operations) while maintaining the original
context. This can be done with the standalone map
function.
var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
`;
query(parent).each([
map("#one", el => console.log(`Selected "${el.id}"`)),
map("#two", el => console.log(`Selected "${el.id}"`)),
map("#three", el => console.log(`Selected "${el.id}"`))
]);
// => Selected "one"
// => Selected "two"
// => Selected "three"
query(parent.firstElementChild).map(el => el.parent).each(el => console.log(`Selected "${el.id}"`));
// => Selected "parent"
Mappers
The map
function will take any element or iterable of elements. The jsua-style
module provides the following commonly used functions.
-
ancestors
: All ancestors of the specified element.
All mappers are used as follows:
query(element).map(mappers.ancestors());
first, last, and nth
TODO: Document
children
var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
`;
query(parent).map(mappers.children()).each(el => console.log(`Selected "${el.id}"`));
// => Selected "one"
// => Selected "two"
// => Selected "three"
// Include an inline filter
query(parent).map(mappers.children("#two")).each(el => console.log(`Selected "${el.id}"`));
// => Selected "two"
realChildren
var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div role="presentation">
<div id="one"></div>
</div>
<div role="presentation">
<div role="presentation">
<div id="two"></div>
</div>
</div>
<div id="three"></div>
`;
query(parent).map(mappers.realChildren()).each(el => console.log(`Selected "${el.id}"`));
// => Selected "one"
// => Selected "two"
// => Selected "three"
// Include a filter parameter
query(parent).map(mappers.realChildren("#two")).each(el => console.log(`Selected "${el.id}"`));
// => Selected "two"
parent
var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
`;
query(parent).select("#two").map(mappers.parent()).each(el => console.log(`Selected "${el.id}"`));
// => Selected "parent"
// Include a filter parameter
query(parent).select("#two").map(mappers.parent("#parent")).each(el => console.log(`Selected "${el.id}"`));
// => Selected "parent"
realParent
var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div role="presentation">
<div id="one"></div>
</div>
<div role="presentation">
<div role="presentation">
<div id="two"></div>
</div>
</div>
<div id="three"></div>
`;
query(parent).select("#two").map(mappers.realParent()).each(el => console.log(`Selected "${el.id}"`));
// => Selected "parent"
// Include a filter parameter
query(parent).select("#two").map(mappers.realParent("#parent")).each(el => console.log(`Selected "${el.id}"`));
// => Selected "parent"
ancestors
var parent = document.createElement("div");
parent.id = "root";
parent.innerHTML = `
<div id="one">
<div id="two"></div>
</div>
`;
query(parent).select("#two").map(mappers.ancestors()).each(el => console.log(`Selected "${el.id}"`));
// => Selected "one"
// => Selected "root"
// Include an inline filter
query(parent).select("#two").map(mappers.ancestors("#root")).each(el => console.log(`Selected "${el.id}"`));
// => Selected "root"
descendants
var parent = document.createElement("div");
parent.id = "root";
parent.innerHTML = `
<div id="one">
<div id="two"></div>
</div>
`;
query(parent).map(mappers.descendants()).each(el => console.log(`Selected "${el.id}"`));
// => Selected "one"
// => Selected "two"
// Include an inline filter
query(parent).map(mappers.descendants("#one")).each(el => console.log(`Selected "${el.id}"`));
// => Selected "one"
previousSiblings and nextSiblings
var parent = document.createElement("div");
parent.id = "root";
parent.innerHTML = `
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
<div id="four"></div>
`;
query(parent).select("#three").map(mappers.previousSiblings()).each(el => console.log(`Selected "${el.id}"`));
// => Selected "two"
// => Selected "one"
// Include a filter parameter
query(parent).select("#two").map(mappers.nextSiblings("#four")).each(el => console.log(`Selected "${el.id}"`));
// => Selected "four"
slot
You can map to a component's named slot (see "Components" below).
query(component).each([
map(mappers.slot("header"), [
el => el.style.borderBottom = "1px solid #cccccc"
])
]);
In the case where an element uses more than one component, you can pass the name of the component as a second parameter.
query(component).each([
map(mappers.slot("header", "material-card"), [
el => el.style.borderBottom = "1px solid #cccccc"
])
]);
wrapper
You can wrap an element and return the wrapper.
query(el).each([
map(mappers.wrapper(), [
el => el.style.padding = "20px"
])
]);
Styling Based on Visual State
You can style an element in one or many states (using on
to respond to events and when
to apply styles only when the element is in a particular state):
query(element).each([
el => el.style.backgroundColor = "red",
on("mouseover", setState("hover")),
on("mouseout", clearState("hover")),
when("normal", el => el.style.backgroundColor = "red"),
when("hover", el => el.style.backgroundColor = "blue"),
setState("normal")
]);
Styling within a when
function will be called in the order of declaration.
In the example above, the normal
styles will be applied and then, if the
element is in the 'hover' state, the hover
styles will be applied. The end
result, when in the hover
state, is a blue background.
Mirroring the State of Another Element
Consider an input field with an invalid input. I'd like to turn the label red when the input is invalid.
Here's the structure of the input field:
<div id="input-field">
<label>First name</div>
<input type="text" />
</div>
The input field itself does not have validity, but it can mirror the validity of its input and act as if it does.
select("#input-field", [
when("validity", "invalid", [
select("label", el => el.style.color = "red")
]),
// The input field is mirroring the state of its input.
mirrorState("validity", mappers.children("input"))
])
Other Styling Functions
Context
You can add information about visual context with the context
function.
query(element).each([
context("page")
]);
// Now you can add style based on that context.
query(element).select("[data-jsua-context~=page] > [data-lynx-hints~=header]")
.each(el => el.style.padding = "16px");
Components
You can create a named component with slotted HTML structure.
var element = document.createElement("div");
var initialHTML = `
<div data-jsua-style-slot-name="label">Label</div>
<div>Other Content</div>
`;
element.innerHTML = initialHTML;
var componentHTML = `
<div role="presentation" data-jsua-style-slot="label"></div>
<div role="presentation" data-jsua-style-slot="content"></div>
`
query(element).each([
component("material-card", componentHTML)
]);
The component now has the following HTML.
<div data-jsua-style-component="material-card">
<div role="presentation" data-jsua-style-slot="label">
<div data-jsua-style-slot-name="label">Label</div>
</div>
<div role="presentation" data-jsua-style-slot="content">
<div>Other Content</div>
</div>
</div>
By default, children will be added to an element with the attribute
data-jsua-style-slot="content"
. If the child has adata-jsua-style-slot-name
attribute, it will be added to an element with the same value fordata-jsua-style-slot
.
We could also have added an element to a specific slot after the component had been created.
var mapHeader = mappers.realChildren("[data-lynx-hints~=header]");
query(component).each([
slot("header", mapHeader)
]);
For more on Authoring Components visit the wiki.
Media Queries
You can achieve responsive styling using the media
function.
const largeScreen = "(min-width: 840px)";
const mediumScreen = "(min-width: 600px) and (max-width: 839px)";
const smallScreen = "(max-width: 599px)";
query(element).each([
media(largeScreen, [
el => el.style.backgroundColor = "green"
]),
media(mediumScreen, [
el => el.style.backgroundColor = "yellow"
]),
media(smallScreen, [
el => el.style.backgroundColor = "red"
])
]);
Styling Adjustments
After styling has been applied to the element and its children, it is sometimes necessary to make adjustments. For example, it may be necessary to set an overflow
state if it turns out there's insufficient room for an element. Use the adjust
function for this purpose.
adjust([
filter(el => el.offsetHeight > 200, setState("overflow")),
filter(el => el.offsetHeight <= 200, clearState("overflow"))
])
The
adjust
function is called at the end of a finishing pass and after any changes in the media context. Children are adjusted before parents.