Ladies and gentlemen, meet ELQ – The classy approach to Element Queries for modular and responsive components.
Element queries are not for the faint hearted (read: not suitable for all projects). If your web app consists of modules that are (or should be) responsive – then this is most probably something for you! Otherwise, move on and come back another time :)
For a very detailed description of ELQ and element queries, read the Master's Thesis. The API and architecture is somewhat outdated, but the Thesis is still relevant for understanding ELQ.
We haven't had time to write proper docs yet. What follows is really a minimal explanation of the API. ELQ has more power than meets the eye, you are more than welcome to look under the hood!
Let's just assume the obvious, you want to install using npm. Here you go ;-)
npm install elq --save
dist/elq.js file is included into the HTML (it is built with UMD, so include it as you wish) it exposes a global function Elq. This is a constructor that creates ELQ instances. It is recommended to only use one instance per application.
The main idea is to annotate elements with breakpoints of interest so that children can be conditionally styled in CSS by targeting the different breakpoint states. ELQ is bundled with three plugins as default, that let you annotate breakpoints as attributes of your elements like so:
When ELQ has processed the element, it will always have two classes, one for each breakpoint, that tells if the size of the element is greater or lesser than each breakpoint. For instance, if the element is 400 pixels wide, the element has the two classes
elq-max-width-500px. Similarly, if the element is 200 pixels wide the element the classes are instead
elq-max-width-500px. So for each breakpoint only the
min/max part changes.
It may seem alien that the classes describe that the width of the element is both maximum 300 and 500 pixels. This is because we have taken a user-centric approach, so that when using the classes in CSS the API is similar to media queries. However, developers are free to change this API at will as
Elq is plugin-based.
Now that we have defined the breakpoints of the element, we can conditionally style it by using the classes:
Regarding nested modules… things start's getting a little more complicated when you need to have control over the styling of a modules subelements with regards to the modules breakpoints. It is a bit tricky to scope the styling, but luckily not impossible. Consider the following example:
.block.elq-min-width-210px > .block__element would work to select a subelement in modules that have a width of at least 210px. But this is not a nice selector, since it is dependentent on the structure of the DOM.
So, what alternatives are left? Well, you could of course activate ELQ on the subelements, which would give you total control… But in most cases, you just want the subelements to have the same breakpoint-classes as the whole module. That's why ELQ provides a plugin that lets you define elements to "mirror" the breakpoints classes of the nearest ancestor breakpoints element.
This is how you would use the
An ELQ instance exposes the following public methods:
Returns the version of instance.
Registers a plugin to be used by the instance. Parameter is required to be a
plugin definition object.
Tells if the given plugin has been registered or not. Parameter can be a plugin name (of type string) or a plugin definition object.
Activates the given collection of elements. This triggers ELQ to perform its work so that all element queries are updated. Plugins are invoked, so that they can perform their logic. Resize listeners are also installed when propriate. Elements may be activated multiple times.
Parameter can be any collection of elements, and also accepts a single element. The are assumed to be ELQ-elements and it is not recommended to activate non-ELQ elements.
elq.listenTo([element], event, callback)
Registers a callback to be called for an event of an element. The element parameter is option, and if omitted the callback will be called when the event is emitted for any element.
When enabled, the cycle detection system tries to detect cyclic rules and breaks them if needed. When a cycle is detected, a console warning is printed. This may be helpful during development to catch cycles.
Sets the default unit for all breakpoints that do not have a unit postfix. For instance, if defaultUnit is set to
"em", the breakpoint
300 is interpreted as
300em while breakpoint
500px is still interpreted as
One our of contributions is to allow ELQ to be easily extended with plugins. For example, if annotating HTML is undesired it is possible to create a plugin that instead parses CSS. Likewise, if adding breakpoint classes to element is undesired it is possible to create a plugin that does something else when a breakpoint state of an element has changed. In order to enable such powerful behavior altering by plugins, extensibility has been the main focus when designing the ELQ architecture.
A plugin is defined by a
plugin definition object and has the following structure:
All of the methods are invoked when registered to an ELQ instance. The
getVersion methods tells the name and version of the plugin. The
isCompatible tells if the plugin is compatible with the ELQ instance that it is registered to. In the
make method the plugin may initialize itself to the ELQ instance and return an object that defines the API accessible by ELQ and other plugins.
When necessary, ELQ invokes certain methods of the plugin API, if implemented, to let plugins decide the behavior of the system. Those methods are the following:
Called when an element is requested to be activated, in order for plugins to initialize listeners and element properties.
Called in order to let plugins reveal extra elements to be activated in addition to the given element.
Called to retrieve the current breakpoints of an element.
Called to apply the given breakpoint states of an element.
Emitted when an ELQ element has changed size.
Emitted when an element has changed breakpoint states (e.g., when the width of an element changed from being narrower than a breakpoint to being wider).
There are two main flows of the ELQ system; activating an element and updating an element. When ELQ is requested to activate an element, the following flow occurs:
getElementsmethod of all plugins is called to retrieve any additional elements to activate. Additional elements will go through an own flow.
activatemethod of all plugins is called so that plugin specific initialized may occur.
The update flow is as follows:
getBreakpointsmethod of all plugins is called to retrieve all breakpoints of the element.
applyBreakpointsmethod of all plugins is called.
breakpointStatesChangedevent is emitted.
Of course, there are options to disable some of the steps such as cycle detection and applying breakpoints. In addition to being triggered by the start flow and plugins, it is also triggered by element resize events.
The API that enables developers to annotate breakpoints in HTML, as described in the usage section, is implemented as two plugins. One plugin parses the breakpoints of the element attributes and one plugin applies the breakpoint classes. The simplified code of the
make method of the parsing plugin is as following:
The applying plugin simply implements the
applyBreakpoints method to alter the
className property of the element by the given breakpoint states.