How to organize our JS / jQuery spaghetti code better

Jordan Enev
Dev Labs
Published in
6 min readApr 24, 2017

--

How can I turn the lights on?

At various times of our programming career each of us has faced with a spaghetti oriented implementation — either in the first coding steps or later maintaining other’s code.

In this tutorial we’ll develop a small wish list app, that will evolve from a single file into loosely coupled objects, split by context.

Each time we improve the code, firstly we’ll answer ourselves:

“Why do we need that improvement?”

Okay! Let’s do it … better!

Wish list app specification

The app functionality will be pretty simple, but enough for taking notes and good practices. Here are the app’s features:

  1. A wish has a name and price.
  2. A wish can be added or removed.
  3. There should be a sum of all added wishes.

As simple as possible, so let’s continue with the first version of our app — the spaghetti one.

Version 1: Spaghetti code

It’s the beginning of the project. Let’s assume we’re a newbie, we don’t have enough knowledge or whatever the reason is. Therefore we start implementing everything in a single JS file, without supposing what are the possible issues of such decision.

Files structure

[source code | demo]

Here are the files of the first app version:

  1. index.html — the app’s layout.
  2. app.js — the entire app’s functionality is coded here.

index.html

app.js

Why do we need an improvement?

The app codebase looks very compact and readable at first sight, but let’s take a closer look at app.js. Here are the main pitfalls, these can scale totally wrong:

1. Presentation logic is tightly coupled to the business logic! Take a look at how we calculate the wishes’ sum for example:

// Calculate wishes sum
let sum = $('.wish-list .wish-list-sum').text();
sum = sum ? parseFloat(sum) : 0;
sum += price;

* Presentation logic is responsible for accessing and manipulating the DOM elements. For instance — showing / hiding divs.

We depend on $('.wish-list .wish-list-sum').text() in order to calculate the sum. Now imagine that we want to change the app’s presentation and stop displaying the total sum, but in spite of that keep calculating the sum. According to the current implementation we have to refactor the sum functionality together with the presentation changes. That’s tight coupling!

2. Single responsibility principle (SRP) is violated! We have a file with mixed presentation and business logic.

3. Large spaghetti file. Imagine we continue in the same fashion developing the new features. The file will become extremely large and harder for maintenance.

Version 2: Separation of concerns

Here we’ll address the problems, mentioned above. Separating the functionality by context will help us achieve modularity and split the spaghetti-like app.js into smaller objects.

In computer science, separation of concerns (SoC) is a design principle for separating a computer program into distinct sections, such that each section addresses a separate concern. A concern is a set of information that affects the code of a computer program.

Wikipedia

Files structure

[source code | demo]

We’ll split app.js in a few more files:

  1. app.js — it acts as a bootstrap loader.
  2. wishes-view.js — object, responsible for wishes presentation logic — html rendering, DOM events handlers.
  3. wish.js — object representing a wish. Encapsulates a single wish business logic.
  4. wishes-collection.js — object containing wishes (Wish objects). Here we’ll encapsulate all collective wish business logic as sum calculation, wish addition and remove.

app.js

wishes-view.js

wish.js

wishes-collection.js

Now we can easily change the presentation of wishes, without breaking or changing the sum functionality (business logic) or vice versa! The both presentation and business logic are loosely coupled now. Also each file (object) has its own context and once we want to change something, we’ll know where we can find it!

Why do we still need an improvement?

1. WishesView.initEvents() knows too much and still breaks SRP! Take a closer look to the function’s body:

// Handle wish addition
$('.wish-list .wish-add').on('click', function() {
let name = $('.wish-list input[name="name"]').val();
let price = parseFloat($('.wish-list input[name="price"]').val());
// Create wish and it to the wishes collection
let wish = new self.Wish(name, price);
self.wishesCollection.add(wish);
self.render();
});

WishesView.initEvents() responsibility is to initialize the DOM events, nothing more! In spite of that the function knows what exactly happens on a new wish addition, namely adding a new wish to wishesCollection (self.wishesCollection.add(wish)).

Now imagine that we have another functionality that has to be executed on a wish addition. If WishesView.initEvents() still knows everything, it can get too complicated and larger quickly.

The big problem would be if we have another presentation, where we can add new wishes too. For example — a quick wish submitter at the page’s header, in order the user can easily add a wish, no matter on which page he is. In that case how can we reuse the functionality that has to be executed on a new wish addition? It would look something like that:

wish-quick-submitter-view.js

WishQuickSubmitterView.prototype.initEvents = function() {
const self = this;
$('.wish-quick-submitter .wish-add').on('click', function() {
let name = $('.wish-quick-submitter input[name="name"]').val();
let price = parseFloat($('.wish-quick-submitter input[name="price"]').val());
// Duplicated
let wish = new self.Wish(name, price);
self.wishesCollection.add(wish);
// How can we rerender wishesView too?
self.render();
});
};

As you see something’s wrong. We have a duplicated content and we have to find a way to recalculate the wishes’ sum in WishesView instance. We can pass a WishesView instance to WishQuickSubmitterView, but that makes the WishQuickSubmitterView tightly coupled to WishesView and requires us to always use WishesView, once we needed WishQuickSubmitterView. That’s wrong!

Version 3: Publish / Subscribe design pattern (Observer)

Thanks to Publish / Subscribe pattern we can improve SRP and make WishQuickSubmitterView loosely coupled to any other views.

The general idea behind the Observer pattern is the promotion of loose coupling (or decoupling as it’s also referred as). Rather than single objects calling on the methods of other objects, an object instead subscribes to a specific task or activity of another object and is notified when it occurs. Observers are also called Subscribers and we refer to the object being observed as the Publisher (or the subject). Publishers notify subscribers when events occur.

Addy Osmani | Wikipedia | Here is a great explanation too

Concerning our app architecture, the presentation objects (WishesView, WishQuickSubmitterView) can act as both publishers and subscribers, because on a DOM Event ($('.wish-list .wish-add').on('click', function() {})) they will publish a message saying “hey, a new wish is being added”, and also they have to respond respectively (to be subscribed to new wishes addition) - i.e. “Hey, there is a new wish added! I have to update the sum and the wishes list html!”.

WishesCollection would act as subscriber and on a new wish addition, the collection would store it itself.

Files structure

[source code | demo]

It’s almost the same as Separation of concern implementation, so here are only the differences.

  1. index.html — quick wish submitter header layout is added.
  2. app.js — it acts as a bootstrap loader (here we initialize the subscribers too).
  3. wishes-view.js — it publishes new messages, once a wish is added, and it’s subscribed for new wishes addition, because there is another view (wish-quick-submitter-view.js) that can add new wishes too.
  4. wish-quick-submitter-view.js — a quick wish submitter at the page’s header bar. It publishes new messages on a wish addition.
  5. wish-collection.js — it’s subscribed and listen for new wishes addition.
  6. publish-subscribe.js — that’s a jQuery Callbacks implementation of Publish / Subscribe pattern. You can use any other vendor’s implementation.

index.html

app.js

wishes-view.js

wish-quick-submitter-view.js

wishes-collection.js

What’s next

Can we improve something more? Great question!

In most of the time there would be a functionality that can be implemented better. It depends on time availability and features priority. However, starting with a solid architecture will give us the flexability to improve the codebase’s weaknesses once needed.

According to our codebase, it would be a good idea to encapsulate the objects in modules, to use template library and so on. We’ve skipped it on purpose, in order to keep the focus on several architecture concepts.

If you feel familiar with the shared codebase and concepts, then I suppose it’s time to get dive in more advanced concepts like component based aproach. You can check how React deals with it.

Conclusion

As we can see now we have a clear separation on concerns and loosely coupled objects. We successfully addressed all already mentioned pitfalls. Due to the fact we improved the code quality, that results in reusability, readability and maintainability.

It’s worth to mention that using a Framework you get most of the good practices. That tutorial aims to provoke the right attitude once we start a project, no matter we use a Framework or not.

Separation of concern implementation is a good starting point for any size project. For sure there isn’t one-size-fits-all solution. In the project’s beginning we can think carefully about our architecture and possible pitfalls, but we can’t predict everything!

Knowing good programming practices, principles and patterns can help us improve the codebase continuously!

Now it’s better, isn’t it?

--

--

Web developer with strong Back-end and Front-end experience. For the last few years I’ve been focused on and passionate about JS development and its ecosystem.