Package Exports
- conditioner-core
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (conditioner-core) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Conditioner 
Conditioner provides a straight forward Progressive Enhancement based solution for linking JavaScript modules to DOM elements.
Modules can be linked based on contextual parameters like viewport size and element visibilty making Conditioner your perfect Responsive Design companion.
Example
Mount a component (like a Date Picker, Section Toggler or Carrousel), but only do it on wide viewports.
<h2 data-module="/ui/component.js"
data-context="@media (min-width:30em)"> ... </h2>
If the viewport is resized or rotated and suddenly it's smaller than 30em
Conditioner will automatically unmount the component.
Demo
Features
- Progressive Enhancement as a starting point 💆🏻
- Perfect for a Responsive Design strategy
- Declarative way to bind logic to elements, why this is good
- No dependencies and small footprint (~1KB gzipped)
- Compatible with ES
import()
, AMDrequire()
and webpack - Can be extended easily with plugins
Installation
Install with npm:
npm i conditioner-core --save
Using a CDN:
<script src="https://unpkg.com/conditioner-core/conditioner-core.js"></script>
Setup
Using Conditioner on the global scope:
<script src="https://unpkg.com/conditioner-core/conditioner-core.js"></script>
<script>
// mount modules!
conditioner.hydrate( document.documentElement );
</script>
Using Conditioner async with ES6 modules:
import('conditioner-core/index.js').then(conditioner => {
conditioner.addPlugin({
// converts module aliases to paths
moduleSetName: (name) => `/ui/${ name }.js`,
// get the module constructor
moduleGetConstructor: (module) => module.default,
// fetch module with dynamic import
moduleImport: (name) => import(name),
});
// mount modules!
conditioner.hydrate( document.documentElement );
});
Using Conditioner with webpack:
import * as conditioner from 'conditioner-core';
conditioner.addPlugin({
// converts module aliases to paths
moduleSetName: (name) => `./ui/${ name }.js`,
// get the module constructor
moduleGetConstructor: (module) => module.default,
// override the import
moduleImport: (name) => import(`${ name }`)
});
// lets go!
conditioner.hydrate( document.documentElement );
Using Conditioner in AMD modules:
require(['conditioner-core.js'], function(conditioner) {
// setup AMD require
conditioner.addPlugin({
// converts module aliases to paths
moduleSetName: function(name) { return '/ui/' + name + '.js' },
// setup AMD require
moduleImport: function(name) {
return new Promise(function(resolve) {
require([name], function(module) {
resolve(module);
});
});
}
});
// mount modules!
conditioner.hydrate( document.documentElement );
});
A collection of boilerplates to get you started with various project setups:
API
Public Methods
Inspired by React and Babel, Conditioner has a tiny but extensible API.
Method | Description |
---|---|
hydrate(element) |
Mount modules found in the subtree of the passed element, returns an array of bound module objects. |
addPlugin(plugin) |
Add a plugin to Conditioner to extend its core functionality. |
Bound Module
Bound modules are returned by the hydrate
method. Each bound module object wraps a module. It exposes a set of properties, methods and callbacks to interact with the module and its element.
Property / Method | Description |
---|---|
alias |
Name found in dataset.module . |
name |
Module path after name has been passed through moduleSetName . |
element |
The element the module is bound to. |
mount() |
Method to manually mount the module. |
unmount() |
Method to manually unmount the module. |
mounted |
Boolean indicating wether the module is currently mounted. |
onmount(boundModule) |
Callback that runs when the module has been mounted. Scoped to element. |
onmounterror(error, boundModule) |
Callback that runs when an error occurs during the mount process. Scoped to element. |
onunmount(boundModule) |
Callback that runs when the module has been unmounted. Scoped to element. |
Plugins
Adding a plugin can be done with the addPlugin
method, the method expects a plugin definition object.
Plugins can be used to override internal methods or add custom monitors to Conditioner.
You can link your plugin to the following hooks:
Hook | Description |
---|---|
moduleSelector(context) |
Selects all elements with modules within the given context and returns a NodeList . |
moduleGetContext(element) |
Returns context requirements for the module. By default returns the element.dataset.context attribute. |
moduleImport(name) |
Imports the module with the given name, should return a Promise . By default searches global scope for module name. |
moduleGetConstructor(module) |
Gets the constructor method from the module object, by default expects module parameter itself to be a factory function. |
moduleGetDestructor(moduleExports) |
Gets the destructor method from the module constructor return value, by default expects a single function. |
moduleSetConstructorArguments(name, element) |
Use to alter the arguments supplied to the module constructor, expects an array as return value. |
moduleGetName(element) |
Called to get the name of the module, by default retrieves the element.dataset.module value. |
moduleSetName(name) |
Called when the module name has been retrieved just before setting it. |
moduleWillMount(boundModule) |
Called before the module is mounted. |
moduleDidMount(boundModule) |
Called after the module is mounted. |
moduleWillUnmount(boundModule) |
Called before the module is unmounted. |
moduleDidUnmount(boundModule) |
Called after the module is unmounted. |
moduleDidCatch(error, boundModule) |
Called when module import throws an error. |
monitor |
A collection of registered monitors. See monitor setup instructions below. |
Let's setup a basic plugin.
Instead of writing each module with extension we want to leave out the extension and add it automatically.
We'll use the moduleSetName
hook to achieve this:
conditioner.addPlugin({
moduleSetName: (name) => `${ name }.js`
});
Next up is a plugin that adds a visible
monitor using the IntersectionObserver
API. Monitors can be used in a context query by prefixing the name with an @
.
Monitor plugins should mimic the MediaQueryList
API. Each monitor should expose a matches
property and an addListener
method.
conditioner.addPlugin({
monitor: {
name: 'visible',
create: (context, element) => ({
// current match state
matches: false,
// called by conditioner to start listening for changes
addListener (change) {
new IntersectionObserver(entries => {
// update the matches state
this.matches = entries.pop().isIntersecting == context;
// inform conditioner of the new state
change();
}).observe(element);
}
})
}
});
With our visible
monitor registered, we are ready to use it in a context query:
<div data-module="/ui/component.js" data-context="@visible true"></div>
To make context queries easier to read Conditioner will automatically set the context value to true
if its omitted. So the following context query is the same as @visible true
.
<div data-module="/ui/component.js" data-context="@visible"></div>
To invert the monitor state we can use the not
operator instead of writing @visible false
. It's simply more natural to read not @visible
than @visible false
.
The @visible
state context monitor will unload modules when they are no longer visible.
This might be exactly what you want, it's however more likely you want the modules to stick around after they've been loaded for the first time.
You can achieve this by adding the was
statement.
<div data-module="/ui/component.js" data-context="was @visible"></div>
Now the module will stay loaded once its required context has been matched for the first time.
You can string multiple monitors together with an and
statement allowing for very precise context queries.
<div data-module="/ui/component.js" data-context="@media (min-width:30em) and was @visible"></div>
Polyfilling
To use Conditioner on older browsers you'll have to polyfill some modern JavaScript features. You could also opt to use a Mustard Cut and prevent your JavaScript from running on older browsers by wrapping the hydrate
method in a feature detection statement.
// this will only run on IE10 and up
// https://caniuse.com/#feat=pagevisibility
if ('visibilityState' in document) {
conditioner.hydrate();
}
Polyfills required for Internet Explorer 11
Internet Explorer 10
You can either polyfill dataset
or add the following plugin to override the moduleGetName
and moduleGetContext
hooks (which use dataset
).
conditioner.addPlugin({
moduleGetName: function(element) { return element.getAttribute('data-module'); },
moduleGetContext: function(element) { return element.getAttribute('data-context'); }
});
The above plugin will also be required when you need to mount modules on SVG elements. Browser support for use of dataset
on SVG elements is a lot worse than HTML elements.