Skip to content

Commit

Permalink
proof-of-concept for a plugin system.
Browse files Browse the repository at this point in the history
  • Loading branch information
botandrose-machine committed Feb 16, 2025
1 parent 5f878eb commit e6e0d13
Show file tree
Hide file tree
Showing 4 changed files with 343 additions and 20 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,26 @@ Idiomorph.morph(document.documentElement, newPageSource, {head:{style: 'morph'}}

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

### Plugins

Idiomorph supports a plugin system that allows you to extend the functionality of the library, by registering an object of callbacks:

```js
Idiomorph.registerPlugin({
name: 'logger',
onBeforeNodeAdded: function(node) {
console.log('Node added:', node);
},
onBeforeNodeRemoved: function(node) {
console.log('Node removed:', node);
},
});

Idiomorph.plugins // { logger: { ...} };
```

These callbacks will be called in addition to any other callbacks that are registered in `Idiomorph.morph`. Multiple plugins can be registered.

### Setting Defaults

All the behaviors specified above can be set to a different default by mutating the `Idiomorph.defaults` object, including
Expand Down
65 changes: 45 additions & 20 deletions src/idiomorph.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ var Idiomorph = (function () {
restoreFocus: true,
};

let plugins = {};
function addPlugin(plugin) {

Check failure on line 148 in src/idiomorph.js

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'plugin' implicitly has an 'any' type.

Check failure on line 148 in src/idiomorph.js

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'plugin' implicitly has an 'any' type.
plugins[plugin.name] = plugin;

Check failure on line 149 in src/idiomorph.js

View workflow job for this annotation

GitHub Actions / typecheck

Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{}'.

Check failure on line 149 in src/idiomorph.js

View workflow job for this annotation

GitHub Actions / typecheck

Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{}'.
}

/**
* Core idiomorph function for morphing one DOM tree to another
*
Expand Down Expand Up @@ -348,6 +353,26 @@ var Idiomorph = (function () {
}
}

function withNodeCallbacks(ctx, name, node, fn) {

Check failure on line 356 in src/idiomorph.js

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'ctx' implicitly has an 'any' type.

Check failure on line 356 in src/idiomorph.js

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'name' implicitly has an 'any' type.

Check failure on line 356 in src/idiomorph.js

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'node' implicitly has an 'any' type.

Check failure on line 356 in src/idiomorph.js

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'fn' implicitly has an 'any' type.

Check failure on line 356 in src/idiomorph.js

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'ctx' implicitly has an 'any' type.

Check failure on line 356 in src/idiomorph.js

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'name' implicitly has an 'any' type.

Check failure on line 356 in src/idiomorph.js

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'node' implicitly has an 'any' type.

Check failure on line 356 in src/idiomorph.js

View workflow job for this annotation

GitHub Actions / typecheck

Parameter 'fn' implicitly has an 'any' type.
const allPlugins = [...Object.values(plugins), ctx.callbacks];

const shouldAbort = allPlugins.some((plugin) => {
const beforeFn = plugin[`beforeNode${name}`];
return beforeFn && beforeFn(node) === false;
});

if (shouldAbort) return;

const resultNode = fn();

allPlugins.reverse().forEach((plugin) => {
const afterFn = plugin[`afterNode${name}`];
afterFn && afterFn(resultNode);
});

return resultNode;
}

/**
* This performs the action of inserting a new node while handling situations where the node contains
* elements with persistent ids and possible state info we can still preserve by moving in and then morphing
Expand All @@ -359,23 +384,22 @@ var Idiomorph = (function () {
* @returns {Node|null}
*/
function createNode(oldParent, newChild, insertionPoint, ctx) {
if (ctx.callbacks.beforeNodeAdded(newChild) === false) return null;
if (ctx.idMap.has(newChild)) {
// node has children with ids with possible state so create a dummy elt of same type and apply full morph algorithm
const newEmptyChild = document.createElement(
/** @type {Element} */ (newChild).tagName,
);
oldParent.insertBefore(newEmptyChild, insertionPoint);
morphNode(newEmptyChild, newChild, ctx);
ctx.callbacks.afterNodeAdded(newEmptyChild);
return newEmptyChild;
} else {
// optimisation: no id state to preserve so we can just insert a clone of the newChild and its descendants
const newClonedChild = document.importNode(newChild, true); // importNode to not mutate newParent
oldParent.insertBefore(newClonedChild, insertionPoint);
ctx.callbacks.afterNodeAdded(newClonedChild);
return newClonedChild;
}
return withNodeCallbacks(ctx, "Added", newChild, () => {
if (ctx.idMap.has(newChild)) {
// node has children with ids with possible state so create a dummy elt of same type and apply full morph algorithm
const newEmptyChild = document.createElement(
/** @type {Element} */ (newChild).tagName,
);
oldParent.insertBefore(newEmptyChild, insertionPoint);
morphNode(newEmptyChild, newChild, ctx);
return newEmptyChild;
} else {
// optimisation: no id state to preserve so we can just insert a clone of the newChild and its descendants
const newClonedChild = document.importNode(newChild, true); // importNode to not mutate newParent
oldParent.insertBefore(newClonedChild, insertionPoint);
return newClonedChild;
}
});
}

//=============================================================================
Expand Down Expand Up @@ -505,9 +529,9 @@ var Idiomorph = (function () {
moveBefore(ctx.pantry, node, null);
} else {
// remove for realsies
if (ctx.callbacks.beforeNodeRemoved(node) === false) return;
node.parentNode?.removeChild(node);
ctx.callbacks.afterNodeRemoved(node);
withNodeCallbacks(ctx, "Removed", node, () => {
return node.parentNode?.removeChild(node);
});
}
}

Expand Down Expand Up @@ -1300,5 +1324,6 @@ var Idiomorph = (function () {
return {
morph,
defaults,
addPlugin,
};
})();
1 change: 1 addition & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ <h2>Mocha Test Suite</h2>
<script src="hooks.js"></script>
<script src="htmx-integration.js"></script>
<script src="ops.js"></script>
<script src="plugins.js"></script>
<script src="preserve-focus.js"></script>
<script src="restore-focus.js"></script>
<script src="retain-hidden-state.js"></script>
Expand Down
Loading

0 comments on commit e6e0d13

Please sign in to comment.