Home

Awesome

dry.js

Lightweight SPA framework based on Convention over Configuration

Build status: Build Status

Philosophy

Best practices

As its name implies, Dry.js is based around the idea of applying the Don't Repeat Yourself (DRY) principle to front end development as extensively as possible. It does so by following a Convention Over Configuration (CoC) approach.

Lightweight

Initial page load is one of the main concerns of Dry.js. The library weights less than 8kb minified (3kb gzipped) which makes it viable for any sort of web application.

Easily replaceable components

Dry.js is intended to be a standard way to join external components and libraries. All of its components are designed so that they can be easily replaced.

For example, you can swap Dry.js's templating engine with Handlebars, integrate React to improve rendering performance, or manage all DOM interactions with jQuery/Zepto, etc.

Please read this article to further understand why this approach is necessary.

Easy to learn, easy to use and easy to change

No one likes learning a new front-end framework every week, so that's why Dry.js was specifically designed to be super easy to use. Getting started will only take you some minutes, and there's not that much more to it. Complexity is the first sign of bad architecture, and your developers will thank you for it.

Have you ever wanted to change a certain feature of a given framework? Well, this is super easy in Dry.js because its code is brief and understandable, so re-writting a component is not complex at all. In fact I encourage you to do this if you want to replace any of the framework's core components with external libraries, you'll only have to run the tests afterwards to make sure that everything still works as expected.

Inspiration

Dry.js was inspired by what I consider to be the best features of the most well-known single page application frameworks:

Getting Started

Setting up Dry.js is extremely easy. You only need to include the script anywhere on the page, and you are all set.

<html>
  <head>
    <title>Getting started</title>
  </head>
  <body>
    <h1>Hello world</h1>
  
    <!-- Include dry.min.js here -->
    <script src="scripts/dry.min.js"></script>
  </body>
</html>

You can download the minitied and development versions through any of these options.

Components

App

A website is divided in apps. Their goal is to provide structure to the code, and can be seen as modules. Each app contains its own controllers, views and models, and handles a specific set of routes.

// Create new app
var app1 = dry.app('app1');
// Start the app
app1.init();

Controller

A controller contains methods. Each method handles a specific route or event, returning a view instance if the result of the method's execution triggers UI changes. In this sense, Dry.js controllers are very similar to Rails or ASP.NET MVC.

var app1 = dry.app('app1');

// Handles a list of products
app1.controller('products', {
    'list': function() { // Triggered when navigating to /products/list
        return new dry.View('ProductList')
    }
});

Router

The router is responsible for directing page navigation actions to controllers. As the previous example hints, you do not need to specify routes anywhere. They are automatically generated from controller names and methods.

var app1 = dry.app('app1');

// Home page
app1.controller('default', {
    // Triggered on base url (root)
    'default': function() {
        return new dry.View('HomePage')
    }
});

// Handles a list of products
app1.controller('products', {
    // Triggered when navigating to /products/:id
    ':id': function(id) {
        return new dry.View('ProductDetails')
    }
    // Triggered when navigating to /products/list
    'list': function() {
        return new dry.View('ProductList')
    }
});

Model

In a typical fashion, models concentrate the responsibility of handling data structure, storage and server communication. They are injected into views through controller actions, and come with a convenient API for handling Ajax communication. Expanding on the previous example:

var app1 = dry.app('app1');

// Product model definition
app1.model('Product', {
    attributes: {
        id: 1 // default
    }
    getAll: 'GET https://someUrl/products'
    newProduct: 'POST https://someUrl/products/new',
    updateProduct: 'PUT https://someUrl/products/{id}',
    deleteProduct: 'DELETE https://someUrl/{id}'
});

// Handles a list of products
app1.controller('products', {
    'list': function() {
        // Creates a new model instance and calls the getAll method
        var allProducts = app1.model('Product');
        return allProducts
            .getAll() // Promises are built-in
            .then(function (data) {
		allProducts.set(data);
		// Pass the model to the view
		return new dry.View('ProductList', {model: allProducts});
	});
    }
});

View

Views contain presentation logic. Each view contains a model instance which stores the data that is rendered on the template. They are also responsible for handling events, and do so by calling methods from the controller that created the view.

var app1 = dry.app('app1');

// Product model definition
app1.model('Product');

app1.view('ProductList', {
    events: {
        // When the button with class .btn-new' is pressed,
        // call the 'new' method in the controller
        'click .btn-new': 'new'
    }
});

// Handles a list of products
app1.controller('products', {
    'new': function() {
        // Creates a new model instance
        var product = app1.model('Product');
        // Pass the model to the view
        return app1.view('ProductList', {model: product});
    }
});

Template

Templates can be either strings or functinons which construct HTML code from the view data. Dry.js automatically finds template definitions in the dom looking for a script with a data-dry attribute, and then renders it to a DOM element with a data-dry attribute of the same value.

var app1 = dry.app('app1');

// Home page
app1.controller('main', {
    'hello': function() {
        return new dry.View('main/hello');
    }
});
<!-- Template -->
<script data-dry="main/hello" type="text/template">
    <% for(var i=0; i<10; i++) { %>
        Lorem ipsum
    <% } %>
</script>
<div data-dry="main/hello"><!-- Template will be rendered here --></div>

The convention here is very simple: by default, once a user navigates to a route (say www.someUrl.com/main/hello), then the main controller executes the hello method, which will create a new instance of the main/hello view.

Notice that the view instance is created on the fly, and since the only parameter specified when creating the view is its name, it will simply follow the default behavior for views. More specifically, it look for a script with a data-dry attribute equal to its name (the template) and render it into the first non-script DOM element with the same attribute value.

Filters

Filters are a simple and semantic way to reuse code inside your application (again, DRY principle). They check if a condition is fulfilled and execute a given action if it is. They can seve as annotations and make controller logic much easier to read.

var app1 = dry.app('app1');

function isLoggedIn() {
    // some logic...
}

function redirectToHome() {
    dry.navigate("/home");
}

app1.filter('IsLoggedIn', isLoggedIn, redirectToHome);

// Home page
app1.controller('main', {
    'hello': function() {
          app1.filter("IsLogedIn");
        
          // Continue controller logic...
    }
});

Note: filters are not required, so you can choose not to use them if you find them irrelevant or confusing.

Thanks To