Utility that takes care of creating and destroying instances (e.g. of jQuery plugins) as soon as specific DOM nodes are added to the document, respectively removed from the document. Universal minimal (1.2 KB, 583 bytes compressed) helper with zero dependencies.
Many websites or web apps examine the DOM and create instances (e.g. of jQuery plugins) for specific DOM nodes during the initial load. Often a class name indicates what kind of instance should be created for a specific DOM Node.
Example 1: Tabs that use a jQuery plugin:
$('.tabs').tabs();
While this works fine for the markup that is present at the time the website or web app is loaded, dealing with dynamic markup is more complicated. Every time new DOM nodes are loaded into the document one must take care that instances for those new nodes are also created.
Example 2: new markup is loaded into the page:
$.get('/server/foo/bar', function(markup) {
$('#theTargetNode')
.html(markup)
.find('.tabs').tabs();
});
This approach is problematic because
These questions show the problem: in this case the separation of concerns is violated! Every piece of code that deals with loading markup must either know what plugins are expected in the result or know the complete list of plugins used on the page.
Loading data from the server is not the only place where the markup is changed. JavaScript templates are often used to create HTML markup during runtime.
Many components need a destructor to clean up variables or unbind from events to avoid memory leaks. This makes our little example even more complicated:
Example 3: load markup and clean up before the new markup is added:
$.get('/server/foo/bar', function(markup) {
$('#theTargetNode')
.find('.tabs').tabs('destroy');
$('#theTargetNode')
.html(markup)
.find('.tabs').tabs();
});
Not only must the little snippet know all about the components that are use, but also about the destructors and how to call them.
ComponentManager solves all these problems by creating instances for DOM nodes as soon as they are inserted into the DOM. It also takes care of calling the destructor functions when a DOM node is removed from the DOM.
ComponentManager has zero dependencies and uses a universal JavaScript module to be used in any environment. It depends on MutationOberserver
which is supported by all modern browser.
Example 4: Register a new component and provide callback for creating and removing instances:
var createTab = function(node) {
$(node).tabs();
};
var removeTab = function(node) {
$(node).tabs('destroy');
}
ComponentManager.register('tabs', createTab, removeTab);
Every time a DOM node with the data attribute configuration data-component-name="tabs"
is added to the DOM, createTab()
is executed with this node as parameter. As soon as a DOM node with this attribute and value is removed, removeTabs()
(with the node as payload).
Example 5: Registering a component and using it
var createTab = function(node) {
$(node).tabs();
};
var removeTab = function(node) {
$(node).tabs('destroy');
}
ComponentManager.register('tabs', createTab, removeTab);
$.get('/server/foo/bar', function(markup) {
$('#theTargetNode').html(markup);
});
We've successfully decoupled loading markup into the DOM from the creation of instances of the jQuery tab plugin. The small snippet that loads markup into the page doesn't need to know about components and the ComponentManager doesn't need to know any specifics about how a component works. Any specific code can, of course, be used in the callbacks.
Example 6: How to use component-specific code in a callback:
var createTab = function(node) {
var $node = $(node);
var options = $node.data('options');
$node.tabs(options);
};
Sometimes one component depends on another and therefore they should be initialised in a specific manner. If you need a specific order you can use the optional fourth argument when registering a component. A lower number indicates a higher priority.
Example 7: Components can have a priority when they are registered
ComponentManager.register('tabs', createTab, removeTab, 10);
ComponentManager.register('gmaps', createGmap, removeGmap, 20);
You can also have a look at the second demo to see this in action.
There are four ways to use ComponentManager.
bower install component-manager
npm install verwalter
<script src="https://unpkg.com/verwalter/dist/manager.min.js"></script>
<script src="https://github.com/jonathanweiss/component-manager/raw/master/manager.min.js"></script>
Please note that due to the nature of MutationObserver
the callbacks for creating and removing instances will be called asynchronously.
Example 8: Instances are used too early:
// see Example 5 for the code of the callbacks
ComponentManager.register('tabs', createTab, removeTab);
$.get('/server/foo/bar', function(markup) {
$('#theTargetNode').html(markup);
$('#theTargetNode .tabs').eq(0).tabs('load', '#foo'); // <-Uncaught TypeError: $(...).tabs is not a function
});
There are many solutions to wait for the MutationObserver
to finish working before using the instances. They range from a (somewhat sloppy) call of setTimeout(callback, 0)
to using events.
Example 9: Wait for event before using instances
var createTab = function(node) {
$(node)
.tabs()
.trigger('TabCreated');
};
var removeTab = function(node) {
$(node).tabs('destroy');
}
ComponentManager.register('tabs', createTab, removeTab);
$.get('/server/foo/bar', function(markup) {
$('#theTargetNode')
.html(markup)
.one('TabCreated', '.tabs', function() {
$('#theTargetNode .tabs').eq(0).tabs('load', '#foo');
});
});
In IE11 the list of child nodes is empty when nodes are removed when the property innerHTML
is set. This is a known issue in IE11 that won't be fixed.
It prevents the callback for remove from being executed. If you need to support IE11 and use the remove callback be sure to include a polyfill for MutationObserver
.