A DOM-merging algorithm

Overview

Idiomorph

Idiomorph is a javascript library for morphing one DOM tree to another. It is inspired by other libraries that pioneered this functionality:

  • morphdom - the original DOM morphing library
  • nanomorph - an updated take on morphdom

Both morphdom and nanomorph use the id property of a node to match up elements within a given set of sibling nodes. When an id match is found, the existing element is not removed from the DOM, but is instead morphed in place to the new content. This preserves the node in the DOM, and allows state (such as focus) to be retained.

However, in both these algorithms, the structure of the children of sibling nodes is not considered when morphing two nodes: only the ids of the nodes are considered. This is due to performance: it is not feasible to recurse through all the children of siblings when matching things up.

id sets

Idiomorph takes a different approach: before node-matching occurs, both the new content and the old content are processed to create id sets, a mapping of elements to a set of all ids found within that element. That is, the set of all ids in all children of the element, plus the element's id, if any.

Id sets can be computed relatively efficiently via a query selector + a bottom up algorithm.

Given an id set, you can now adopt a broader sense of "matching" than simply using id matching: if the intersection between the id sets of element 1 and element 2 is non-empty, they match. This allows Idiomorph to relatively quickly match elements based on structural information from children, who contribute to a parent's id set, which allows for better overall matching when compared with simple id-based matching.

Usage

Idiomorph is a small (1.7k min/gz'd), dependency free JavaScript library, and can be installed via NPM or your favorite dependency management system under the Idiomorph dependency name. You can also include it via a CDN like unpkg to load it directly in a browser:

<script src="https://unpkg.com/idiomorph"></script>

Or you can download the source to your local project.

Idiomorph has a very simple usage:

  Idiomorph.morph(existingNode, newNode);

This will morph the existingNode to have the same structure as the newNode. Note that this is a destructive operation with respect to both the existingNode and the newNode.

You can also pass string content in:

  Idiomorph.morph(existingNode, "<div>New Content</div>");

And it will be parsed and merged into the new content.

If you wish to target the innerHTML rather than the outerHTML of the content, you can pass in a morphStyle in a third config argument:

  Idiomorph.morph(existingNode, "<div>New Content</div>", {morphStyle:'innerHTML'});

This will replace the inner content of the existing node with the new content.

htmx

Idiomorph was created to integrate with htmx and can be used as a swapping mechanism by including the Idiomorph-ext file in your HTML:

<script src="https://unpkg.com/idiomorph/dist/idiomorph-ext.min.js"></script>
<div hx-ext="morph">
    
    <button hx-get="/example" hx-swap="morph:innerHTML">
        Morph My Inner HTML
    </button>

    <button hx-get="/example" hx-swap="morph:outerHTML">
        Morph My Outer HTML
    </button>
    
    <button hx-get="/example" hx-swap="morph">
        Morph My Outer HTML
    </button>
    
</div>

Performance

Idiomorph is not designed to be as fast as either morphdom or nanomorph. Rather, its goals are:

  • Better DOM tree matching
  • Relatively simple code

Performance is a consideration, but better matching is the reason Idiomorph was created. Initial tests indicate that it is approximately equal to 10% slower than morphdom for large DOM morphs, and equal to or faster than morphdom for smaller morphs.

Example Morph

Here is a simple example of some HTML in which Idiomorph does a better job of matching up than morphdom:

Initial HTML

<div>
    <div>
        <p id="p1">A</p>
    </div>
    <div>
        <p id="p2">B</p>
    </div>
</div>

Final HTML

<div>
    <div>
        <p id="p2">B</p>
    </div>
    <div>
        <p id="p1">A</p>
    </div>
</div>

Here we have a common situation: a parent div, with children divs and grand-children divs that have ids on them. This is a common situation when laying out code in HTML: parent divs often do not have ids on them (rather they have classes, for layout reasons) and the "leaf" nodes have ids associated with them.

Given this example, morphdom will detach both #p1 and #p2 from the DOM because, when it is considering the order of the children, it does not see that the #p2 grandchild is now within the first child.

Idiomorph, on the other hand, has an id set for the (id-less) children, which includes the ids of the grandchildren. Therefore, it is able to detect the fact that the #p2 grandchild is now a child of the first id-less child. Because of this information it is able to only move/detach one grandchild node, #p1. (This is unavoidable, since they changed order)

So, you can see, by computing id sets for nodes, idiomoroph is able to achieve better DOM matching, with fewer node detachments.

Demo

You can see a practical demo of Idiomorph out-performing morphdom (with respect to DOM stability, not performance) here:

https://github.com/bigskysoftware/Idiomorph/blob/main/test/demo/video.html

For both algorithms, this HTML:

<div>
    <div>
        <h3>Above...</h3>
    </div>
    <div>
        <iframe id="video" width="422" height="240" src="https://www.youtube.com/embed/dQw4w9WgXcQ"
                title="Rick Astley - Never Gonna Give You Up (Official Music Video)" frameborder="0"
                allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
                allowfullscreen></iframe>
    </div>
</div>

is moprhed into this HTML:

<div>
    <div>
        <iframe id="video" width="422" height="240" src="https://www.youtube.com/embed/dQw4w9WgXcQ"
                title="Rick Astley - Never Gonna Give You Up (Official Music Video)" frameborder="0"
                allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
                allowfullscreen></iframe>
    </div>
    <div>
        <h3>Below...</h3>
    </div>
</div>

Note that the iframe has an id on it, but the first-level divs do not have ids on them. This means that morphdom is unable to tell that the video element has moved up, and the first div should be discarded, rather than morphed into, to preserve the video element.

Idiomorph, however, has an id-set for the top level divs, which includes the id of the embedded child, and can see that the video has moved to be a child of the first element in the top level children, so it correctly discards the first div and merges the video content with the second node.

You can see visually that idiomoroph is able to keep the video running because of this, whereas morphdom is not:

Rick Roll Demo

To keep things stable with morphdom, you would need to add ids to at least one of the top level divs.

You might also like...

Fast and robust triangle-triangle intersection test with high precision for cross and coplanar triangles based on the algorithm by Devillers & Guigue.

fast-triangle-triangle-intersection Fast and robust triangle-triangle intersection test with high precision for cross and coplanar triangles based on

Nov 15, 2022

⭐️ my baekjoon algorithm

Baekjoon Algorithm Python3 import sys # 공백으로 구분된 2개 숫자 입력 받기 N, M = map(int, sys.stdin.readline().split()) # 여러 줄 입력 받기 n = int(sys.stdin.readline

Jul 22, 2022

k-means algorithm module for n-dimensional data

K-Means Algorithm This module allows you to compute the k-Means algorithm with n-dimensional data. You simply put in your data as a list and the k you

Jan 31, 2022

This project will be using various AI and Rule Engine algorithm to detect various attack against a company!

This project will be using various AI and Rule Engine algorithm to detect various attack against a company!

📌 Introduction This project will be using various AI and Rule Engine algorithm to detect various attack against a website! 📌 Mission After starting

Apr 29, 2022

A Flood-Fill Algorithm written in JavaScript

flood https://magnogen.net/flood A Flood-Fill Algorithm for Creative Coders This is the source code for a section of my website. You're welcome to sno

Dec 22, 2022

Deno's first lightweight, secure distributed lock manager utilizing the Redlock algorithm

Deno-Redlock Description This is an implementation of the Redlock algorithm in Deno. It is a secure, lightweight solution to control resource access i

Dec 31, 2022

An algorithm for fast 2D pattern-matching with wildcards.

An algorithm for fast 2D pattern-matching with wildcards.

pattern-match-2d.js An algorithm for fast 2D pattern-matching with wildcards, with a demo app inspired by MarkovJunior (by Maxim Gumin). The algorithm

Nov 5, 2022

The Google Earth Engine implementation of the BioNet algorithm to estimate biophysical parameters along with their uncertainties.

The Google Earth Engine implementation of the BioNet algorithm to estimate biophysical parameters along with their uncertainties.

ee-BioNet The Google Earth Engine implementation of the BioNet algorithm to estimate biophysical parameters along with their uncertainties. Quantifyin

Oct 30, 2022

Onchain private messaging app with a significant encryption algorithm.

Onchain private messaging app with a significant encryption algorithm.

Hedwig DEMO We want to implement SSL technology to blockchain so decided to build onchain private messaging app. Diffie Hellman protocol was invented

Nov 3, 2022
Comments
  • open to callbacks?

    open to callbacks?

    StimulusReflex folks here. This is a really exciting vision, kudos for your approach.

    I happen to agree that sacrificing a bit of performance for much more reliable outcomes is a win. We actually keep a checklist of weird morphdom shit and boy, would I love to be able to delete it.

    We're considering jumping ship for a few reasons beyond edge case gotchas, mostly relating to callbacks. There is a onBeforeElUpdated callback, but they don't have anything for text nodes, or text <-> element conversions with incompatible types. We use onBeforeElUpdated to allow SR developers to mark elements as "permanent" via a data-reflex-permanent attribute. It works for element nodes, but people are constantly asking us why morphing a string can't be prevented.

    Are you open to providing a callback that would halt all morphing for an element and its children if it returns false?

    The other thing is a nice-to-have, and that's a callback for when all nodes have been processed. This has been extensively discussed but nothing seems to have solidified.

    Finally, as a bonus round question: are you open to implementing the equivalent to morphdom's childrenOnly option?

    opened by leastbad 11
  • Full page morphing

    Full page morphing

    Will morphing whole pages be supported? I'd love a library where you can throw anything at it, and it would "just work". Even including the head tag.

    opened by kpietraszko 9
  • Might want to look at isEqualNode and isSameNode for a possible performance bump

    Might want to look at isEqualNode and isSameNode for a possible performance bump

    Stoked to see someone else working on this problem 🙌 https://developer.mozilla.org/en-US/docs/Web/API/Node/isEqualNode https://developer.mozilla.org/en-US/docs/Web/API/Node/isSameNode

    opened by kristoferjoseph 1
Owner
Big Sky Software
We find hot new industry trends & then do the opposite of that...
Big Sky Software
Custom Vitest matchers to test the state of the DOM, forked from jest-dom.

vitest-dom Custom Vitest matchers to test the state of the DOM This library is a fork of @testing-library/jest-dom. It shares that library's implement

Chance Strickland 14 Dec 16, 2022
An extension of DOM-testing-library to provide hooks into the shadow dom

Why? Currently, DOM-testing-library does not support checking shadow roots for elements. This can be troublesome when you're looking for something wit

Konnor Rogers 28 Dec 13, 2022
A personal semantic search engine capable of surfacing relevant bookmarks, journal entries, notes, blogs, contacts, and more, built on an efficient document embedding algorithm and Monocle's personal search index.

Revery ?? Revery is a semantic search engine that operates on my Monocle search index. While Revery lets me search through the same database of tens o

Linus Lee 215 Dec 30, 2022
Algorithm visualizer made with React, Material UI and P5JS.

Made with React, P5JS and Material UI. Link https://andresrodriguez55.github.io/algorithmsVisualizer/#/ Description The purpose of doing this was to l

Andres Arturo Rodriguez Calderon 31 Nov 22, 2022
JS_GAME/Algorithm

?? JS_GAME JavaScript 를 이용하여 각종 게임을 만들어보자. (자바스크립트를 사용해서 게임을 만들 수 있음.) 웹페이지를 만드는 것도 좋지만, JS 를 이용하여 간단한 게임을 만들면서 생각의 전환을 해보자. 클론 코딩도 좋다! 하지만, 클론 코딩 후에

LEEJAE-GO 2 Nov 5, 2021
NodeJS Implementation of Decision Tree using ID3 Algorithm

Decision Tree for Node.js This Node.js module implements a Decision Tree using the ID3 Algorithm Installation npm install decision-tree Usage Import

Ankit Kuwadekar 204 Dec 12, 2022
A deterministic object hashing algorithm for Node.js

Deterministic-Object-Hash A deterministic object hashing algorithm for Node.js. The Problem Using JSON.stringify on two objects that are deeply equal

Zane Bauman 7 Nov 30, 2022
Dominating set solver using quantum algorithm Grover

Solution for dominating set problem using improved quantum algorithm Grover, which uses Schoning algorithm for k-SAT problem to accomplish this improvement

Fatemehe Lajevardi 8 Aug 31, 2022
Non-interactive publicly verifiable distributed key generation and resharing algorithm over BLS12-381

NPVDKG-RS This repository contains a mathematical presentation and some code to demonstrate our developed non-interactive publicly verifiable distribu

NATRIX Official 8 May 19, 2022
🤖A Tic-Tac-Toe solver that uses the minimax algorithm and alpha-beta pruning to make it unbeatable

Tic-Tac-Toe AI A Tic-Tac-Toe solver that uses the minimax algorithm and alpha-beta pruning to make it unbeatable How it Works Tic-Tac-Toe is what is k

Martin 4 May 20, 2022