Skip to content

sroucheray/bootscope

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bootscope

Bootscope is a RequireJS boot module that load other modules using a declarative syntax within HTML pages.

It suits to websites, not webapps.

It anwsers one question : How do you make the decision to load or not to load a specific module in a specific page of your site ?

Table of Contents

Why Bootscope ?

Webapps is the trending topic, this is a great paradigm change... but websites still exist, aren't they ?

Websites are very different from webapps.

Websites are not uniform and features scattered.

Some pages have forms, some have menus, some have widgets, some have no specific features.

You cannot rely on URLs of your Website's pages to identify its unique features because of the SEO rewriting.

Bootscope serves four purposes :

  • Single script tag entry point for all pages of a site
  • Only required features for a page are loaded
  • Simple to setup
  • Framework agnostic, it does not prescribes any MVC framework

How it works ?

When the page has loaded, Bootscope look at any element in the page holding a data-feat attribute. If the value of this attribute is linked to a module, Bootscope loads it. The module is then defined.

If the return value of the module's factory is a function see here, Bootscope will execute it passing the element.

The parts

Bootscope is made of three parts

  1. requiresjs-jquery.js : the RequireJS module loader
  2. bootscope.js : a module. It contains Bootscope logic
  3. bootconfig.js : a module. It holds the routes linking features to modules (you set it up)

First steps

Once you have dropped these required files in your project, it's a four steps work :

1. Add the script tag in the HTML page

...
<head>
    <script src="requiresjs-jquery.js"
            data-main="path/to/bootscope"
            data-bootscope="bootconfig"
            type="text/javascript">
    </script>
</head>
...

The script tag holds some specific attributes :

  • data-main attribute is standard to RequireJS, it's value is the first module loaded, in our case, it is bootscope.js (Note : never include the trailing .js when referencing a module)
  • data-bootscope attribute's value reference the bootconfig module

2. Add data-feat attribute to any tag in the HTML document

<!DOCTYPE html>
<html>
  <head>...</head>
  <body>
    <div data-feat="menu"></div>
  </body>
</html>

This indicates that this div holds a specific feature named menu

3. Add a route to the bootconfig module

The bootconfig file is also a module, it returns a config object. This config object holds the routes. A route links a feature to a module

define({
    routes : {
        menu : "path/to/module/menu-module"
    }
});

4. Create the module in menu-module.js in path/to/module

define(["jquery"], function($){
    //Here is the module logic
    //The code here is executed once in page life, when the module is loaded 
    return function(element){
      //Here is the menu logic
      //This code is executed for each element found on the page with 
      //the data-feat attribute linked to this module
 
      //element is the div holding the data-feat attribute
    }
});

How to...

I want to pass parameters from the backend to my scripts

The easiest way is to add a globals property to the bootconfig file :

define({
    routes: { //Feature to module mapping
        menu : "path/to/module/menu-module"
    },
    globals: { //Any parameter that can be retreived in bootsope.global
        imgPath: "path/to/images/",
        dataJSONPath: "javascript/data/data.json"
    }
});

This bootscope script being a module itself, your modules can depend on it :

define(["jquery", "bootscope"], function($, bs){
    //Data is retrieved from the globals property of the bootscope module
    var pathToImg = bs.globals.imgPath,
        json = bs.globals.dataJSONPath;
    return function(node){
    }
});

Ok, but my parameters are dynamics and can't be dumped in a JS file

The other option is to dump a JSON string within the script tag itself, thus being dropped in the HTML page, it can be pulled from your backend system :

...
<head>
    <script src="require-jquery.js"
            data-main="path/to/bootscope"
            data-bootscope="bootconfig"
            type="text/javascript">
    '{
        "imgPath" : "path/to/img/",
        "data" : "path/to/data/"
    }'
    </script>
</head>
...

The magic is that you retrieved this JSON string parsed as an plain object in the locals property of the bootscope module.

define(["jquery", "bootscope"], function($, bs){
    //Data is retrieved from the globals property of the bootscope module
    var pathToImg = bs.locals.imgPath,
        data= bs.locals.data;
    return function(node){
    }
});

Ok, but I want my parameters to be specific to a data-feat module

In this case, just drop other data-* attributes on your node :

<!DOCTYPE html>
<html>
  <head>...</head>
  <body>
    <div data-feat="menu" data-color="red"></div>
    <div data-feat="menu" data-color="blue"></div>
    <div data-feat="menu"></div>
  </body>
</html>

You can then retrieve these parameters in your module (note : this time no dependency to bootscope is required) :

define(["jquery"], function($){
    //Remember that the function below is executed for each menu element
    return function(menu){
        var $menu = $(menu),
            data = $menu.data();
        if(data.hasOwnProperty("color")){
            $menu.css("background-color", data.color);
        }
    }
});

How to load a module before another one

By default Bootscope loads module in the order of the HTML page. To give a higher priority to a specific feature that is not a the top of the page simply add a data-priority atribute to the tag :

<!DOCTYPE html>
<html>
  <head>...</head>
  <body>
    <div data-feat="menu"></div>
    <div data-feat="search" data-priority="1"></div>
    <div data-feat="carousel"></div>
  </body>
</html>

In this case the search module will be loaded before the menu and the carousel.

Default priority for a module is 0 so you can pass a negative integer to lower thr priority of a module :

<!DOCTYPE html>
<html>
  <head>...</head>
  <body>
    <div data-feat="carousel" data-priority="-1"></div>
    <div data-feat="menu"></div>
    <div data-feat="search"></div>
  </body>
</html>

While, in this case, the carousel is the first to appear in the page, it will be loaded after all the other one.

How not to load a module on the page load

It is possible to delay the load of a specific feature module adding a data-inactive attribute to the element.

Consider this case :

<!DOCTYPE html>
<html>
  <head>...</head>
  <body>
    <a href="#" data-feat="link"></div>
    <div id="dialog" data-feat="map" data-inactive="true" style="display:none"></div>
  </body>
</html>

The bootconfig will look like this :

define({
    routes: {
        link : "path/to/module/link",
        map : "path/to/module/map"
    }
});

The link module when clicked should show a dialog like box with a map in it. While the map module is not executed automatically, the link module will be executed on page load :

//link.js
define(["jquery", "bootscope"], function($, bs){
    return function(link){
        $(link).click(function(){
            var $dialog = $("#dialog");
            
            $dialog.show();
            //The bootscope loadFeatures method will force the load of the map module
            bs.loadFeatures($dialog);

            return false;
        });
    }
});


//map.js
define(["jquery"], function($){
    //Execution delayed after click on the link
    return function(map){
      //Logic to setup the map
    }
});

####I have module not linked to a specific part of the page, what should I do ? If one or several modules are not linked to an element in the page, you can use the script tag to load it like this :

...
<head>
    <script src="requiresjs-jquery.js"
            data-main="path/to/bootscope"
            data-bootscope="bootconfig"
            data-preload="module1, sub/module2"
            data-postload="sub2/module3, sub2/module4"
            type="text/javascript">
    </script>
</head>
...

module1 and module2 will be loaded as soon as possible. Bootscope will not wait for the page to be ready. module3 and module4 will be loaded after all the featured module in the page are loaded. This is useful for modules that need the page to be fully functionnal before being executed. ####I need my modules to communicates each other, how do I pass data from one module to another ? Bootscope provides a specific module named mediator which act as a communication layer using the Pub/Sub pattern. If moduleA wants to send to moduleB a message saying the data are updated passing the specific data.

//moduleB.js
define(["jquery", "mediator"], function($, mediator){
    return function(node){
        //moduleB defines a function which handles the receiving data
        function dataHandler(data){
          //Do whatever you want with those data
          console.log(data);
        }


        //moduleB subscribe to 'data:updated' channel
        mediator.on("data:updated", dataHandler);
    }
});


//moduleB.js
define(["jquery", "mediator"], function($, mediator){
    return function(node){
      var object = {
        foo : "bar"
      };

      //moduleA publish its object to the channel "data:updated"
      //the object will be received by all modules subsribing to this channel
      mediator.trigger("data:updated", object);
    }
});

The mediator code is borrowed from BackboneJS, click to see more information on channels.

####Where do I setup RequireJS for paths and other things Module paths can be a bit tricky. Let's make it clear :

  1. Module paths are the string you write when you add a dependency to a module
  2. Module paths are relatives to the base url
  3. If nothing is specified, the base url is the bootscope module url
  4. It can be changed by adding a data-baseurl to the script tag
    • In this case the the first module to be loaded using this redefined base url is the bootconfig module
    • The data-baseurl attribute is useful if the bootconfig module is not in the same directory as the bootscope
  5. In the bootconfig module you can redefine again the base url using the require property
    • This is useful if all the modules base url is not the bootconfig directory

The require property in the bootconfig is the RequireJS config object. Documentation can be found here : RequireJS Config

#####Paths to modules can be shortcuted using the require.paths property of the bootconfig module :

define({
  require: { //RequireJS config
        paths: {
            "mediator": "bootscope/mediator",
            "text": "plugins/text",
            "i18n": "plugins/i18n",
            "async": "plugins/async"
        }
    },
    routes: {
        link : "path/to/module/link",
        map : "path/to/module/map"
    }
});

//anymodule.js
define(["jquery", "mediator"],//Would have to use "bootscope/mediator" here if it had not been shortcuted  
    function($, _){
    //This is executed after Underscore is available even if Underscore is not a module
    return function(node){
    }
});

#####You can also define dependency name for script that are not module using RequireJS shim feature :

//bootscope.js
define({
  require: { //RequireJS config
        paths: {
            "mediator": "bootscope/mediator",
            "text": "plugins/text",
            "i18n": "plugins/i18n",
            "async": "plugins/async"
        },
        shim: {
            "underscore": {
                exports: "_"
            },
            "handlebars": {
                exports: "Handlebars"
            },,
            "jqueryui": {
                deps: ["jquery"],
                exports: "jqueryui"
            }
        }
    },
    routes: {
        link : "path/to/module/link",
        map : "path/to/module/map"
    }
});

//anymodule.js
define(["jquery", "underscore"], function($, _){
    //This is executed after Underscore is available even if Underscore is not a module
    return function(node){
    }
});

About

Declarative module loader based on web page features

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages