Skip to content

Commit 7f62062

Browse files
alexanderGugelmichaelobriena
authored andcommitted
feat: Add ElementCache lifecycle events
The DOMRenderer exposes an API for registering callback functions to be executed on element insertion and removal.
1 parent c0eaddb commit 7f62062

File tree

2 files changed

+140
-1
lines changed

2 files changed

+140
-1
lines changed

dom-renderers/DOMRenderer.js

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ var ElementCache = require('./ElementCache');
2828
var math = require('./Math');
2929
var PathUtils = require('../core/Path');
3030
var vendorPrefix = require('../utilities/vendorPrefix');
31+
var CallbackStore = require('../utilities/CallbackStore');
3132
var eventMap = require('./events/EventMap');
3233

3334
var TRANSFORM = null;
@@ -68,6 +69,9 @@ function DOMRenderer (element, selector, compositor) {
6869
this._children = []; // a register for holding the children of the
6970
// current target.
7071

72+
this._insertElCallbackStore = new CallbackStore();
73+
this._removeElCallbackStore = new CallbackStore();
74+
7175
this._root = new ElementCache(element, selector); // the root
7276
// of the dom tree that this
7377
// renderer is responsible
@@ -457,7 +461,10 @@ DOMRenderer.prototype.insertEl = function insertEl (tagName) {
457461
'Void elements are not allowed to have children.'
458462
);
459463

460-
if (this._target) this._parent.element.removeChild(this._target.element);
464+
if (this._target) {
465+
this._parent.element.removeChild(this._target.element);
466+
this._removeElCallbackStore.trigger(this._path, this._target);
467+
}
461468

462469
this._target = new ElementCache(document.createElement(tagName), this._path);
463470

@@ -468,6 +475,8 @@ DOMRenderer.prototype.insertEl = function insertEl (tagName) {
468475

469476
this._parent.element.appendChild(this._target.element);
470477
this._elements[this._path] = this._target;
478+
479+
this._insertElCallbackStore.trigger(this._path, this._target);
471480
}
472481
};
473482

@@ -705,4 +714,69 @@ DOMRenderer.prototype._stringifyMatrix = function _stringifyMatrix(m) {
705714
return r;
706715
};
707716

717+
/**
718+
* Registers a function to be executed when a new element is being inserted at
719+
* the specified path.
720+
*
721+
* @method
722+
*
723+
* @param {String} path Path at which to listen for element insertion.
724+
* @param {Function} callback Function to be executed when an insertion
725+
* occurs.
726+
* @return {DOMRenderer} this
727+
*/
728+
DOMRenderer.prototype.onInsertEl = function onInsertEl(path, callback) {
729+
this._insertElCallbackStore.on(path, callback);
730+
return this;
731+
};
732+
733+
/**
734+
* Deregisters a listener function to be no longer executed on future element
735+
* insertions at the specified path.
736+
*
737+
* @method
738+
*
739+
* @param {String} path Path at which the listener function has been
740+
* registered.
741+
* @param {Function} callback Callback function to be deregistered.
742+
* @return {DOMRenderer} this
743+
*/
744+
DOMRenderer.prototype.offInsertEl = function offInsertEl(path, callback) {
745+
this._insertElCallbackStore.off(path, callback);
746+
return this;
747+
};
748+
749+
/**
750+
* Registers an event handler to be triggered as soon as an element at the
751+
* specified path is being removed.
752+
*
753+
* @method
754+
*
755+
* @param {String} path Path at which to listen for the removal of an
756+
* element.
757+
* @param {Function} callback Function to be executed when an element is
758+
* being removed at the specified path.
759+
* @return {DOMRenderer} this
760+
*/
761+
DOMRenderer.prototype.onRemoveEl = function onRemoveEl(path, callback) {
762+
this._removeElCallbackStore.on(path, callback);
763+
return this;
764+
};
765+
766+
/**
767+
* Deregisters a listener function to be no longer executed when an element is
768+
* being removed from the specified path.
769+
*
770+
* @method
771+
*
772+
* @param {String} path Path at which the listener function has been
773+
* registered.
774+
* @param {Function} callback Callback function to be deregistered.
775+
* @return {DOMRenderer} this
776+
*/
777+
DOMRenderer.prototype.offRemoveEl = function offRemoveEl(path, callback) {
778+
this._removeElCallbackStore.off(path, callback);
779+
return this;
780+
};
781+
708782
module.exports = DOMRenderer;

dom-renderers/test/DOMRenderer.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,4 +316,69 @@ test('DOMRenderer', function(t) {
316316
t.end();
317317
});
318318

319+
t.test('onInsertEl method', function(t) {
320+
var element = document.createElement('div');
321+
var selector = 'selector';
322+
var compositor = createUnidirectionalCompositor();
323+
var domRenderer = new DOMRenderer(element, selector, compositor);
324+
325+
t.equal(typeof domRenderer.onInsertEl, 'function', 'domRenderer.onInsertEl should be a function');
326+
327+
var triggeredInsertEl = false;
328+
var triggeredElementCache = null;
329+
330+
domRenderer.onInsertEl(selector + '/1/2/3', function(elementCache) {
331+
triggeredInsertEl = true;
332+
triggeredElementCache = elementCache;
333+
});
334+
335+
domRenderer.loadPath(selector + '/1/2');
336+
domRenderer.findTarget();
337+
domRenderer.insertEl('div');
338+
t.equal(triggeredInsertEl, false, 'domRenderer.onInsertEl should not be triggered on parent of registered path');
339+
340+
341+
domRenderer.loadPath(selector + '/1/2/3');
342+
domRenderer.findTarget();
343+
domRenderer.insertEl('div');
344+
t.equal(triggeredInsertEl, true, 'domRenderer.onInsertEl should be triggered on element insertion at registered path');
345+
t.notEqual(triggeredElementCache, null, 'domRenderer.onInsertEl listener should receive newly created ElementCache');
346+
347+
t.equal(triggeredElementCache.path, selector + '/1/2/3', 'domRenderer.onInsertEl should trigger ElementCache with the correct path');
348+
t.equal(triggeredElementCache.element, element.children[0].children[0], 'domRenderer.onInsertEl should trigger ElementCache that manages the correct element');
349+
350+
t.end();
351+
});
352+
353+
t.test('onRemoveEl method', function(t) {
354+
var element = document.createElement('div');
355+
var selector = 'selector';
356+
var compositor = createUnidirectionalCompositor();
357+
var domRenderer = new DOMRenderer(element, selector, compositor);
358+
359+
t.equal(typeof domRenderer.onRemoveEl, 'function', 'domRenderer.onRemoveEl should be a function');
360+
361+
var triggeredInsertEl = false;
362+
var triggeredElementCache = null;
363+
364+
domRenderer.onRemoveEl(selector + '/1', function(elementCache) {
365+
triggeredInsertEl = true;
366+
triggeredElementCache = elementCache;
367+
});
368+
369+
domRenderer.loadPath(selector + '/1');
370+
domRenderer.findTarget();
371+
domRenderer.insertEl('div');
372+
373+
t.equal(triggeredInsertEl, false, 'DOMRenderer.onRemoveEl should not be triggered on initial insertion');
374+
375+
domRenderer.findTarget();
376+
domRenderer.insertEl('section');
377+
378+
t.equal(triggeredInsertEl, true, 'DOMRenderer.onRemoveEl should be triggered when overriding an element at the same path');
379+
380+
t.equal(triggeredElementCache.element.tagName.toLowerCase(), 'div', 'DOMRenderer.onRemoveEl listener should receive ElementCache with correct tagName');
381+
382+
t.end();
383+
});
319384
});

0 commit comments

Comments
 (0)