The DOM and Managing Complexity in API Evolution

Here’s a confession: I have a love/hate relationship with DOM related APIs.

You see, I love having a standard surface and that we have helped improve it in a lot of ways – there are some really great improvements.  But I still hate the reality of working with it sometimes. Originally I thought that it was merely because we know a lot of common usage patterns and while they are entirely accomplishable with existing APIs they currently tend to require a lot of boilerplate.  As I use them I often feel a dissonance between how simple the thing i am trying to describe is conceptually versus how much it really takes to accomplish — and how much that boilerplate can differ depending on which APIs i am dealing with.  Some of it is just sugar, sure, but recently I am thinking there might be more to it…

It’s frustrating when I see developers I know try to stick to native but eventually go and grab jQuery just for its DOM goodness, but – I kinda get it and it makes me think that maybe we’re missing something kind of fundamental.  Maybe not, but I’d like to throw it out there and see.  I’m happy to be wrong.

First, let me describe the more generalized problem as I see it, using the DOM APIs as an example:

  • Even as fundamentally new capabilities arrive, we are constantly attempting to simultaneous align the new trajectory of the bright and glorious future with the very real requirement of “Don’t break the Web”. 
  • The combination of these competing factors mean that legacy inevitably co-exists with things that have a different (hopefully better) new view on the world, but, at least for the near future (pronounced “years”) cannot completely eliminate the legacy.  In the case of DOM APIs, Arrays of Elements will live along side and cooperate with live NodeLists and Elements and jQuery array likes for years to come, and we’re still tweaking…
  • The crux then is that the true state of affairs is that developers work with APIs that are in a state of perpetual transition between the mediocre past and the increasingly better future.  

 Just add this, over there…

A developer knows that conceptually we want to “just add this thing over here” in the DOM tree.  Seems simple enough.  But:  Given the state of things, Is the thing we want to add an Element? An Array of Elements? A node list?  Or, god forbid, a fragment of HTML serialization? Worse still is if that serialization is something that requires context (a table row, for example).  Or – is it some new thing?  Think about it – how you approach the problem is affected by which of these “over here” is.

Yikes.

Yes, It’s all accomplishable. Yes, and it’s way better than where we were 5 years ago.  But it leaves developers wanting and leaves a lot of room for error.  I have to use Array.prototype to turn something that is *almost* an array into an array, I have to worry about whether I am talking about a single element or an array of elements, or a NodeList.  It all seems unnecessarily complex and error prone at some level.

It sees that sometimes, as in this case, perhaps we could recognize that this will be a common evolutionary step and perhaps we could stabilize continuity by way of a unifying API based on the common concepts?  

And if you think about it, it’s sort of the one thing core thing that jQuery does that still isn’t currently a proposed standard.

A proposal to get things started…

Let’s imagine that we create a new unification API based on the common concepts:  Select one or more points in the DOM to modify and then perform modification on them.  Let’s call them DOMModificationTargets in keeping with the generally unfriendly tradition – aliases and bike-shedding a better name are easy.

DOMModificationTargets is a constructible abstraction with an underlying array that allows you to perform common patterns of DOM operations on All of the Things

// A dead-simple unified API for conceptually similar 
// past, present and future APIs... It always freezes and
// exposes an underlying array
class DOMModificationTargets {

  // Create targets object via Any of the Things...
  DOMModificationTargets constructor(string markup);
  DOMModificationTargets ,constructor(Node node);
  DOMModificationTargets constructor(array elements);
  DOMModificationTargets constructor(NodeList list);

  // Perform conceptually common modifications on them...
  DOMModificationTargets append(string markup);
  DOMModificationTargets append(Node node);
  DOMModificationTargets append(array elements);
  DOMModificationTargets append(NodeList list);

  DOMModificationTargets prepend(string markup);
  DOMModificationTargets prepend(Node node);
  DOMModificationTargets prepend(array elements);
  DOMModificationTargets prepend(NodeList list);

  DOMModificationTargets replace(string markup);
  DOMModificationTargets replace(Node node);
  DOMModificationTargets replace(array elements);
  DOMModificationTargets replace(NodeList list);
  
  DOMModificationTargets replaceWith(String markup);
  DOMModificationTargets replaceWith(Node node);
  DOMModificationTargets replaceWith(array elements);
  DOMModificationTargets replaceWith(NodeList list);

  DOMModificationTargets addClass(string className);
  DOMModificationTargets removeClass(string className);
  DOMModificationTargets toggleClass(string className);
  DOMModificationTargets setAttribute(string name, string value);
  DOMModificationTargets removeAttribute(string name);

  array collection;

}

I’ve created a quick prollyfill for this on github with a demo – the source is a mere 200 lines of JavaScript.

Given that the underlying operations are the same, it would even be easy to add an extension pattern to this for prollyfilling future API types.

It it worth something to think that occasionally we should look at conceptually unifying/adapter APIs?  What do you think?

1 thought on “The DOM and Managing Complexity in API Evolution

Leave a comment