Rewriting an old project! Part 2: JavaScript
Tags: javascript, css, html, showdev
Published: 2019-04-12 14:48:51

---
title: Rewriting an old project! Part 2: JavaScript
published: true
description: I revisited an old weekend hack of mine from college and decided to rewrite it! Since it's a mostly JavaScript project, this is the part I'm most excited about!
tags: javascript, css, html, showdev
series: Rewriting jSphere
cover_image: https://thepracticaldev.s3.amazonaws.com/i/l3a4q80j3wg5g5m1bh4w.png
---

> **Note**
> This is Part 2 of a miniseries in which I'm revisiting an old weekend hack of mine from college, which I called [jSphere](https://github.com/kenbellows/jsphere) (because I was lazy and couldn't come up with anything more interesting).
>
> If you haven't read [Part 1](https://dev.to/kenbellows/rewriting-an-old-project-part-1-html-css-4o5p), which was all about the HTML and CSS, I recommend giving it a skim, or at least reading the Backstory section at the beginning.
>
> As I said last time, I'm not going to discuss what the code *does* in this post, just its structure and syntax. But I do plan to write a proper walkthrough and tutorial later.

# Last time...

In Part 1, time we went over the markup and styling of , and I already found that pretty interesting. But, I mean, this project is primarily a JavaScript project. It's all about that `<canvas>`!

For reference, here's what we're rebuilding:

{% codepen https://codepen.io/kenbellows/pen/MxZrjy %}


Okay. Where to begin? Man, there was so much to change about this code, and a lot of it was interwoven, which makes it tough to really talk about step-by-step. So what I'll have to do is just... go for it, I guess.

Please, feel free to critique or otherwise comment on the code here! I love feedback and (constructive) criticism, I'm always interested in how other people would approach the same tasks, and I've even been known to enjoy a little [bikeshedding](https://en.wiktionary.org/wiki/bikeshedding) now and then.


# Minutia: nah.

Let me mention up front here that there are some changes I won't explicitly discuss, because they're too minor. Yes, I changed all the `var`s to `const`s and `let`s, I reordered some functions, I changed all the TitleCasedMethods to normalCamelCase (*why did I capitalize the first letter of all my methods? was I insane?*). But you don't care about all those things. I'm going to focus on the really large things, the things I found interesting or funny, and the things that really demonstrate how far the language has come in seven years.

Let's start with a funny one.


# Bro, do you even loop?

I fixed a spot where I had done something super weird, maybe because I was rushing and didn't think it through. This is in `Sphere`'s constructor, when I'm generating all the `Dot` instances needed to represent the points on the surface of the sphere. I use a nested `for` loop to move around the sphere in rings, top to bottom, adding dots to its surface as I went. This is fine, but I wrote the loops in a super weird way:

```javascript
this.points = new Array();
var angstep = Math.PI/10;
var i = 0;
// Loop from 0 to 2*pi, creating one row of points at each step
for (var angxy=0; angxy<2*Math.PI; angxy+=angstep){
    this.points[i] = new Array();
    var j=0;
    for (var angyz=0; angyz<2*Math.PI; angyz+=angstep) {
        // Loop from 0 to 2*pi, creating one point at each step
        var px  = r * Math.cos(angxy) * Math.sin(angyz) + x,
            py  = r * Math.sin(angxy) * Math.sin(angyz) + y,
            pz  = r * Math.cos(angyz) + z,
            pfg = pz > z;
        this.points[i][j] = new Dot(px,py,pz,pfg);
        j++;
    }
    i++;
}
```
What's up with the `i` and `j` variables there, hanging out outside of the loops? Why aren't they created and incremented within the loops? I definitely knew that a `for` loop can have multiple variables; why didn't I just do this?
```
for (var i=0, angxy=0; angxy<2*Math.PI; i++, angxy+=angstep) { ... }
```

But also, why do I even need those index counters in the first place? Why do I use `this.points[i] =` and `this.points[i][j] =` to add to the arrays? Why aren't I just using `[].push()` to add stuff to the array? And why on Earth did I use `new Array()` instead of `[]`? How much OOP was I on???

So I got rid of all that nonsense. I also decided to make `this.points` a single flat list of points, rather than a 2D matrix with rows, because I realized while refactoring some other code that the *only* way I ever referenced `this.points` was with a couple of nested loops to reach each point, usually with weird `for ... in` loops like this:

```javascript
for (var i in this.points) {
    for (var j in this.points[i]) {
        this.points[i][j]...
    }
}
```

First, it does *work*, but using `for...in` loops on arrays like this is strange, and it's unexpected to say the least. I was very confused when I first saw it, and I imagine any other devs reading it would be thrown off as well.

Second, I never used `i` or `j` for anything other than indexing into `this.points`, which makes this a great candidate for the much more elegant `for...of` loop, which didn't exist when I originally wrote this code.

I refactored to fix all of these problems, and here's where I wound up. The constructor loops were updated to this (which also includes a change to the Dot constructor that I'll talk about in a bit):


```javascript
// The angle delta to use when calculating the surface point positions;
// a larger angstep means fewer points on the surface
const angstep = Math.PI/10;
this.points = [];
// Loop from 0 to 2*pi, creating one row of points at each step
for (let angxy=0; angxy<2*Math.PI; angxy+=angstep){
    for (let angyz=0; angyz<2*Math.PI; angyz+=angstep) {
        // Loop from 0 to 2*pi, creating one point at each step
        this.points.push(new Dot({
            x: r * Math.cos(angxy) * Math.sin(angyz) + x,
            y: r * Math.sin(angxy) * Math.sin(angyz) + y,
            z: r * Math.cos(angyz) + z,
            fg: Math.cos(angyz) > 0
        }));
    }
}
```

And the loops over the elements of `this.points` now look like this:

```javascript
for (const point of this.points) {
    point...
}
```

Ahh... So much better. 😌


# Classes!

Since this project was all about this interactive sphere composed of a bunch of dots on its surface, and since I was doing a CS degree in 2012 and the Functional Revolution of the last few years hadn't begun yet, the project is largely composed of two high-level object types:

* `Dot` - represents a single dot on the surface of the sphere
* `Sphere` - represents the whole sphere, with all of its `Dot`s

In 2012, the native `class` was yet to be standardized, so I wrote everything in the old-school `function`-as-a-constructor style. For exapmle, here's part of the `Sphere` constructor:

```javascript
function Dot(x,y,z,fg,fgColor,bgColor) {
    // Default Values:  { x: 0, y: 0, z: 0, fg: true, fgcolor: #7EE37E, bgcolor: "#787878" }
    this.x = typeof x !== 'undefined' ? x : 0;
    this.y = typeof y !== 'undefined' ? y : 0;
    this.z = typeof z !== 'undefined' ? z : 0;
    this.fg = typeof fg !== 'undefined' ? fg : true;
    this.fgColor = typeof fgColor !== 'undefined' ? fgColor : "#7EE37E";
    this.bgColor = typeof bgColor !== 'undefined' ? bgColor : "#787878";
    
    this.Draw = function(ctx) {
        // Store the context's fillStyle
        var tmpStyle = ctx.fillStyle;
        // Set the fillStyle for the dot
        ctx.fillStyle = (this.fg ? this.fgColor : this.bgColor);
        // Draw the dot
        ctx.fillCircle(x,y,this.fg?10:5);
        // Restore the previous fillStyle
        ctx.fillStyle = tmpStyle;
    }
}
```

Now, there's another issue here, which probably had to do with my limited understanding of JS prototypes: every instance of `Dot` defines its own copy of the `Draw` function, rather than referring to a class method. What I should have written is this:

```javascript
function Dot(x,y,z,fg,fgColor,bgColor) {
    // ...
}
Dot.protoype.Draw = function(ctx) {
    // Store the context's fillStyle
    var tmpStyle = ctx.fillStyle;
    // Set the fillStyle for the dot
    ctx.fillStyle = (this.fg ? this.fgColor : this.bgColor);
    // Draw the dot
    ctx.fillCircle(x,y,this.fg?10:5);
    // Restore the previous fillStyle
    ctx.fillStyle = tmpStyle;
}
```

This declares a single copy of the function, rather than wasting memory on a new function every time.

But that's a side issue anyway, since the native `class`es introduced in ES2015 (aka ES6) handles that under the hood!

But before we get to that, let mention one more thing: ES2015 also introduced [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring), [function parameter default values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters), and [using both of those things together](https://davidwalsh.name/destructuring-function-arguments) to basically get the equivalent of named parameters with default values, as seen in languages like Python and Ruby!

Aaaaand one *more* thing: ES2015 *also* introduced [`Object.assign()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign), which I like to use  to initialize `this` with properties much more succinctly than the traditional one-by-one method. (Man, ES2015 was some good stuff!)

Using these two techniques together, I can eliminate this nasty snippet from the the `Dot` constructor:

```javascript
// Gross 🤢
this.x = typeof x !== 'undefined' ? x : 0;
this.y = typeof y !== 'undefined' ? y : 0;
this.z = typeof z !== 'undefined' ? z : 0;
this.fg = typeof fg !== 'undefined' ? fg : true;
this.fgColor = typeof fgColor !== 'undefined' ? fgColor : "#7EE37E";
this.bgColor = typeof bgColor !== 'undefined' ? bgColor : "#787878";
```

Putting all these improvements together, I can rewrite the `Dot` like this:

```javascript
class Dot {
    constructor({x=0, y=0, z=0, fg=true, fgColor='#7EE37E', bgColor='#787878'}={}) {
        Object.assign(this, {x, y, z, fg, fgColor, bgColor});
    }

    draw(ctx) {
        // Store the context's fillStyle
        var tmpStyle = ctx.fillStyle;
        // Set the fillStyle for the dot
        ctx.fillStyle = (this.fg ? this.fgColor : this.bgColor);
        // Draw the dot
        ctx.fillCircle(x, y, this.fg ? 10 : 5);
        // Restore the previous fillStyle
        ctx.fillStyle = tmpStyle;
    }
}
```

So much cleaner!


# Re-separating my concerns

As I was rewriting my classes as I just described, I started to find places where `Dot`s were doing stuff that was better handled by `Sphere`, and `Sphere` was repeatedly performing operations on each `Dot` that could be much more cleanly handled internally by each of them.

First, that `Draw` method of the `Dot` class up there? Yeah, it was never actually being called. It turned out that I needed some context from the `Sphere` about each `Dot` in order to draw it correctly, namely where it was located on the surface of the sphere and how the sphere was currently rotated. And rather than pass all that context around to each `Dot`, it was much cleaner to do all my drawing right from the `Sphere` class. So... I deleted the `Draw` method on `Dot`, as well as the `fgColor` and `bgColor` properties that went with it.

Second, there are two major ways that the `Dot`s are changed to move the `Sphere` around: they're *translated* (moved around), and they're *scaled* (grown or shrunken). I had a bunch of repeated code in my event handlers where each `Dot`'s properties were being manually recalculated and updated in the same way, so I figured I'd [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) up my code and move the logic to change a `Dot` into the `Dot` class itself. I gave `Dot` two new methods: `scale` and `translate`.

After all that, my `Dot` class looks like this:


```javascript
class Dot {
    constructor({x=0, y=0, z=0, fg=true}={}) {
        Object.assign(this, {x, y, z, fg});
    }

    /**
     * Scale this dot's position by multiplying all coordinates by the given scale factor
     * @param {Number} scaleFactor
     */
    scale(scaleFactor) {
        this.x *= scaleFactor
        this.y *= scaleFactor
        this.z *= scaleFactor
    }

    /**
     * Move this dot to a new position indicated by the given x, y, and z distances
     * @param {Number} [x=0]
     * @param {Number} [y=0]
     * @param {Number} [z=0]
     */
    translate({x=0, y=0, z=0}) {
        this.x += x
        this.y += y
        this.z += z
    }
}
```


# Don't mess with the prototype!

I made several changes that came from experience. Before I left college, I really had little to no knowledge of accepted community best practices, or why they were important. For example, I have a few utility functions for drawing on the canvas. In my old code, I added them to the `CanvasRenderingContext2D` prototype directly, as follows:

```javascript
Object.getPrototypeOf(ctx).fillCircle = ctxFillCircle;
```

This is not ideal. Suppose a real `ctx.fillCircle` were added to the spec one day, and it was different from my code. In this minor demo it probably doesn't matter much, as I'm the only one working with the code, but in a project with multiple authors, it could be very confusing to come across a standardized method that looks different than it should. Even future me might be confused!

So I refactored: a `ctx` parameter was added to each function, all references to `this` in the functions were changed to `ctx`,  I removed the above lines that modified the `CanvasRenderingContext2D` prototype, and I changed the code to call the functions directly and pass `ctx` as the first argument rather than calling them as a method of `ctx`.


# Removing jQuery

Finally, jQuery. I love it, it did a lot of good for the web, but in most cases, including mine, it's no longer needed.

I basically used jQuery for three things in this project, two of which are trivially replaced:

* I used the `$(function() { ... })` wrapper to delay code execution until the page loaded. As I discussed in Part 1, this is unnecessary if you add the `defer` attribute to any `<script>` tags that need to wait. ([Thanks again, @crazytim!](https://dev.to/crazytim/comment/a255))
* I used the classic `$()` selector function in several places to get elements. This is now (and actually was then, though I was unfamiliar with it) a native part of the platform with `document.querySelector` and `document.querySelectorAll`.
* The tough one: I used the `jquery.events.drag` plugin to handle mouse-based interactivity. This one takes slightly more doing to replace.

The first two are super basic, and I won't bother showing them. Instead, I'll focus on that third one: the `jquery.event.drag` event plugin.

Here's how I was using it:

```javascript
$("#canvas")
    .drag("init", function(e){
        startX = e.clientX;
        startY = e.clientY;
    })
    .drag(function(e){
        if (e.ctrlKey) {
            if (e.altKey) sphere.HiddenFun2(e.clientX-startX,e.clientY-startY);
            else sphere.Zoom(e.clientX-startX, e.clientY-startY, -1);
        } else if (e.shiftKey) {
            if (e.altKey) sphere.HiddenFun1(e.clientX-startX,e.clientY-startY);
            else sphere.Pan(e.clientX-startX,e.clientY-startY);
        } else {        
            sphere.Rotate(e.clientX-startX,e.clientY-startY);
        }
        startX = e.clientX;
        startY = e.clientY;
    });
```

So when dragging begins, store the starting position of the cursor, then when dragging continues, perform certain actions based on the modifier keys used, and update the cursor position for the next movement.

So how would we do this with regular old `mouse` events?

I implemented it in three stages: drag start, drag, and drag end. The drag start event is `mousedown`; we'll record the starting position of the cursor, and add the drag and drag end event listeners. The drag event will be `mousemove`; here we'll do all the logic to check for modifier keys and actually manipulate the sphere. Finally, I'll use two events for drag stop: `mouseup` and `mouseout`. That way, we don't get that weird case where you move your mouse out of the window and then let go, and the app is stuck in the dragging state until you click again. Those events just remove the event handlers registered on drag start.

I decided to add `mousemove`, `mouseup`, and `mouseout` to the `window` so that you can move your mouse around the page after clicking. And I had to suppress the `mouseout` event on the `canvas` and `body` elements using `event.stopPropagation()` to prevent prematurely triggering the drag stop when moving your mouse in and out of the canvas.

Here's what all that looks like:

```javascript
let dragOrigin;
canvas.addEventListener('mousedown', dragStartHandler)
function dragStartHandler(e){
    dragOrigin = {
        x: e.clientX,
        y: e.clientY
    };
    document.addEventListener('mousemove', dragHandler);

    window.addEventListener('mouseup', dragStopHandler);
    window.addEventListener('mouseout', dragStopHandler);

    canvas.addEventListener('mouseout', stopPropagation);
    document.body.addEventListener('mouseout', stopPropagation);
}

function dragHandler(e){
    if (e.ctrlKey || e.metaKey) {
        if (e.altKey) sphere.hiddenFun2(e.clientX-dragOrigin.x, e.clientY-dragOrigin.y);
        else sphere.zoom(e.clientX-dragOrigin.x, e.clientY-dragOrigin.y, -1);
    }
    else if (e.shiftKey) {
        if (e.altKey) sphere.hiddenFun1(e.clientX-dragOrigin.x, e.clientY-dragOrigin.y);
        else sphere.pan(e.clientX-dragOrigin.x, e.clientY-dragOrigin.y);
    }
    else {
        sphere.rotate(e.clientX-dragOrigin.x, e.clientY-dragOrigin.y);
    }
    dragOrigin.x = e.clientX;
    dragOrigin.y = e.clientY;
}

function dragStopHandler(e) {
    document.removeEventListener('mousemove', dragHandler);
    document.removeEventListener('mouseup', dragStopHandler);
    document.removeEventListener('mouseout', dragStopHandler);

    canvas.removeEventListener('mouseout', stopPropagation);
    document.body.removeEventListener('mouseout', stopPropagation);
}

function stopPropagation(e) {
    e.stopPropagation();
}
```

So even that wasn't too bad! But then I realized I had forgotten about...


# Touch support for mobile 😭

After implementing my updated code, I visited the page on my phone; nothing worked. Then it hit me: *phones don't have mouses!* 🤦

Some quick research reminded me of the relatively recent [`pointer` events](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent) (`pointermove`, `pointerup`, etc.), which are made to unify lots of different kinds of pointer-ish interactions: mouse, touch, stylus, new stuff we haven't thought of yet. The problem (for now) is that [mobile support is spotty](https://caniuse.com/#feat=pointer): `pointer` events work on Chrome for Android, Opera Mobile, Android Browser, Samsung Browser, and IE Mobile (???), but not Firefox for Android, iOS Safari, or most other mobile browsers..

The other problem is that the controls to do anything other than spin the sphere currently require modifier keys on the keyboard to be pressed (Shift+drag to pan, Ctrl+drag to zoom). So `pointer` events on their own will only enable rotating, nothing else.

A proper solution would be to learn about `touch` events and how to implement gestures like pinch-zoom and 2-finger-pan, but I'll be honest, I looked into it a little and it's *so* complex! More than I want to get into right now. So the `pointer` events will have to do as a half-measure that only fixes rotating and only works on some mobile devices. And I'm not thrilled about it 😞


Anyway, here's what all of that looks like. I'm testing for `pointer` event support by checking whether the `<body>` has an `onpointermove` property, and if not I fall back to `mouse` events, since there are desktop browsers that don't support `pointer` events, too (looking at you, Safari 😡).

```javascript
// Prefer 'pointer' events when available
const pointerEvent = (
  'onpointermove' in document.body
    ? 'pointer'
    : 'mouse'
);
// Shorthands for 'mouse' or 'pointer' events
const [downEvt, upEvt, moveEvt, outEvt] = (
  ['down', 'up', 'move', 'out']
    .map(evtType => pointerEvent + evtType)
);

// Drag events
let dragOrigin;
canvas.addEventListener(downEvt, dragStartHandler)
function dragStartHandler(e){
    dragOrigin = {
        x: e.clientX,
        y: e.clientY
    };
    document.addEventListener(moveEvt, dragHandler);

    window.addEventListener(upEvt, dragStopHandler);
    window.addEventListener(outEvt, dragStopHandler);

    canvas.addEventListener(outEvt, stopPropagation);
    document.body.addEventListener(outEvt, stopPropagation);
}

function dragHandler(e){
    if (e.ctrlKey || e.metaKey) {
        if (e.altKey) sphere.hiddenFun2(e.clientX-dragOrigin.x, e.clientY-dragOrigin.y);
        else sphere.zoom(e.clientX-dragOrigin.x, e.clientY-dragOrigin.y, -1);
    }
    else if (e.shiftKey) {
        if (e.altKey) sphere.hiddenFun1(e.clientX-dragOrigin.x, e.clientY-dragOrigin.y);
        else sphere.pan(e.clientX-dragOrigin.x, e.clientY-dragOrigin.y);
    }
    else {
        sphere.rotate(e.clientX-dragOrigin.x, e.clientY-dragOrigin.y);
    }
    dragOrigin.x = e.clientX;
    dragOrigin.y = e.clientY;
}

function dragStopHandler(e) {
    document.removeEventListener(moveEvt, dragHandler);
    document.removeEventListener(upEvt, dragStopHandler);
    document.removeEventListener(outEvt, dragStopHandler);

    canvas.removeEventListener(outEvt, stopPropagation);
    document.body.removeEventListener(outEvt, stopPropagation);
}

function stopPropagation(e) {
    e.stopPropagation();
}
```

By the way, while working on this I discovered that [Chrome's dev tools are very nice for emulating mobile devices with touch events](https://developers.google.com/web/tools/chrome-devtools/device-mode/)!

Oh, one final thing. I had to add a CSS rule to the canvas to make touch work properly: `touch-action: none;`. This basically prevents the browser from capturing touch events to try and scroll the page, and lets me use them in my JavaScript.


# Finished product

It was a lot of iteration, and to be honest I tweaked it a bunch more while writing this article, but it's out there! Here's what it looks like with all the updates:

{% codepen https://codepen.io/kenbellows/pen/bZzxMP %}


# Conclusion

There are still things I'd like to fix. The biggest issue I have is that it's not mobile friendly, really at all. Not only do zoom and pan not work on mobile, but the webpage itself is almost completely non-responsive. I'd like to add some CSS to handle especially small page sizes more cleanly. Maybe I'll tweak it some more after this 🤔 As I said at the top, I'd love any feedback you might have! Heck, if you're feeling generous, make a PR against [the repo](https://github.com/kenbellows/jsphere)!

But here's my overall takeaway: HTML, CSS, and JavaScript have improved a ton in the last 7 years, and so have I as a developer. Experience is an amazing thing!


In the final part of this series, which I'll write...eventually, I'll walk through the JavaScript itself and talk about how it works. That one will really be a standalone tutorial on how to build a thing with `<canvas>` in vanilla JavaScript, not really dependent on these previous two posts.