Skip to content

Commit e6e0d13

Browse files
proof-of-concept for a plugin system.
1 parent 5f878eb commit e6e0d13

File tree

4 files changed

+343
-20
lines changed

4 files changed

+343
-20
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,26 @@ Idiomorph.morph(document.documentElement, newPageSource, {head:{style: 'morph'}}
160160

161161
The `head` object also offers callbacks for configuring head merging specifics.
162162

163+
### Plugins
164+
165+
Idiomorph supports a plugin system that allows you to extend the functionality of the library, by registering an object of callbacks:
166+
167+
```js
168+
Idiomorph.registerPlugin({
169+
name: 'logger',
170+
onBeforeNodeAdded: function(node) {
171+
console.log('Node added:', node);
172+
},
173+
onBeforeNodeRemoved: function(node) {
174+
console.log('Node removed:', node);
175+
},
176+
});
177+
178+
Idiomorph.plugins // { logger: { ...} };
179+
```
180+
181+
These callbacks will be called in addition to any other callbacks that are registered in `Idiomorph.morph`. Multiple plugins can be registered.
182+
163183
### Setting Defaults
164184

165185
All the behaviors specified above can be set to a different default by mutating the `Idiomorph.defaults` object, including

src/idiomorph.js

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ var Idiomorph = (function () {
144144
restoreFocus: true,
145145
};
146146

147+
let plugins = {};
148+
function addPlugin(plugin) {
149+
plugins[plugin.name] = plugin;
150+
}
151+
147152
/**
148153
* Core idiomorph function for morphing one DOM tree to another
149154
*
@@ -348,6 +353,26 @@ var Idiomorph = (function () {
348353
}
349354
}
350355

356+
function withNodeCallbacks(ctx, name, node, fn) {
357+
const allPlugins = [...Object.values(plugins), ctx.callbacks];
358+
359+
const shouldAbort = allPlugins.some((plugin) => {
360+
const beforeFn = plugin[`beforeNode${name}`];
361+
return beforeFn && beforeFn(node) === false;
362+
});
363+
364+
if (shouldAbort) return;
365+
366+
const resultNode = fn();
367+
368+
allPlugins.reverse().forEach((plugin) => {
369+
const afterFn = plugin[`afterNode${name}`];
370+
afterFn && afterFn(resultNode);
371+
});
372+
373+
return resultNode;
374+
}
375+
351376
/**
352377
* This performs the action of inserting a new node while handling situations where the node contains
353378
* elements with persistent ids and possible state info we can still preserve by moving in and then morphing
@@ -359,23 +384,22 @@ var Idiomorph = (function () {
359384
* @returns {Node|null}
360385
*/
361386
function createNode(oldParent, newChild, insertionPoint, ctx) {
362-
if (ctx.callbacks.beforeNodeAdded(newChild) === false) return null;
363-
if (ctx.idMap.has(newChild)) {
364-
// node has children with ids with possible state so create a dummy elt of same type and apply full morph algorithm
365-
const newEmptyChild = document.createElement(
366-
/** @type {Element} */ (newChild).tagName,
367-
);
368-
oldParent.insertBefore(newEmptyChild, insertionPoint);
369-
morphNode(newEmptyChild, newChild, ctx);
370-
ctx.callbacks.afterNodeAdded(newEmptyChild);
371-
return newEmptyChild;
372-
} else {
373-
// optimisation: no id state to preserve so we can just insert a clone of the newChild and its descendants
374-
const newClonedChild = document.importNode(newChild, true); // importNode to not mutate newParent
375-
oldParent.insertBefore(newClonedChild, insertionPoint);
376-
ctx.callbacks.afterNodeAdded(newClonedChild);
377-
return newClonedChild;
378-
}
387+
return withNodeCallbacks(ctx, "Added", newChild, () => {
388+
if (ctx.idMap.has(newChild)) {
389+
// node has children with ids with possible state so create a dummy elt of same type and apply full morph algorithm
390+
const newEmptyChild = document.createElement(
391+
/** @type {Element} */ (newChild).tagName,
392+
);
393+
oldParent.insertBefore(newEmptyChild, insertionPoint);
394+
morphNode(newEmptyChild, newChild, ctx);
395+
return newEmptyChild;
396+
} else {
397+
// optimisation: no id state to preserve so we can just insert a clone of the newChild and its descendants
398+
const newClonedChild = document.importNode(newChild, true); // importNode to not mutate newParent
399+
oldParent.insertBefore(newClonedChild, insertionPoint);
400+
return newClonedChild;
401+
}
402+
});
379403
}
380404

381405
//=============================================================================
@@ -505,9 +529,9 @@ var Idiomorph = (function () {
505529
moveBefore(ctx.pantry, node, null);
506530
} else {
507531
// remove for realsies
508-
if (ctx.callbacks.beforeNodeRemoved(node) === false) return;
509-
node.parentNode?.removeChild(node);
510-
ctx.callbacks.afterNodeRemoved(node);
532+
withNodeCallbacks(ctx, "Removed", node, () => {
533+
return node.parentNode?.removeChild(node);
534+
});
511535
}
512536
}
513537

@@ -1300,5 +1324,6 @@ var Idiomorph = (function () {
13001324
return {
13011325
morph,
13021326
defaults,
1327+
addPlugin,
13031328
};
13041329
})();

test/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ <h2>Mocha Test Suite</h2>
4545
<script src="hooks.js"></script>
4646
<script src="htmx-integration.js"></script>
4747
<script src="ops.js"></script>
48+
<script src="plugins.js"></script>
4849
<script src="preserve-focus.js"></script>
4950
<script src="restore-focus.js"></script>
5051
<script src="retain-hidden-state.js"></script>

0 commit comments

Comments
 (0)