Ractive-Require

Require Ractive views on demand in Package by Feature way!

Download (0.5.2) GitHub project

What is it?

Ractive.js is a template-driven UI library, but unlike other tools that generate inert HTML, it transforms your templates into blueprints for apps that are interactive by default.

Ractive-Require is an extension to Ractive.js that provides the support to require features on demand. A feature is composed by a HTML template file, and its JavaScript and CSS assets. They are not loaded on the page as long as the user does't need to use it.

The final goal is to create large webapps that loads and uses features only when necessary.

The project

CodeCorico

The project is maintened by an enthusiast community. They work together on enhancing the engine by making new features, fixes and more on GitHub.

Examples

The basics

Here is a page




          
$('#basics-help').click(function() {
  var opened = $('#basics-help').data('opened') || false;

  opened = !opened;

  $('#basics-help')
    .data('opened', opened)
    .html(opened ? 'Close the help' : 'Open the help');

  if (opened) {

    // This loads all the rv-require elements
    // It loads the element assets and HTML
    // then it calls the controller of each element

    BasicsPage.require();

  }
  else {

    // We assume "BasicsPage" is a Ractive element
    // When this element calls the .require() method
    // it takes its children in the childrenRequire property

    BasicsPage.childrenRequire[0].teardown();

  }
});

HELP

Help content

(function() {
  'use strict';

  // When the .require() is fired and after the load of the
  // rv-require assets, the "help" controller is fire

  window.Ractive.controller('help', function(component, data, el, config, done) {

    // "component" is a magic function that create the Ractive element
    // It uses the HTML template loaded and the target rv-require element
    // to create the view.

    var Help = component({
      data: data
    });

    done();
  });

})();
.help {
  position: absolute;
  top: 4.2rem;
  right: 0;
  bottom: 0;
  width: 50%;
  border-left: 1px solid #ececec;
  padding: 1rem;
  background: white;
}

Partials

Here is a page


- Partials My awesome content


          
$('#partials-help').click(function() {
  var opened = $('#partials-help').data('opened') || false;

  opened = !opened;

  $('#partials-help')
    .data('opened', opened)
    .html(opened ? 'Close the help' : 'Open the help');

  if (opened) {
    PartialsPage.require();
  }
  else {
    PartialsPage.childrenRequire[0].teardown();
  }
});
<div class="help">

  <!-- Include your Ractive partials in your view -->
  <!-- They will be replaced by the content inside rv-partial tags -->

  <h4>HELP {{> title}}</h4>
  <p>
    Help content:<br />
    {{> content}}
  </p>
</div>
(function() {
  'use strict';

  window.Ractive.controller('help', function(component, data, el, config, done) {

    var Help = component({
      data: data
    });

    done();
  });

})();
.help {
  position: absolute;
  top: 4.2rem;
  right: 0;
  bottom: 0;
  width: 50%;
  border-left: 1px solid #ececec;
  padding: 1rem;
  background: white;
}

Databinding

{{title}}




          
$('#databinding-edit').click(function() {
  var opened = $('#databinding-edit').data('opened') || false;

  opened = !opened;

  $('#databinding-edit')
    .data('opened', opened)
    .html(opened ? 'Edit the title' : 'Close the edition');

  if (opened) {
    DatabindingPage.require();
  }
  else {
    DatabindingPage.childrenRequire[0].teardown();
  }
});

Edit the title

(and drink a {{drink}})

(function() {
  'use strict';

  window.Ractive.controller('edition', function(component, data, el, config, done) {

    var Edition = component({
      data: data
    });

    done();
  });

})();
.edition {
  position: absolute;
  top: 4.2rem;
  right: 0;
  bottom: 0;
  width: 50%;
  border-left: 1px solid #ececec;
  padding: 1rem;
  background: white;
}

.edition .info {
  margin-top: -1.4rem;
  font-size: 1.4rem;
}

On demand

Here is a page




          
$('#on-demand-require').click(function() {
  OnDemandPage.require();
});

$('#on-demand-chat').click(function() {
  OnDemandPage.require('chat');
});

$('#on-demand-profile').click(function() {
  OnDemandPage.require('profile');
});

$('#on-demand-reset').click(function() {
  for (var i = OnDemandPage.childrenRequire.length - 1; i >= 0; i--) {
    OnDemandPage.childrenRequire[i].teardown();
  }
});

{{title}}

...

Here is the chat


Cascading elements

Here is a page


Content from the parent


          
$('#cascading-elements-help').click(function() {
  var opened = $('#cascading-elements-help').data('opened') || false;

  opened = !opened;

  $('#cascading-elements-help')
    .data('opened', opened)
    .html(opened ? 'Close the help' : 'Open the help');

  if (opened) {

    // Require only the help-full feature

    CascadingElementsPage.require('help-full');
  }
  else {
    CascadingElementsPage.childrenRequire[0].teardown();
  }
});

HELP

(function() {
  'use strict';

  window.Ractive.controller('help-full', function(component, data, el, config, done) {

    var HelpFull = component({
      data: data
    });

    // Immediatly after its creation,
    // the feature require its sub-features
    // and fire done() after

    HelpFull.require().then(function() {
      done();
    });

  });

})();
<div class="article">
  <h4>{{title}}</h4>
  ...
  {{> content}}
</div>
;

Installation

Use the CDN version:

<script src="https://cdnjs.cloudflare.com/ajax/libs/ractive-require/0.6.5/ractive-require.min.js"></script>

Or copy the dist folder into your project and include ractive-require.js or ractive-require.min.js (production) file in your HTML page after Ractive.

Use it

Require a feature

A feature is a package of an HTML template and its assets. Use the HTML tag <rv-require> to put your feature unloaded in your page. This tag requires a name and a src attribute. The name is not unique, it's used to avoid loading several times the same assets files in the browser. The src is used to locate the feature "package".

The missing file extension is assumed because we want to load the entire feature package.

At this point, nothing happens, the feature is only loaded on demand.

To load and create your feature view, you have to use the Ractive container view by calling the .require() of its parent.

// Start by creating the parent view from the <body> element
var ractive = new Ractive({
  el: 'body',
  template: document.body.innerHTML
});

// Call the require() method to fire the rv-require injection
ractive.require().then(function() {
  console.log('done');
});

.require() is a promise thats provide a .then() method to callback after the end of the process.

The cycle of a require

When you fire .require() on a ractive element, all of its <rv-require> children are required in the view (except those with a ondemand attribute).

First, Ractive-Require adds the following to the <head> element:

  • components/button.css
  • components/button.js

Then it registers the components/button.html template in its memory (by xhr). The feature is loaded.

In the <rv-require> src attribute the extension is not set because Ractive-Require will load 3 different types of files

The HTML template will still be a Mustache template with data-binding, but for now, it's just a string.

Control a feature

When a <rv-require> is loaded, all of its registered Ractive.controller are fired. You can wrap it for your feature to get the feature definition and other things:

// Declare a controller for the rv-require named "button"
Ractive.controller('button', function(component, data, el, config, done) {

  // "component" is a pre-configured view function.
  // It already contains template, partials and the targeted DOM element.
  var ractive = component();

  // You can get the pre-configured details with the "config" object.

  // When your controller logic is complete, call done().
  done();

});

Now your feature is totally applied in the DOM and you can control it!

Cache

Each feature template is cached by its name. If you declare:


The second button does not reload the JavasScript and CSS files in the head and it re-uses the template content previously loaded.

HTML only

To avoid loading the CSS or JavaScript files, you can add no-script="true" and/or no-css="true":


On demand

With Ractive.require() all of the <rv-require> are fired except those with a ondemand attribute. To have the ability to require only some of features, you can use the ondemand attribute:


Ractive.require() has no effect on the button. To fire it, you have to specify Ractive.require('button1').

With the same ondemand attribute value, you can make groups of features loaded together.

Double databinding

You can pass two types of arguments in a <rv-require>:

  • Direct value with the data-* parameter.
  • Double databinding value between the parent and the child with the data-bind-* parameter.

The * is the property name fetched in data.


Ractive.controller('button', function(component, data, el, config, done) {

  console.log(data.id);

  // With the first button, data.id gets the 5 direct value.
  // With the second, data.id gets the value of the parent view "ractive.data.model.id"
  // If the parent value "model.id" changes, the value is directly changed in the child too.
  // If the child value "model.id" changes, the value is directly changed in the parent too.

  // If you pass an object, it will not be cloned but directly sent.

  var ractive = component({
    data: data
  });

});

It's also possible to bind in one way only:

  • Use data-bindparent-* to bind the values from the parent to the child.
  • Use data-bindchild-* to bind the values from the child to the parent.

Events

A component can listen its parent events and its parent can do the same with its children:

  • Use data-on-* to bind the child event to a parent event (named *).
  • Use data-listen-* to bind the parent event to the child event (named *).
  • Optionnaly you can bind many events this these syntaxes: data-on-*-*-*-... and data-listen-*-*-*-...

Ractive.controller('button', function(component, data, el, config, done) {

  // The "button" component can fire the "click" event when the user clicks on it.
  // So when the user click on the first button, the "buttonClick" event is fired too.
  // Then it fires the "parentEvent" that fire the "parentclick" of the second button.

  var ractive = component({
    data: data
  });

  ractive.on('buttonClick', function() {

    ractive.fire('parentEvent');

  });
});

Partials

It's possible to define partials for a <rv-require> that are used only on it. A partial is a <rv-partial> element defined inside the <rv-require>. It can takes a src attribute or directly a HTML code. It must have a target attribute with the name of the partial Mustache tag.

If no partial is defined in the <rv-require>, it takes the parent partial inclusion.

Inside the feature template, use the standard {{> partial}} partial include:

<!-- index.html -->

<rv-require name="button" src="components/button">

  <!-- A partial from a HTML page -->
  <rv-partial target="content" src="components/content.html"></rv-partial>

  <!-- Or a direct HTML content -->
  <rv-partial target="footer">
    <div>The footer</div>
  </rv-partial>

</rv-require>

<!-- components/content.html -->

<div class="content">
  {{> content}}

  {{> footer}}
</div>

Tips: To use Mustache inside a partial section, you can escape the Mustache code: \{{my-variable}}.

Cascading requires

Inside a feature or partial template, it's possible to call other features. It works exactly the same as requiring a feature in the page, you have to call ractive.require() for the parent feature.

The sub-feature already contain its parents partials.

Scopes

Each <rv-require> has its own scope. When you call ractive.require() it doesn't look at the DOM parent or the children of the <rv-require> on it.

When you create your feature with ractive = component(), ractive contains two new properties:

  • ractive.parentRequire: It's the instance of the parent feature.
  • ractive.childrenRequire: It's an array filled by all of the children features.

Paths

In a feature or partial template, all of the next src attributes of the <rv-require> and <rv-partial> elements are relative of the actual file:












Icon

Teardown

When you fire a ractive.teardown() of a feature, it fires all of its children teardown after it cleans the DOM.

Your <rv-require> is reseted but still in the DOM. So you can re-use .require() on its parent to re-fire its controller.

Require a file directly

In your JavaScript, you can require another JavaScript and CSS files. Use Ractive.require(file).

The goal is to use the same cache as for <rv-require> elements.

// Inject the js files sequentially
// The return is a Promise
Ractive.require('/public/jquery.js')
  .then(function() {
    return Ractive.require('/public/jquery-ui.js');
  })
  .then(function() {
    return Ractive.require('/public/styles.css');
  })
  .then(function() {
    console.log('Assets loaded');
  });

// You can add a name to avoid many injections of the same file.
// Otherwise, it's the filename wich is used as name.
Ractive.require('jquery', '/public/jquery.js');

Static methods

.controller()

Ractive.controller( name, controllerFunc )

Register a controller for a specific feature. When the feature will be required, the controller will be fire.

name string

The name of the feature.

controllerFunc( component, data, el, config, done ) function

The controller function. It is fired each time the specific feature is required.

component( config )function

Create and return a Ractive view (new Ractive()) plugged on the actual feature DOM. It provide automatically the native el, template and partials configurations. It also create the parents/children links and add Ractive-Require instance attributes and methods.

data object

Data configuration getted from the data-* and data-bind-* attributes.

el DOM element

rv-require DOM element.

config object

It contains the HTML (string) template and the partials.

done( ) function

The callback function to finish the initialization of the controller. The .then() of the parent's .require() will be fired.

.require()

Ractive.require( file )

Inject a .js or .css file in the head tag. It returns a promise that it is fired when the file is completely loaded. If the file is already loaded, the promise is directly fired.

file string

The file to inject in the head. The total path will be used to knows if the file is already loaded.

Ractive.require( name, file )

Inject a .js or .css file in the head tag. It returns a promise that it is fired when the file is completely loaded. If the file is already loaded, the promise is directly fired.

name string

The name of the file. Instead of the file path, this name will be used to knows if the file is already loaded.

file string

The file to inject in the head.

Instance attributes

.parentRequire

ractive.parentsRequire ractive object

Each ractive instance required by its parent owns its reference by using ractive.parentsRequire.

.childrenRequire

ractive.parentsRequire array

When a ractive instance require its children, it became owner of this property with all of its children required in it.

Instance methods

.require()

ractive.require( )

Require all of its sub rv-require DOM elements except those with a ondemand attribute.

This method returns a promise that it is fired when the elements are totally loaded.

The ractive.require() process steps:

  1. Inject the CSS file of the feature in the head (Unless the no-css="true" attribute is given in the rv-require child element). Wait the end of the file load to go to the next step.
  2. Inject the JavaScript file of the feature in the head (Unless the no-script="true" attribute is given in the rv-require child element). Wait the end of the file load and execution to go to the next step.
  3. Get the HTML template from the URL (xhr) and keep it in cache.
  4. Call all the registered controllers from the rv-require name.
  5. Fire the promise returned.

ractive.require( name )

Instead of the method without a name, this one require all of its sub rv-require DOM elements with the ondemand attribute filled by the name.

name string

The name of the ondemand children.

.findParents()

ractive.findParents( attribute, value )

Return an array filled by all of the ractive parents searched through the cascading ancestors matching the filter.

attribute string

The attribute name to filter the parents.

value string

The attribute value to filter the parents.

.findParent()

ractive.findParent( attribute, value )

Return the ractive parent searched through the cascading ancestors matching the filter.

attribute string

The attribute name to filter the parents.

value string

The attribute value to filter the parents.

.findChildren()

ractive.findChildren( attribute, value )

Return an array filled by all of the ractive children searched through the cascading children matching the filter.

attribute string

The attribute name to filter the children.

value string

The attribute value to filter the children.

.findChild()

ractive.findChild( attribute, value )

Return the ractive child searched through the cascading children matching the filter.

attribute string

The attribute name to filter the children.

value string

The attribute value to filter the children.