Source: g:/development/project/web/thelab/suitjs/documentation/source/lapel.doc.js

/**
* Class that implements the feature of webcomponents and extends the functionalities of Suit.
* @class
* @type Lapel
*/
var Lapel = {};
(function(window,document,body) {

"use strict";

console.log("Lapel> Init v1.0.0");

//Blocks the mutation handling while components are parsed.
var m_mutationLock = false;

//Reference to the first parse.
var m_schedulerId = -1;

//Flag that indicates Lapel has made the first setup.
var m_booted = false;

//Flag that indicates the Suit core framework exists in the page.
var m_hasSuit = window.Suit != null;

/**
 * Container for added components. 
 * @type {LapelComponent[]}
 */
Lapel.components = [];

//Helper function to validate if a variable is null or the wrong type and return a default result.
function assert(v,d,t) { return t==null ? (v==null ? d : v) : ((typeof(v)==t) ? v : d);	}

/**
 * Lapel Web Component.
 * @typedef {Object} LapelComponent
 * @property {String} tag - HTML tag that defines this component. Lapel will either replace or insert the 'src' contents based on this tag.
 * @property {String} src - HTML content that will either replace or fill the Element.
 * @property {?Boolean} inner - Flag that tells if 'src' will replace the tag or fill it. Defaults to true.
 * @property {LapelComponentCallback} init - Method that handles this component initialization. Setup your logic on this callback.     
 */

/**
 * Lapel Component Init Callback.
 * @typedef {Object} LapelComponentCallback
 * @property {Element|Element[]} element - Created DOM Element. Allows to do extra operations using the new created component reference. If the 'src' of the component is made of several tags an Array is returned.       
 */

/**
 * Finds a LapelComponent template based on its tag name. If none is found, null is returned.
 * @param	{String} p_tag - HTML tag of the template. 
 * @returns {LapelComponent} - Reference to the lapel component template.
 * @example
 * //Define a simple component. 
 * Lapel.add({
 *  tag: "my-component",
 *  src: "<div class='my-component'></div>"
 * });
 * 
 * var c = Lapel.find("my-component");
 * console.log(c); //{ tag: 'my-component', src: "<div class='my-component'></div>" }
 */
Lapel.find = 
function find(p_tag) {

    var l = Lapel.components;
    for (var i=0;i<l.length;i++) if (l[i].tag == p_tag) return l[i];
    return null;
};

/**
 * Creates a new instance of a LapelComponent.
 * @param  {String|LapelComponent} p_tag - Name of the template's tag or reference to the component itself.
 * @param  {?Object} p_attribs - Table of attributes for the created Element.
 * @param  {?String} p_src - String that can fill the 'textContent' variable of the created Element. Defaults to empty.
 * @returns {Element} - Reference to the created Element with features of the component.
 * @example
 * //Define a simple component.
 * ```js 
 * Lapel.add({
 *  tag: "custom-button",
 *  src: "<button class='custom'></button>"
 * });
 * 
 * var bt = Lapel.create("custom-button",{name: "cbutton"},"Click Me!");
 * console.log(bt); //<button class='custom' name="cbutton">Click Me!</button>
 * ```
 */
Lapel.create =
function create(p_tag,p_attribs,p_src) {

    var c = (typeof(p_tag)=="string") ? Lapel.find(p_tag) : p_tag;

    if (c == null) { console.error("Lapel> Component ["+p_tag+"] not found"); return null; }
    
    var it = document.createElement(c.tag);
    
    if (p_attribs != null) for(var s in p_attribs) it.setAttribute(s,p_attribs[s]);
    
    if (p_src != null) it.textContent = p_src;

    return it;
};

/**
 * Register a new component to the component pool.
 * @param  {LapelComponent} p_component - Reference to the Lapel component.
 * @returns {LapelComponent} - Reference to the added component.
 * @example <caption>Given the script</caption>
 *  var c = {
 *  tag: "custom-component",
 *  src: "&lt;div class='custom'&gt;&lt;/div&gt;",
 *  init: function(p_element) {
 *      p_element.textContent = "I'm Here.";
 *  },
 *  inner: false
 * };
 * Lapel.add(c);
 * @example <caption>Add the custom tag in the HTML</caption> 
 * ```html 
 * <custom-component>Original Text</custom-component>
 * 
 * <!-- After Init -->
 * <div class='custom'>I'm Here.</div>
 * 
 * <!-- If `inner` is true, then the result will be. -->
 * <custom-component><div class='custom'>I'm Here.</div></component>
 * ```
 */
Lapel.add =	
function add(p_component) {		

    var l = Lapel.components;
    if (l.indexOf(p_component) >= 0) return p_component;
    
    var fn  = null;
    var evt = m_hasSuit ? "component" : "load";
    window.addEventListener(evt,
    fn = function lapelOnInit(ev) {

        if (m_schedulerId >= 0) window.clearInterval(m_schedulerId);
        m_schedulerId = window.setTimeout(function() { m_lapelBoot(); }, 1);			
        l.push(p_component);
        window.removeEventListener(evt,fn);
    });		

    if(p_component.inner==null) p_component.inner = true;
    if(p_component.init==null)  p_component.init  = null;

    return p_component;
};

//Startup Lapel after the first components.
var m_lapelBoot = 
function boot() {		

    if (!m_booted) {			

        m_mutationLock = false;
        if (window.MutationObserver != null) {

            var mo = new MutationObserver(m_lapelOnMutation);
            mo.observe(body, { subtree:true, childList:true } );
        }
        else {

            console.warn("Lapel> MutationObserver not found!");
        }
    }
    
    m_lapelSchedule();
    
    m_booted = true;
};

//Sweeps the DOM and insert components where they should appear.
var m_lapelParse =
function parse() {		

    var cl = Lapel.components;
    
    for (var i=0;i<cl.length;i++) {

        var c = cl[i];
        var l = body.querySelectorAll(c.tag);

        for (var j=0;j<l.length;j++) {				

            var v 		= l[j];
            var attribs = v.attributes;				
            var text    = v.textContent;

            if (v.isParsed != null) if (v.isParsed) continue;

            v.isParsed  = true;				
            v.innerHTML = c.src.replace("$text", text);

            m_lapelParseElement(c,v);
        }
    }		
};

//Finishes parsing the component element.
var m_lapelParseElement =
function parseElement(p_component,p_target) 	{

    var v = p_target;
    var c = p_component;

    window.setTimeout(
    function() {

        var p = v.parentElement;					
        for (var i=0; i<v.children.length;i++) {	

            var vc = v.children[i];
            for (var j=0;j<v.classList.length;j++) { 					

                if(!vc.classList.contains(v.classList[j])) vc.classList.add(v.classList[j]);
            }
            
            for (var j=0;j<v.attributes.length;j++) { 

                var a = v.attributes[j]; 
                if (a.name == "class") continue;
                vc.setAttribute(a.name, a.value);
            }
        }
        
        var is_inner = c.inner == null ? true : c.inner;

        if (is_inner) { if (c.init != null) c.init(v); return; }

        var l = [];

        var p = v.parentElement;					
        for (var i=0;i<v.children.length;i++) {	

            var vc = v.children[i];	
            l.push(vc);			
            if (v.nextSibling != null) { p.insertBefore(vc,v); } else { p.appendChild(vc); }
        }
        	
        if(l.length==1) { if (c.init != null) c.init(l[0]); } else
        if(l.length>1)  { if (c.init != null) c.init(l);	} else 
        if (c.init != null) c.init();	
        
        if(p!=null)p.removeChild(v);					

    }, 1);
};

//Callback that handles DOM changes.
var m_lapelOnMutation =
function onMutation(p_records,p_observer) {
    m_lapelSchedule();
};


//Schedules an atomic callback.
var m_lapelSchedule =
function schedule() {

    if (m_mutationLock) return;

    m_mutationLock = true;		
    m_lapelParse();
    window.setTimeout(function() { m_mutationLock = false; }, 2);
};

})(window,document,document.body);
comments powered by Disqus