DEV Community

Professional Joe
Professional Joe

Posted on

The Declarative vs Imperative DOM Manipulation Divide: A jQuery Conversion That Opened My Eyes

I recently stumbled upon something that made me question everything I thought I knew about DOM manipulation. It started with a simple request: "Can you help convert this to jQuery to see how long the code would be?"

What I discovered wasn't just about line count—it was about two fundamentally different philosophies of web development that couldn't be more different in their approach to the same problem.

The Original: Beauty in Simplicity

Here's the code I was asked to convert. It's written in a framework called Juris:

juris.enhance('.data-table', (ctx) => ({
  // Header cells become sortable
  'th[data-column]': {
    className: (el) => () => {
      const column = el.dataset.column;
      const sortColumn = ctx.getState('sort.column');
      const sortDirection = ctx.getState('sort.direction');

      let classes = 'sortable';
      if (sortColumn === column) {
        classes += sortDirection === 'asc' ? ' sort-asc' : ' sort-desc';
      }
      return classes;
    },

    onclick: (el) => () => {
      const column = el.dataset.column;
      const currentColumn = ctx.getState('sort.column');
      const currentDirection = ctx.getState('sort.direction');

      let newDirection = 'asc';
      if (currentColumn === column && currentDirection === 'asc') {
        newDirection = 'desc';
      }

      ctx.setState('sort.column', column);
      ctx.setState('sort.direction', newDirection);
      sortTable(column, newDirection, el.dataset.type);
    }
  },

  // Table rows get hover effects
  'tbody tr': {
    style: () => ({
      transition: 'all 0.3s ease'
    })
  },

  // Status badges within table cells
  '.status': {
    style: (el) => () => {
      const status = el.textContent.toLowerCase();
      return {
        backgroundColor: status === 'active' ? '#28a745' :
                        status === 'pending' ? '#ffc107' : '#dc3545',
        color: 'white',
        padding: '4px 8px',
        borderRadius: '4px'
      };
    }
  }
}));
Enter fullscreen mode Exit fullscreen mode

At first glance, this looked clean, but I didn't think much of it. "Sure," I thought, "I can convert this to jQuery. How hard could it be?"

The jQuery Reality Check

Here's what the equivalent jQuery code looked like:

function enhanceTable(selector) {
    const $table = $(selector);

    // Initialize state storage on the table element
    $table.data('sort', { column: null, direction: 'asc' });

    // Helper functions for state management
    function getState(key) {
        const sortData = $table.data('sort');
        const keys = key.split('.');
        let value = sortData;
        for (let k of keys) {
            value = value[k];
        }
        return value;
    }

    function setState(key, value) {
        const sortData = $table.data('sort');
        const keys = key.split('.');
        let obj = sortData;
        for (let i = 0; i < keys.length - 1; i++) {
            obj = obj[keys[i]];
        }
        obj[keys[keys.length - 1]] = value;
        $table.data('sort', sortData);
        updateHeaderClasses();
    }

    // Function to update header classes based on current sort state
    function updateHeaderClasses() {
        $table.find('th[data-column]').each(function() {
            const $th = $(this);
            const column = $th.data('column');
            const sortColumn = getState('sort.column');
            const sortDirection = getState('sort.direction');

            // Remove existing sort classes
            $th.removeClass('sortable sort-asc sort-desc');

            // Add base sortable class
            $th.addClass('sortable');

            // Add direction class if this is the active sort column
            if (sortColumn === column) {
                $th.addClass(sortDirection === 'asc' ? 'sort-asc' : 'sort-desc');
            }
        });
    }

    // Header cells become sortable
    $table.find('th[data-column]').each(function() {
        const $th = $(this);
        const column = $th.data('column');

        // Add click handler
        $th.on('click', function() {
            const currentColumn = getState('sort.column');
            const currentDirection = getState('sort.direction');

            let newDirection = 'asc';
            if (currentColumn === column && currentDirection === 'asc') {
                newDirection = 'desc';
            }

            setState('sort.column', column);
            setState('sort.direction', newDirection);

            // Sort the table
            if (typeof sortTable === 'function') {
                sortTable(column, newDirection, $th.data('type'));
            }
        });
    });

    // Table rows get hover effects and transitions
    $table.find('tbody tr').css({
        'transition': 'all 0.3s ease'
    });

    // Status badges within table cells
    $table.find('.status').each(function() {
        const $status = $(this);
        const status = $status.text().toLowerCase();

        let backgroundColor;
        if (status === 'active') {
            backgroundColor = '#28a745';
        } else if (status === 'pending') {
            backgroundColor = '#ffc107';
        } else {
            backgroundColor = '#dc3545';
        }

        $status.css({
            'background-color': backgroundColor,
            'color': 'white',
            'padding': '4px 8px',
            'border-radius': '4px'
        });
    });

    // Initial setup of header classes
    updateHeaderClasses();
}
Enter fullscreen mode Exit fullscreen mode

The result? 40 lines became 85 lines. But the line count was the least shocking part.

What Hit Me Like a Truck

Looking at these two pieces of code side by side, I realized I was witnessing two completely different philosophies of programming:

The Declarative Approach (Juris)

  • "Here's what I want" - The code reads like a specification
  • Nested structure - Mirrors the HTML hierarchy you're working with
  • Reactive by default - Classes and styles automatically update when state changes
  • Pure functions - Each property is a function of current state, no side effects

The Imperative Approach (jQuery)

  • "Here's how to do it" - The code reads like a step-by-step manual
  • Scattered logic - State management, event handling, and styling are mixed together
  • Manual coordination - You must remember to call update functions when state changes
  • Side effects everywhere - Mutating DOM, managing state, coordinating updates

The Readability Gap

The Juris version reads almost like CSS-in-JS: "Table headers with data-column should have these classes based on sort state, and when clicked should update the sort state."

The jQuery version reads like assembly instructions: "Find all the headers, loop through them, attach click handlers, create state management functions, remember to update classes manually when state changes, don't forget to call the initial setup..."

In Juris, you focus on what you want. In jQuery, you focus on how to implement it.

The Maintenance Nightmare

But here's where it gets really interesting. In the jQuery version, if I want to add a new sort state (like "unsorted"), I need to:

  1. Update the state management logic
  2. Update the class calculation logic
  3. Update the click handler logic
  4. Remember to update the initial setup
  5. Make sure all the manual coordination still works

In the Juris version, I just update the className function. The reactive system handles the rest.

The jQuery approach doesn't just have more code—it has more places where bugs can hide.

What This Means for Modern Development

This comparison made me realize why modern frameworks like React, Vue, and Svelte have gained such traction. It's not just about performance or bundle size—it's about the fundamental shift from imperative to declarative programming.

But here's what's interesting about Juris: it brings declarative programming directly to DOM manipulation, without the overhead of a virtual DOM. It's like having the best parts of modern frameworks, but applied directly to the browser's native elements.

The Questions This Raises

Seeing this comparison left me with some burning questions:

  • Have we been overcomplicating DOM manipulation all these years?
  • Is there a middle ground between jQuery's imperative approach and full framework overhead?
  • What if reactive DOM updates could be as simple as declaring what you want?

Final Thoughts

I went into this thinking I was just converting some code from one library to another. I came out questioning fundamental assumptions about how we build web interfaces.

The jQuery era taught us how to manipulate the DOM programmatically. Modern frameworks taught us to think declaratively. But maybe there's still room for innovation in how we bridge that gap.

Juris might be onto something here. The fact that it can express complex, stateful DOM interactions so concisely suggests there are still unexplored territories in web development tooling.

What do you think? Are we ready for a new approach to DOM manipulation, or is the jQuery-to-framework evolution already complete?


Have you experienced similar "aha moments" when comparing different approaches to the same problem? I'd love to hear about them in the comments.

Top comments (3)

Collapse
 
szagi3891 profile image
Grzegorz Szeliga

Great insights.

If you'd like to go further down the rabbit hole, check out solidjs.com/.

This library is 100% reactive. An additional interesting feature is that it doesn't use a virtual DOM (like React does), which makes it incredibly fast.

Collapse
 
artydev profile image
artydev

Look at Juris site, one single file HTML rendered in around :

Image description

No bundler, no mess with nodejs modules, pure HTML/CSS/JS, personaly i don't need more.

Collapse
 
professional_joe profile image
Professional Joe

Solid is great for what it is, it pioneered signal, but I find signal is not the future for state transparency and domain-based global state. Solid was not designed to no-build, non-blocking, lazy component rendering and the philosophy is more around Signal. while Juris is questioning all the complexity that all the Phd's from React had created. All framework that relies on JSX relies on build that needs 6-12 months adaptions before going mainstream. While juris can release features, bug fixes weekly.