m2jobe / mag.js

MagJS - Modular Application Glue

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Mag.JS - Elegant DOM Bindings

Intuitive, tiny, fast, JavaScript 2 HTML component templating library.


Features

  • Changes to state are immediately reflected in the dom by their element matchers. Super crazy fast & 8KB Gzip!
  • Valid HTML templates - No virtual/shadow dom or new templating language!
  • Semantic data binding - Use normal HTML as a template and a related module (plain JS object) as instructions for transpiling/interpolations.
  • Module has a constructor, called once and a viewer called on every change to the state of that module.
  • Collection rendering - No need for hand-written loops. Write templates as a part of the HTML, in plain HTML
  • View logic in JavaScript - No crippled micro-template language, just plain JavaScript functions
  • Native events & attributes, full life cycle events control, Hookin to modify and create custom attributes
  • Built-in observables on existing data structures like objects, arrays for state and props reactivity

Mag.JS - Elegant DOM Bindings

"There is no JavaScript code in the HTML and there is no HTML code in the JavaScript!"

Hello MagJS!

Initial dom:
<div id="hello">
  <h1></h1>
</div>

Module:
mag.module('hello', {
  view: function(state) {
    state.h1 = 'Hello Mag.JS!'
  }
})

Mag.JS dom!:
<div id="hello">
  <h1>Hello Mag.JS!</h1>
</div>

View receives 2 arguments, "state" & "props"

  1. state is the DOM element(s) we want to set/get - the element Matchers and their controls
  2. Any change to the state object will trigger a redraw of the view - it is observed.
  3. props is what we want the DOM element(s) to be set to - the data
  4. If the props have changed a new view redraw will run if triggered.
  5. props are passed from the parent and is set by mag.module() or an instance instance (props)
  6. Components can reference others Components in props to pass children elements directly into their output: props.children = mag()

Dynamic node example with controller

mag.module(document.body, {
  controller: function() {
    this.h2 = 'Hello MagJS World!';
  }
});

Controllers are only called once vs views which are re-run on every change to the state. Example - Prevent initial placeholder flicker

Two way binding

var Component = {
  controller: function() {
    this.yourName = '';
  }
}

Just define the default value of the selector to bind.

Tutorials

Hello world!

JSBin - Ternary - Controller - Handler - Merge props - Subscribe - Proxy - No view only controller - Component prop - Parent Child Controller Comps - Componentized - Dynamic templates

Initial html

<div id="hello">
  <label>Name:</label>
  <input type="text" placeholder="Enter a name here" />
  <hr/>
  <h1>Hello <span class="name"></span>!</h1>
</div>

Module:

mag.module("hello", {
  view: function(state) {
    state.input = {
      _oninput: function(e) {
        state.name = e.target.value
      }
    }
  }
})

Boilerplates

Blank JSBin - Blank CodePen - Event Module - JSbin Addons - CodePen Addons (Simple Modal) - Plunker - Plunker Namespace - Plunker Templating - Stateless

Examples

Hello world - Hello world, take2 - With dynamic Node

Components

Simple Component Container - Reusable Clock component - Master detail - Stateless components perf - Clocks

Component Composition containment - Component composition specialization

props.children accessor - Access static children of placeholder from inner module - Specialization with props.children - Shared state

Reusable Tooltip Component - Controller only

Simple Wrapped component(HoC) - Reusable Container(HoC) - Switch and Remove Comps

Simple tabbed content - Initialization

Toggle Button - Custom Component - Counter Component

Async

SetTimeout

Native promise

Using fetch to render json data

Stateless

Timer - Counter - Interaction with state

Simple Todos - Stateless counter

Icons as Components - Deep Nesting (keys, children, create)

Recursive Error

App State

Shared across modules - Synchronous state

Forms

Select menu - Linked menus

Multiple Radio selection - Multiple Select Menu

Transform Input - Concatenate - To Upper Case

Slider - Textarea - Remove elements temporarily

Dynamic Radio Group - Pre-selected - Static group - MagJS Radio Button / Pads

Basic Math: addition - Basic Math: addition (no auto binding) - Take 3 - Take 4 - V0.12 auto wiring - Video tutorial - Nested data auto wiring

Auto wiring - select menu - Single form create/edit

Quiz - Instant validation - Field valid - Simple

Forms - input handling - AutoComplete w/spinner - Select menu - Select addon

Forms - passFail component - Search Highlight

Form & list - model - comps - boilerplate

Simple messaging component example - Video tutorial - Take 2, w/Reusable child component - Nested components

Hello world with passFail reusable component - Modal component w/CSS Transitions

Hello world (proxy w/Config) - Without controller - Without config or controller - Take2

Events

Hello array lifecycle event

Lifecycle Disable & re-render

Parent Eventing - Node switching - Event handler in Child

Count - Dynamic reusable counter component - With shared state

Lists

Simple List - Reverse list

More lists

Sortable List

Tiny filter

Filter list - Leaderboard

Filter list sort

Filter list components

List of Lists of items

Loading and using external data

Modals

Tab component - Take2

Modal component - Reusable Modal Component

Modal with select menu - Modal Child Component

Modal with external template shared appState - Alternate with mag - Alternate with create - Addons vs Extends - mag.template

Forms - composable components - link manager

Todos Take2 - Take3 - Simple v0.2 - Simple v0.2 component, Take2 - v0.24

Todos

Todo Proxy

Todos (expanded)

Contacts - Take 2

Async

Async - Geo Location

Infinity scroll

Animation

CSS transitions - Animation, FadeIn, FadeOut - Message Fader Component

Movie plotter service - Plot selection

Mithril 2 Mag

Rotate Links - Alternate - Web service - Loader

Pagination

Function composition - Todos

Volunteer form application

StopWatch

Shopping Cart

Ajax Github Api

REM API - User list

Simple Application - Tutorial

React 2 Mag

Getting started with React example - Affix - Controller default

Navigation menu - As Component

Timer - Timer components

TabList - key components - Without Binding

TabList module pattern - dynamic children keys - Video Tutorial

Real-time search Same with different code style - creative Mag.JS!

FilterableProductTable/static version (Thinking in React tutorial)

FilterableProductTable (Thinking in React tutorial)

Occlusion culling - Latest

Tab state (From Why React is Awesome)

Board Switcher - Stateless board

Weather App Take 2

Comment Box - Video tutorial - Take1, Take 2 - Take3 - MagJS v0.14 - Module Pattern Video tutorial

Image app with AJAX

Employee Directory with tiny Router - Take2 - Latest

News feed with undo state

Mag Redux implementation

Mag Redux Async

Angular 2 Mag

Todos

Order form

Navigation menu

Switchable Grid

Phone Gallery

Contact Manager application - Take 2

Country App - JSON/Routing

More advanced examples

State Scope

The scope of a components state/selectors is limited to its template elementId|Node

A nested component, child of a parent, will not be effected by the parent's state element selectors.

Statefull Example - Stateless Example

Statefullness

When redrawing the view method is called. To maintain statefulness we can use the controller method. Plainly these are default values.

HTML for below examples:

<div id="lister">
  <h2></h2>
  <ul>
    <li class="item"></li>
  </ul>
</div>

Example without controller

mag.module('lister', {
  view: function(state, props, element) {
  state.item = [1, 2, 3]
  state.title = 'Lister'
    state.h2 = {
      _text: state.title,
      _onclick: function() {
        state.show = state.show ? !state.show : true
        state.item.reverse()
        state.title = 'Gister' + state.show
      }
    }
  }
})

Example with controller

mag.module('lister', {
  controller: function(props) {
    this.item = [1, 2, 3]
    this.title = 'Lister'
  },
  view: function(state, props, element) {
    state.h2 = {
      _text: state.title,
      _onclick: function() {
        state.show = state.show ? !state.show : true
        state.item.reverse()
        state.title = 'Gister' + state.show
      }
    }
  }
})

This link displays both for comparison: http://jsbin.com/fopunubogi/edit?html,output

You can see that the first one when clicked nothing is changed while the second is dynamic. The reasons is simply because the controller is called once while the view is called on every redraw/action/state change.

Here's an alternative approach to the above that only uses a view method and no controller for a similar result: http://jsbin.com/xayobawuxo/edit?html,output

Example with config and without controller

mag.module("lister", {
  view: function(state) {
    var name1 = 'Yo!',
      name2 = 'Joe!'
    state.h2 = {
      _config: function(node, isNew) {
        if (isNew) {
          state.span = name1
          state.item = [1, 2, 3]
        }
      },
      _onclick: function() {
        state.item.reverse()
        state.span = state.span == name1 && name2 || name1;
      }
    }
  }
})

This is similar to using a controller or onload. Every element has a _config to act as onload for hookins. It receives 4 arguments:

  1. is the element itself
  2. is a boolean stating if this is attaching or not, first run is always true, subsequent executions are always false
  3. context is an object that can be used to pass values to the method itself on every iterative call
  • a. one available sub method of context is onunload e.g. context.onunload = fun is called when the element is removed from the dom.
    • context.onunload (configContext, node, xpath)
  1. Index- the x path based index of the element

Simple API

mag.module (String domElementID|Element Node, Object ModuleDefinition, Optional Object DefaultProperties )

This is the core function to attach a object of instructions to a dom element, when called it is executed.

ModuleDefinition is the instructions it can have a controller and a view function.

var component = {
  view: function (state, props, element) {
  }
}

view receives three arguments: state, props and element

  • State is the object used to transpile the dom
    • e.g. state.h1 ='Hello' converts the first h1 tag in the element to that value
  • Props is the optional properties object passed to its mag.module definition
  • Element is the node itself whose ID/Node was passed to its mag.module definition

The controller function has access to the original props as well as all life cycle events, it is only called once.

var component = {
  controller: function (props) {
    this.didupdate = function (Element, currentProps, instanceId) {}
  },
  view: function (state, props, Element) {
   this.state, this.props, this.element
  }
}

this in the view has access to the 3 main properties of this.state, this.props and this.element All _on events context is this

mag (String domElementID|Element Node, Object ModuleDefinition, Optional Object DefaultProperties)

This is a shortcut method to the internal makeClone function returned by mag.module

returns a function to run the module and template with given props.

//Define Component:
var CounterComponent = {
  view: function(state, props) {
    state.div = "Count: " + props.count;
  }
}

//Wire it:
var Counter = mag('counter', CounterComponent);

//Run:
var Element = Counter({count: state.count});

//Attach to state:
state.counter = Counter({count: state.count});

//Reflect on the component
Counter.getProps() ..

Example - Stateless

mag can also be used to create stateless components   Which are helpful in constructing the UI.

Note: There are subtle differences between mag() and mag.module.

  • Skips mag.module setup
    • The major difference is that the normal setup in mag.module is not run on the template node. Therefore, the instance does not exist until it is called.
    • This means there is no pre-loading and caching in the UI and that it only runs on the template clone not the template itself. Example with mag - Direct with mag.module - Same with mag.module - Clone with key
  • Each call to mag() with the same ID/Node reuses it by default
  • mag() defaults to a reference and does not create unique keys for you automatically. Example
  • In order to reuse one instance uniquely you must pass a key via props. Example or use mag.create from the AddOns.
  • Or simply call mag() again. Example - List - Defined - Button Factory

Lifecycle methods

There are 8 life cycle events: willload, willgetprops, didload, willupdate, didupdate, isupdate, onbeforeunload, onunload

They each get the same 3 parameters, their context is the Object no need to bind to this:

  • Element is the original module definition ID element
  • newProps is the active state of the props, since the controller is only called once, the original props parameter contains the original default values.
  • instance ID - Internal Mag.JS ID, can be used for reflection
  • [nextProps (4th argument in willgetprops, contains the next props)]
  • [done() (4th argument in onbeforeunload, function to call when completed)]

To prevent default from any Life Cycle method- stop continued processing return false

this.willgetprops = function(node, currentProps, instanceId, nextProps) {
  if (currentProps == nextProps) {
    return false
  }
}

Optionally, all life cycle methods can also be Object methods

var Component = {
  willload: function() {
    mag.merge(this.state, this.props)
    this.state.input = {
      _oninput: () => {
        this.state.name = this.state.hello ? ' ' + this.state.hello + '!' : '?'
      }
    }
  }
}

Try it on JSBin - With Props Update

Reflection

Use the live instance or an instance ID

var instance = mag.module ('myElementId'|Element Node, component);

Returns a function Object that can be used to create a clone of the instance and the instances information such as InstanceID.

The function object to create a clone instance requires an index/key in its only parameter. When assigned to a state elementMatcher, MagJS does that for you.

These 8 methods are bound to the exact instance

getId draw getState getProps clones destroy subscribe - multiple subscribers allowed! returns a remove function rafBounce([Optional Boolean]) - returns Boolean. Use to change the rendering default engine of rAF throttle to rAF debounce

Inner Reflection

removeSelfFunc = mag.mod.onLCEvent('didupdate', instanceId, handlerFunc)

Available on all life cycle methods for any instanceId multiple handlers per event and instanceID are accepted.

returns a remover function, call to stop the handler from being executed.

For inner reflection the instanceID is available in all lifecycle methods, Note that this is not the elementID but instead the internal MagJS ID for each component includes clones, example:

mag.redraw(mag.getNode(mag.getId(instanceID)), instanceID, 1);

mag.create (String elementID|Element Node, Object ModuleDefinition, Optional Object props) - In the Addons

Wraps around mag.module to return a reference instance you can call later. The reference function can also over write the defaults given in create usually it will only over write the props

var myComponent = mag.create('mydomId', {}) // not executed

var instance = myComponent({props:[]}) // executed 
//add a props.key for a unique component instance or else each call reuses existing.

// instance contains 7 sub methods 

instance.getId();
//returns instance UID for MagJS
//Use mag.getId(instanceId) to get the Node id and mag.getNode(ID) to get the Node itself

instance.draw() // redraws that unique instance, wrap in setTimeout for async
// optional boolean to force redraw i.e. clear the instance's cache instance.draw(true)
// `returns` a Promise resolved on rAF

instance.getState();
//Returns a copy of the current state values of that instance - state is async 

instance.getProps();
//Returns a copy of the current props values of that instance, defaults to bound instance

instance.clones();
//v0.22.6 returns list of any clones with their associated instanceId, and its own subscribe handler.

instance.subscribe(function(state, props, node, previous){});
//v0.22.1 assign handler to an instance to be notified on unqiue changes after life cycle event `didupdate`

instance.destroy([Optional RemoveBoolean]);
//v0.23.5 - if optional remove boolean is true the entire node is removed.
// this calls all nodes config unloaders and the controllers onunload event which can preventDefault.

 instance.rafBounce([Optional  Boolean]);
 //v0.27.2 - returns Boolean flag - used to change the rendering default engine of raf throttle to raf debounce

// instance can be called directly with an index/key to clone the instance, usefull in data arrays
instance('myUniqueKeyIndex') // Usually not called directly, MagJS will create index when attached to state

// returns the live node clone

Normally there's no need to call the instance constructor function directly. When passed to a state object MagJS will create the index for you with or without a key provided in props.

state.myELementMatcher = myComponent({
  props: []
})

// array
state.myELementMatcher = [myComponent({
  props: [3, 2, 1]
}), myComponent({
  props: [1, 2, 3]
})]

//Array object
state.myELementMatcher = [{
  item: myComponent({
    props: [3, 2, 1]
  })
}, {
  item: myComponent({
    props: [1, 2, 3]
  })
}]

JSBin example

Control redrawing flow

mag.redraw (node Element, idInstance magId, optional force Boolean)

initiate a redraw manually

Optional boolean argument to force cache to be cleared

returns a Promise which is resolved when the requestAnimationFrame is run.

mag.begin ( int MagJS uid)

var instance = mag.module('app', module)
mag.begin(instance.getId())
// run some long standing process without redrawing the module no matter what the state changes are

Once called the module will not run a redraw until the corresponding mag.end(id) is called even if instance.draw() is called and even with the optional instance.draw(force true)it will not run.

mag.end ( int MagJS uid)

// run the redraw for the module
mag.end(instance.getId())

This will run the last redraw for the instance assuming the number of begins match the number of ends called.

If you call mag.begin(id) for the same instance ID twice you must call mag.end(id) the same number of times before it will run the redraw.

This is typically not necessary especially since MagJS runs updates to the module state very efficiently via the rAF (requestAnimationFrame)

rAF mag.rafRate, mag.rafBounce AND instance.rafBounce()

Option to select the requestAnimationFrame rendering strategy.

There is an optional global mag integer (defaults to undefined) mag.rafRate this will effect the utils.scheduleFlush rAF refresh rate.

There is an optional global mag boolean (defaults to undefined) mag.rafBounce this will effect the utils.scheduleFlush

If set to true performance/speed in rendering is enhanced but there can be a loss of smoothness in the dom painting such as jerky rendering.

You can also set per instance.rafBounce(Boolean) the desired rAF, true is the debounce, false (default) is to throttle.

Returns the current instance's boolean value.

Try it on JSBin

state object

State is the object that is watched for changes and is used to transpile the related dom parent element ID

there are 5 ways to reference an element within a module

  • class name
  • tag name
  • data-bind attribute value
  • id
  • or name attribute value

state.h1 will match the first h1 element within a module (element id or parent node)

This: <h1></h1>
With: state.h1 = 'Hello!'
Makes: <h1>Hello!</h1>

state.$h1 will match all h1s - greedy matcher, default only selects the first

To change the class for an element

This: <h1></h1>
With: state.h1 = { _class: 'header', _text : 'Hello!'} 
Makes: <h1 class="header">Hello!</h1>

_text and _html are used to fill an elements text node and not as an attribute below.

any prefix underscore will be an attribute except for _on that will be for events such as

state.h1 = { _onclick: function() { state.h1='clicked!' } } 
  • Events are bound to the module instance this.
  • this has this.props, this.state and this.element
  • Events receive arguments in this order f(event, index, node, data)

Lists

Dealing with lists are simple and intuitive, including nested lists with dynamic user based values.

The first list element is used as the template for all new items on the list For example:

<ul><li class="item-template"></li></ul>
state.li = [1,2]

Will render

<ul>
  <li class="item-template">1</li>
  <li class="item-template">2</li>
</ul>

Lists of Objects

<ul><li class="item-template">People: <b class="name"></b></li></ul>
state.li = [{name:'Joe'},{name:'Bob'}]

Will render

<ul>
  <li class="item-template">People: <b class="name">Joe</b>
  </li>
  <li class="item-template">People: <b class="name">Bob</b>
  </li>
</ul>

Nested Lists

<ul>
  <li class="item-template">Project: <b class="projectName"></b>
    <ul>
      <li class="doneBy">
        <name/>
      </li>
    </ul>
    <tasks/>
  </li>
</ul>
state['item-template'] = [{
    projectName: 'house cleaning',
    doneBy: [{
      name: 'Joe'
    }, {
      name: 'Bob'
    }],
    tasks: ['wash', 'rinse', 'repeat']
  }, {
    projectName: 'car detailing',
    doneBy: [{
      name: 'Bill'
    }, {
      name: 'Sam'
    }],
    tasks: ['wash', 'rinse', 'repeat']
  }]

Will render

<ul>
  <li class="item-template">Project: <b class="projectName">house cleaning</b>
    <ul>
      <li class="doneBy">
        <name>Joe</name>
      </li>
      <li class="doneBy">
        <name>Bob</name>
      </li>
    </ul>
    <tasks>wash</tasks>
    <tasks>rinse</tasks>
    <tasks>repeat</tasks>
  </li>
  <li class="item-template">Project: <b class="projectName">car detailing</b>
    <ul>
      <li class="doneBy">
        <name>Bill</name>
      </li>
      <li class="doneBy">
        <name>Sam</name>
      </li>
    </ul>
    <tasks>wash</tasks>
    <tasks>rinse</tasks>
    <tasks>repeat</tasks>
  </li>
</ul>

Try it on JSBin

Data binding List with user input

This is the power and intuitive nature of MagJS. This is what allows for effortless and rapid HTML template prototyping.

With a minimal amount of code and a single row HTML template we can create a dynamic data table list that automatically stays up to date with dynamic values such as user input.

We start with our pure HTML template:

<div id="tickets">
    <h2>How many tickets?</h2>
    <table>
      <tbody>
        <tr class="ticketTypeRow">
          <th class="ticketType">
            Senior
          </th>
          <td class="numberofTickets">
            <input name="quantity" type="number" min="0" maxlength="2" size="1">
          </td>
          <td class="timesX">x</td>
          <td>$ <span class="pricePerTicket"></span>
          </td>
          <td class="equals">= $</td>
          <td class="rowTotal">
            <input name="total" size="8" readonly="readonly" tabindex="-1" value="0.00">
          </td>
        </tr>
      </tbody>
    </table>
  </div>

Next we have our JavaScript data list:

var defaultProps = {
  ticketTypeRow: [{
    quantity: 0,
    ticketType: 'senior',
    total: 0.00,
    pricePerTicket: 5.99
  }, {
    quantity: 0,
    ticketType: 'adult',
    total: 0.00,
    pricePerTicket: 5.99
  }, {
    quantity: 0,
    ticketType: 'child',
    total: 0.00,
    pricePerTicket: 3.99
  }]
}

Where this data comes from or how it is loaded does not effect MagJS in any way. It can be async, iframe, web service, push, io sockets etc...

Normally we would mutate the data in some way through the native Array.map function to return a new Array that is bound by MagJS to our HTML template. In this example we are showing how that is not necessary.

Next, we create our module.

var Tickets = {}

Tickets.controller = function(props) {
  // merge the props with the module's state/html
  mag.utils.merge(this, props);
}

Tickets.view = function(state, props) {

  state.$quantity = {
    _onInput: function(event, index, node, data) {
      var total = state.ticketTypeRow[data.index].quantity * state.ticketTypeRow[data.index].pricePerTicket
      state.ticketTypeRow[data.index].total = total
    }
  }
}

As you can see we are not changing the props data array instead we are merging it directly into our state selectors.

Lastly we will now load the module for MagJS to do the DOM bindings:

mag.module("tickets", Tickets, defaultProps)

Try it on JSBin: Movie ticket quantity selection - Nested math input - Nested messaging components

Attributes

_html, _text, _on[EVENT], _config->context.onunload

to not overwrite an existing attribute use:

state.name._value = state.name._value + ''

event (e, index, node, data) default context is the target element

  • index is the xpath index of the node -1
  • data is the index data of the parent if in a list (map{path,data,node,index})
  • if promise is returned it will defere redraw until resolved

Events

Life cycle events in controller:

  • willload (node, props, instanceID)
  • willgetprops (node, props, instanceID, nextProps)
  • didload (node, props, instanceID)
  • willupdate (node, props, instanceID)
  • didupdate (node, props, instanceID)
  • isupdate (node, props, instanceID)
  • onbeforeunload (node, props, instanceID, done)
  • onunload (node, props, instanceID)

return false - will skip further execution.

It will call any onunload handlers in the current module (includes inner modules and _config onunloaders that are currently assigned)

controller -> this.willload

Native events: parameters -

state.matcher._onclick = function(e, index, node, data)

  • the event
  • the x path based 0 index
  • the node itself (default context)
  • the data of the closest parent list item (In nested lists, the first parent with data).

Config (DOM hookin)

_config (node, isNew, context, index)

Available on all matchers to hookin to the DOM

arguments :

  • node - the element itself

  • isNew is true initially when first run and then is false afterwards

  • context is a empty object you can use to pass to itself

    • context.onunload - will be attached to the current modules onunloaders and called if any lifecycle event triggers a stop by returning false
  • index is 0 based on xpath of the matcher

mag.hookin (type, key, handler)

Allows for custom definitions, see examples below Examples: Promise, binding, custom attributes and elements.. Hookins

Mag.JS AddOns!

Tiny sub library of reusable simple tools can be found here

  • router
  • ajax
  • Reusable utilities (copy, merge .. )
  • namespace
  • hookins

mag.namespace (String namespace, [Optional object Context])

//module library creation with single global namespace / package names
(function(namespace) {
  var mod = {
    controller:function(props){
    },
    view: function(state, props) {
    }
  }
  namespace.CommentBox = mod;
})(mag.namespace('mods.comments'));


var CommentsComponent = mag.create("CommentBox", mag.mod.comments, props);
CommentsComponent();

Allows you to easily add new namespaces to your composable components, useful in the module pattern.

Example of component Module Pattern - Video tutorial

Custom plugins

The ability to register handlers for attribute or value trans compilation.

For example, allow the attribute _className. Register a handler that on every definition will modify both the final attribute name and or the value.

mag.hookin('attributes', 'className', function(data) {
  var newClass = data.value
  data.value = data.node.classList + ''
  if (!data.node.classList.contains(newClass)) {
    data.value = data.node.classList.length > 0 ? data.node.classList + ' ' + newClass : newClass
  }
  data.key = 'class'
})

The above is in the MagJS addons library

Another example

Hookin when a specific elementMatcher is not found and return a set of element matches

// hookin to create an element if does not exist at the root level
mag.hookin('elementMatcher', 'testme', function(data) {
  // data.key, data.node, data.value

  var fragment = document.createDocumentFragment(),
    el = document.createElement('div');
    
  el.setAttribute('class', data.key)
  fragment.appendChild(el);

  var nodelist = fragment.childNodes;
  data.node.appendChild(fragment)

  data.value = nodelist
})

Other hookins such as key/node value!

Extends

Example of extending the core Mag.JS functionality seamlessly

Allow for external template loading:

//Syntax via mag.template extends
mag.module('template.html', {view: ()});
mag('template.html', {view: ()});

mag.module({templateUrl: 'template.html', view: ()});
mag({templateUrl: 'template.html', view: ()});

Notes

  • config attribute won't be called with inner id element matchers, use other element matcher selectors. Fixed in v0.25.5 Example

  • careful with module instance constructor, can stack overflow if circular reference. Don't call instance from within itself or on state, use separate module. See examples. Fixed: MagJS will throw a Error if it detects recursivity (a instance call within an instance call) - Try it on JSBin

  • object observe support for browsers (v0.22 uses native Proxy to observe)

<script src="//cdn.rawgit.com/MaxArt2501/object-observe/master/dist/object-observe-lite.min.js"></script>
  • Promise support for IE
<!--[if IE]><script src="https://cdn.rawgit.com/jakearchibald/es6-promise/master/dist/es6-promise.min.js"></script><![endif]-->

Performance

JSBin - dynamic re-rendering - v0.20.7 - v0.21.3 - v0.22 - Latest - Toggle rAF (Throttle vs Debounce)

Occlusion culling - v0.22 - Latest - Throttle rAF rate - Debounce

JSBin - reversing 1000s of rows - v0.22 - Latest - Optimized - Componentized

Dbmon Repaint rate - v0.23 - Latest

JsPerf v0.20.2 - JsPerf v0.20.2

JsPerf v0.20.3 - JsPerf v0.20.3

JSPerf v0.14.4

JSPerf v0.14.9

JSPerf v0.15

JSPerf v0.15.1

Inspired By & cloned from

Mithril.js, Fill.js, React.js, Angular.js, Fastdom

About

MagJS - Modular Application Glue

License:MIT License


Languages

Language:JavaScript 98.1%Language:HTML 1.3%Language:CSS 0.6%