How I updated my open source plugin from JQUERY to plain JS
Tags: javascript, showdev
Published: 2022-10-06 00:00:18

> I've been wanting to post something for a while but have never really done it before, it's quite scary. Anyway, let's dive into it!

---

A while back I created a [simple plugin](https://github.com/oriongunning/gridder) to recreate the google images sliding effect. It turned out to be rather popular (> 450 stars) and evolved quite a bit from its infancy.

Recently, I've been getting messages asking to remove JQUERY as a dependency or if there was a JS version. I thought it would be interesting/challenging to test myself to redo it with the least amount of dependencies possible, and in plain javascript.

---

> You can find the result here: [GridderJS](https://github.com/oriongunning/gridder-js)

> And here is a demo: [Demo](https://oriongunning.github.io/gridder-js/demo/)

---

### Overall features
First I had to plan what features I wanted to keep/update:
- Multiple instances on the same page
- Easy to use and customize with CSS & HTML
- Any number of columns
- Expanding preview with static HTML content or dynamic (fetch) HTML content
- Responsive
- Smooth Scrolling
- Old / New Google Images display (expander underneath grid or as a sidebar on the right-hand side)
- Lightweight
- Work out of the box with zero user CSS needed
- Multiple themes
- Plays well with other plugins

---

## Things I learned/struggled with

---

### Website I used heavily
I'm not very familiar with using javascript for dom manipulation, and this website made it very easy to do just that.
- https://youmightnotneedjquery.com/

---

### Parcel.js is awesome
Using [parcel.js](https://parceljs.org/) made it a breeze to set up a build process directly in my package.json file as seen in the snippet below. It generates the plugin as a ES6 module & CommonJS module & a standalone js file. Everything is minified and compressed automatically. I barely scratch the surface of what's is capable of doing, and cannot wait to use it again.

```
 {
  "source": "src/gridder-js.js",
  "main": "dist/gridder-js.js",
  "module": "dist/gridder-js.mjs",
  "standalone": "dist/gridder-js-min.js",
  "targets": {
    "main": {
      "source": "src/gridder-js.js"
    },
    "module": {
      "source": "src/gridder-js.js"
    },
    "standalone": {
      "source": "tool/gridderjs-global.js",
      "outputFormat": "global",
      "optimize": true
    },
    "demo": {
      "source": "demo/demo.scss"
    }
  },
  "scripts": {
    "watch": "parcel watch",
    "build": "parcel build"
  },
}
```

---

### How I dealt with the grid

Google Images now show expander on the right-hand side instead of underneath the current row of the selected image so I had to build in an option to toggle between each layout which was not as simple as I originally thought. But with a minimum amount of if's and else + minimum amount of CSS, it's now working quite well,

**Basic columns**
Via the columns option, it's very easy to specify how many columns you want. This was easily achieved using CSS grids
```
display: 'grid';
grid-template-columns: 'repeat('+this.options.columns+', 1fr)';
```

**Display Bottom - Old style**
The important thing on this one was to have the below on the parent element 
```
display: 'grid'; 
grid-auto-flow: 'row dense';
grid-template-rows= 'min-content';
```
**Display Right - New style**
For this option, I had to restructure the JS to allow appending a sidebar at 30% while reducing the main content to 70%. Additionally, I added the below to the sidebar to make it sticky:
```
position: sticky; 
min-height: 100vh;
```

> I could not find a way to have a right-hand sidebar within the CSS grid

---

### Private & Public functions
Thanks to the addition of [Private class features](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields), I can have a #functionName, and call it with this.#functionName. 

I could very easily expose any public function to users, in my case, I added an update(options) & destroy() method.

---

### Adding a responsive option was hard

Previously, I was letting the user use CSS media queries to achieve whatever grid they wanted, but many people wanted a more straightforward option

Finding an easy way to implement responsiveness turned out to be rather difficult, however, I'm pretty happy with the results I came up with.

```
{
    // default options
    columns: 4,
    gap: 15,

    // responsive breakpoints 
    // must be from highest to lowest width
    breakpoints: {
        960: {
            columns: 4,
            gap: 15,
        },
        700: {
            columns: 3,
            gap: 5,
        },
        400: {
            columns: 2,
            gap: 5,
            display: 'bottom',
        }
    },
}
```

With the information above I create an array of breakpoints available, then I have a listener that triggers on window resize, finds the closest breakpoint, and call an update() with the provided options.

```
// Resize Event capture every 500ms
var resizeTimer;
window.addEventListener('resize', function(e){
  clearTimeout(resizeTimer);
  resizeTimer = setTimeout(function() {
    GridderJS.resizeGridder();     
  }, 500);  
});


// Resize all gridder instances.
// a bit ugly, but works: to be refactored once I find a better way to make it work. suggestions, anyone?
GridderJS.resizeGridder = function () {
  // get window width  
  let wWidth = window.innerWidth;

  // iterate through each instance
  GridderJS.instances.forEach((gridder) => {

    // if no additional breakpoint is specified, then skip
    if(gridder.breakpoints.length < 2) return false;

    // else iterate through all breakpoint options
    for(const n in gridder.breakpoints) {

      // skip first
      if(n > 0){
        let breakpoint = gridder.breakpoints[n];

        // update 
        if(wWidth <= breakpoint[0]){
          gridder.update(breakpoint[1]);
          break;
        }
      }

      // if all else fails use the default
      gridder.update(gridder.breakpoints[0][1]);
    };

  });
};
```

---

### Working with other plugins

I've added a few callbacks which allow users to easily interact with other plugins, the demo link shows how I use lazyload.js to lazy load all images.

```
// just call the init function passed in the user options
this.call.option.init();
```

```
// init callback
var gridder = new GridderJS('.gridder', {
    'columns': 8,
    'gap': 15,
    'init': function(el){
        var myLazyLoad = new LazyLoad({
            container: this.element
        });
    },
});

// update callback
gridder.update({ 'columns': 4 });

// destroy callback
gridder.destroy();
```

---
### Things I'm happy with

- Overall features were all implemented
- Nearly zero dependencies (just-extend)
- Performant
- Lightweight (2.6 kB gzipped)

---

### Ending Notes

That's it, we went through some of the things I struggled with & things I learned along the way.

I could probably improve the plugin massively and refactor it to infinity, but I'm very happy with the current result and performance.

I hope you enjoyed reading this article.

Cheers, 
Orion