maskjs

Template / Markup / HMVC Engine


[MaskJS](http://atmajs.com/mask' target='_blank'>mask.js) — is a markup | template | HMVC engine for modern and fast web(Browser), server(NodeJS) or mobile(PhoneGap) applications. Component-based architecture simplifies defining, implementing and composing loosely coupled independent elements into a single application.

Resources:




  • Component and element-based markup
  • Statements, Expressions, Interpolations
  • Performance. No precompilation is required
  • Small size. ~30% smaller than HTML Additionaly, there is a minification tool - mask-minify.
  • DOM Builder [Template → Mask DOM → Shadow DOM → Live DOM]
  • HTML Builder (nodejs) [Template → Mask DOM → HTML]
import :customComponent from './foo'
 
.container {
    h4 > 'Title'
    section.content data-id='myID' {
        span > 'Hello ~[name]!'
        
        if (admins.indexOf(name) > -1) {
            em > 'Admin'
        }
    }
    :customComponent {
        button x-tap='changeName' >
            '~[bind: name]'
            
        for (tag of tags) {
            h4 > '~[tag.title]'
        }
    }
}

MaskJS has extremely extendable API based on interfaces and contracts. It supports Custom Tag Handlers, Custom Attribute Handlers, Model Utils.

MaskJS default build contains sub projects: CompoJS, Bindings, jMask.

Documentation

Core of the HMVC engine. Simple compo sample:

mask.registerHandler(':customComponent', mask.Compo({
    slots: {
        refreshDatefunction(){
            this.model.date = new Date();
        },
        domInsertfunction(){
            alert(this.$.innerWidth());
        }
    },
    events: {
        'click: button'function(){
            alert(this.model.date);
        }
    },
    onRenderStartfunction(modelctx){
        // override model 
        this.model = { date: new Date(); }
    },
    onRenderEndfunction(elementsmodelctx){
        this.// is a domLibrary (jQuery-lite, jQuery/Zepto/Kimbo) wrapper over `elements` 
    },
    disposefunction(){
        // do some cleanup 
    }
})

Documentation (IE9+)

MaskJS itself supports simple interpolations. It means the models are only accessed while render, but with this library you can define single or dual bindings. As MaskJS is a DOM based engine, the bindings are instant.

Simple bindings sample:

h4 > '~[bind: fooDate.getSeconds() * barAge ]'
 
input type=date >
    :dualbind value='fooDate';
 
input type=number >
    :dualbind
        value='barAge'
        x-signal='dom: ageChanged';
/*
 * `ageChanged` is emitted in this sample each time `barAge` changes
 * `:dualbind` component also supports much more properties and configurations
 */

Documentation

jMask offers jQuery-alike syntax for the dynamic MaskDOM Manipulations.

MaskJS is loosely coupled with the DOM Library, like jQuery-Zepto-Kimbo. It means, that it does not depend on any DOM library, but it is highly recommended to use one. Additionally there are some extensions, like

$.fn.appendMask
$.fn.prependMask
$.fn.beforeMask
$.fn.afterMask
$.fn.emptyAndDispose
$.fn.removeAndDispose
//e.g. 
$('.foo').appendMask('h4 > "~[title]"', { title: 'Hello' });

So you would never need to use the HTML.

We thoroughly pay attention to the performance, especially on the mobile CPU. The DOM based and the Shadow DOM approach is the fastest way to create hierarchical component structure.

Some benchmarks:

MaskJS on the server - mask.node. (server)

  • HMVC benefits
  • Models serialization/de-serialization
  • Components render mode - Server, Client, Both
  • HTML rendered output with the Bootstrapping on the client, so that the components are initialized, all events and bindings are attached
  • Application start performance: browser receives ready html for rendering.
  • SEO
  • IE7+

There are already many plugins, components and useful utilities. Some of them worth checking out:

Most simple MaskJS sample to show where you could start from:

<!DOCTYPE html>
<html>
    <body>
        <header>
            <!-- e.g add menu into header -->
            <script type='text/mask' data-run='true'>
                ul {
                    for(page of pages) {
                        log('>> Log current:', page);
                        li > a
                            href='/~[page].html'
                            x-signal='click: fooAction' > '~[page]'
                    }
                    // nested components 
                    :bazCompo > :quxCompo;
                    
                    debugger;
                }
            </script> 
        </header>
        <!-- ... other html, or mask blocks -->
        <!--
            usually you would have only one Mask block, which is the entry point
            for the app, and you would use nested component composition to
            encapsulate logic, models, templates and the behaviour
        -->
        <script src='http://cdn.jsdelivr.net/g/maskjs'></script> 
        <script type='text/javascript'>
            var App = mask.Compo({
                model: {
                    pages: [ 'blog', 'about', 'contact' ]
                },
                slots: {
                    fooActionfunction(event){
                        event.preventDefault();
                        console.log(this instanceof App);
                        // ... 
                    }
                }
            });
            mask.registerHandler(':bazCompo', mask.Compo({/*implement*/}));
            mask.registerHandler(':quxCompo', mask.Compo({/*implement*/}));
            mask.run(App);
        </script> 
    </body>
</html>
  • 0.12.2

    • slot and event javascript handlers (handler)
    • style node syntax support with (style)
      • :host, :host() support
      • scoped css support (IE6+)
      section {
          style scoped {
              span {
                  color: red;
              }
          }
          span > 'Hello World'
      }
  • 0.9.6

    • Merge feature for better encapsulation, e.g:
        define :dialog {
            .wrapper > .modal {
                .modal-header {
                    @title;
                    .close;
                }
                .modal-content > @body;
            }
        }
        // ..
        :dialog {
            @title > 'Hello'
            @body  > 'World!'
        }
  • 0.9.1

    • Expressions:

      • Accessors with Bracket notation: ~[foo[bar]],~[foo["key"]]
    • VarStatement:

          ul {
              var list = ['foo', 'bar'];
              for(key of list){
                  li > '~[key]'
              }
          }
          /* renders to:
           * <ul><li>foo</li><li>bar</li></ul>
           */
  • 0.9.0

    • Syntax: (statements)
      • if (expression) { ... } else if (expr) {} else {}
      • for (el of array) { ... }
      • for ((el,index) of array) { ... }
      • for (key in object) { ... }
      • for ((key, value) in object) { ... }
      • each (array) { ... }
      • with (obj.property.value) { ... }
      • switch (value) { case (expression) { ... } /*...*/ }
    • Controllers scoped model
    • IncludeJS integration
        include ("./UserTemplate.mask") { 
            for(user in users) {
                import('UserTemplate');
            }
        }
  • 0.8.1

    • To get components/context property values use special symbols:

      • '~[$c.compoName]' // component's property sample
      • '~[$a.id]' // component attribute's property sample
      • '~[$ctx.page.id]' // context's property sample
  • 0.8.0

    • Async components. If a components needs to accomplish any async task, it can be done in renderStart/onRenderStart function using Compo.pause(this, ctx) / Compo.resume(this, ctx)
          mask.registerHandler(':asyncCompo', mask.Compo({
              onRenderStartfunction(modelctx){
                  var resume = Compo.pause(this, ctx);
                  
                  someAsyncJob(function(){
                      resume();
                  });
              }
          }));
  • 0.7.5

    • Binded Percent Handler - if, `each
  • 0.7.0

    • Expressions parser. Samples:

      • ~[:controllerFunction(userName.toUpperCase()) + ';']
      • ~[:user && user.id || "Log in"]
    • Variables/Functions look up (deprecated) upd: removed:

      1. model
      2. ctx
      3. controller
      4. up in controllers tree
  • 0.6.95
    • Use ~[] for string interpolation instead of #{}, as mask templates are already overloaded with '#','{' and '}' usage

      mask.setInterpolationQuotes('#{','}') - for fallback (or any other start/end, caution - start should be of 2 chars and the end of 1


:copyright: MIT - 2015 Atma.js Project