Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/rstacruz/rsjs

Reasonable System for JavaScript Structure
https://github.com/rstacruz/rsjs

Last synced: 23 days ago
JSON representation

Reasonable System for JavaScript Structure

Awesome Lists containing this project

README

        

# rsjs

> Reasonable System for JavaScript Structure

This document is a collection of guidelines on how to structure your JavaScript in a standard non-SPA web application. Also see [RSCSS], a document on CSS conventions that follows a similar line of thinking.

## Problem

For a typical non-[SPA] website, it will eventually be apparent that there needs to be conventions enforced for consistency. While [styleguides][abnb] cover coding styles, conventions on namespacing and file structures are usually absent.

[abnb]: https://github.com/airbnb/javascript
[SPA]: https://en.wikipedia.org/wiki/Single-page_application

### The jQuery soup anti-pattern
You will typically see Rails projects with behaviors randomly attached to classes, such as the problematic example below.

```html


This article was written by
Rico Sta. Cruz.

```

```js
$(function () {
$('.author a').on('hover', function () {
var username = $(this).attr('href')

showTooltipProfile(username, { below: $(this) })
})
})
```

### What's wrong?

This anti-pattern leads to many issues, which rsjs attempts to address.

* **Ambiguious sources:** It's not obvious where to look for the JS behavior. (Is the handler attached to *.author*, *.footnote*, or *.profile-link*?)

* **Non-reusable:** It's not obvious how to reuse the behavior in another page. (Should *blogpost.js* be included in the other pages that need it? What if that file contains other behaviors you don't need for that page?)

* **Lack of organization:** Making new behaviors gets confusing. (Do you make a new `.js` file for each page? Do you add them to the global `application.js`? How do you load them?)

## In a nutshell

RSJS makes JavaScript easy to maintain in a typical web application. All recommendations here have these goals in mind:

- Keep your HTML _declarative_ (get rid of inline scripts).
- Put all your _imperative_ code in JS files.
- Reduce ambiguity by having straight-forward conventions.
- HTML elements can be _components_ that have _behaviors_.
- Behaviors apply JavaScript to a `[data-js-behavior-name]` selector.

## Structure

### Think in component behaviors

Think that a piece of JavaScript code to will only affect 1 "component", that is, a section in the DOM.

There files are "behaviors": code to describe dynamic JS behavior to affect a block of static HTML. In this example, the JS behavior `collapsible-nav` only affects a certain DOM subtree, and is placed on its own file.

```html


```

```js
/* Behavior - behaviors/collapsible-nav.js */

$(function () {
var $nav = $('[data-js-collapsible-nav]')
if (!$nav.length) return

$nav
.on('click', '[data-js-expand]', function () {
$nav.addClass('-expanded')
})
.on('mouseout', function () {
$nav.removeClass('-expanded')
})
})
```

### One component per file

Each file should a self-contained piece of code that only affects a *single* element type.

Keep them in your project's `behaviors/` path. Name these files according to the `data-js-___` names ([see below](#use-a-data-attribute)) or class names they affect.

```
└── javascripts/
└── behaviors/
├── collapsible-nav.js
├── avatar-hover.js
├── popup-dialog.js
└── notification.js
```

### Load components in all pages

Your main .js file should be a concatenation of all your `behaviors`.

It should be safe to load all behaviors for all pages. Since your behaviors are localized to their respective components, they will not have any effect unless the element it applies to is on the page.

See [loading component files](#loading-component-files) for guides on how to do this for your project.

### Use a data attribute

It's preferred to mark your component with a `data-js-___` attribute.

You can use ID's and classes, but this can be confusing since it isn't obvious which class names are for styles and which have JS behaviors bound to them. This applies to elements inside the component too, such as buttons (see collapsible-nav example).

```html


$('.user-info').on('hover', function () { ... })
```

```html


$('[data-js-avatar-popup]').on('hover', function () { ... })
```

You can also provide values for these attributes.

```html

...
$('[data-js-tooltip]').on('hover', function () { ... })
```

### Don't overload class names

If you don't like `data-js-*` attributes and prefer classes, don't add styles to the classes that your JS uses. For instance, if you're styling a `.user-info` class, don't attach an event to it; instead, add another class name (eg, `.js-user-info`) to use in your JS.

This will also make it easier to restyle components as needed.

```html


$('.user-info').on('hover', function () { ... })


$('.js-avatar-popup').on('hover', function () { ... })
```

### No inline scripts

Avoid adding JavaScript thats inlined inside your HTML markup. This includes `...` blocks and `onclick='...'` event handlers.

By putting imperative logic outside your `.js` files (eg, JavaScript in your `.html`), it makes your application harder to test and they pose a significant maintenance burden. Keep all your _imperative_ code in your JS files, and your _declarative_ code in your HTML.

```html

$('button').on('click', function () { ... })

```

```html

```

Prefer to use JS behaviors instead of inline scripts.

### Bootstrap data with meta tags

A common pattern is to use inline `` tags to leave data that scripts will pick up later on. In the spirit of [avoiding inline scripts](#no-inline-scripts), these patterns should also be avoided.

```js
<!-- ✗ Avoid -->
<script>
window.UserData = { email: '[email protected]', id: 9283 }

```

If they're going to be used in a component, put that data inside the HTML element and let the behavior pick it up.

```html