Source: msg.js

/**
 * Msg Component<BR>
 * <BR><BR><img src=/tk/lib/components/w/img/msg.png width=30% style="border:1px lime dashed;padding:20px">
 * <BR><BR><a href="/tk/lib/components/w/html/msg.html">DEMO</a>
 */
class Msg extends HTMLElement {
    constructor() {
        wc.group("Msg.constructor")
	
        super();

        wc.groupEnd();
    };
    
    /**
     * Set observable values here. When Changed, attributeChangedCallback is invoked
     * @observedAttributes
     */
    static get observedAttributes() {
        wc.group("Msg.observedAttributes");

	this.observables = [];

        wc.groupEnd();
        return this.observables;
    };

    /**
     * Called when this is attached to DOM
     * @connectedCallback. 
     */
    connectedCallback() {
        wc.group("Msg.connectedCallback")
	
	// GET PROPERTIES AND INTERESTING ELEMENTS
	this._initialize();

	// ADD COMPONENT MARKTOP
	this.innerHTML = `
<div class="alert ${this.properties.type} alert-dismissible shadow" role="alert">
    <div class="container-fluid">
	<div class="row">
	    <div class="col-md-12">
		<table width="100%">
		    <tr>
			<td valign="top" width=30>
			    <div class="wc-msg-icon ${this.properties.type}-icon"></div>
			</td>

			<td class="wc-msg-container">
			    ${this.dom.content}
			</td>

			<td valign="top" align="right" class="div-close">
			    <span aria-hidden="true" class="wc-btn-close" style=font-size:32px;>&times;</span>
			</td>
		    </tr>
		</table>
	    </div>
	</div>
    </div>
</div>`

	if (this.properties.parent) {
	    let offset = $("#" + this.properties.parent).offset()
	    let top = offset.top;
	    let height = $("#" + this.properties.parent).height();
	    $(this).css("top", top + height + "px");
	} else {
	    $(this).css("top", 0);
	}

	// PUBLISH ALL EVENTS OF INTEREST
	this._publish();
	
	// SUBSCRIBE ALL EVENTS OF INTEREST
	this._subscribe();
	
	// ADD STATS AND OTHER FINAL STUFF
	this._finalize();

        wc.groupEnd();
    };

    /**
     * Initial Markup
     * @private
     * @_template
     */
    _template() {
        wc.group("Msg.template");
	
	var temp = this.dom.content;

        wc.groupEnd();
        return temp;
    };

    /**
     * Publish all events of interest
     * @private
     * @_publish
     */
    _publish() {
	wc.group("Msg.publish");

	let close = this.querySelector(".close");

	$(close).on("click", () => {
	    tkloading.hide();

	    wc.publish("wc-msg", {
		time: new Date().getTime(),
		action: "closed",
		id: this.id
	    });
	});

	wc.groupEnd();
    }

    /**
     * Subscribe all to events of interest
     * @private
     * @_subscribe
     */
    _subscribe() {
	wc.group("Msg.subscribe:", this.id);
	
	let self = this;

	// SUBSCRIBE TO REQUESTS FROM CLIENTS
	wc.subscribe(this.id, function(e) {
	    let json = e.detail
	    console.log("MSG DETAIL", JSON.stringify(json));
	    
	    // NOT MEANT FOR ME
	    if (json.id != self.id) {
		console.log("NOT FOR ME", json.msg.id, self.id);
		return
	    }

	    if ("action" in json === false) {
		self.show({id:"M2020: ", type:"error", html:"NO ACTION SPECIFIED", close: true, block: true})
	    }

	    json.msg.close   = json.msg.close    || false;
	    json.msg.block   = json.msg.block    || false;
	    json.msg.timer   = json.msg.timer    || 0;
	    json.msg.spinner = json.msg.spinner  || false;

	    switch(json.action) 
	    {
		case "show":
		self.show({"id":json.msg.id, "type":json.msg.type, html:json.msg.html, "close":json.msg.close, "block":json.msg.block, "timer":json.msg.timer, "spinner":json.msg.spinner})
		break;

		default:
		self.show({id:"M000: ", type:"error", html:"NO SUCH ACTION: " + json.action, close: true, block: true})
		break;
	    }
	});

	wc.groupEnd();
    }

    /**
     * Called with .setAttribute(...) function call
     * @attributeChangedCallback
     */
    attributeChangedCallback(attr, oldval, newval) {
        wc.group("Msg.attributeChangedCallback:", attr, oldval, newval);

	this.properties = this.properties || [];

	let obs = Msg.observedAttributes;

	for (let i = 0; i < obs.length; i++) {
	    if (newval) {
		this.properties[obs[i]] = newval;
	    }
	}
	
	// YOUR CODE FOR CHANGES GO HERE (MAYBE NULL FIRST TIME THROUGH)
	try {
	    switch(attr) 
	    {
		case "header":
		break;
		
		default:
		break;
	    }
	} catch(e) {
	    wc.warn(e.name + ' > ' + e.message);
	}

        wc.groupEnd();
    };

    /**
     * Stores DOM elements of interest for future use
     * @private
     * @_fetchElements
     */
    _fetchElements() {
	wc.group("Msg._fetchElements");
	
	this.dom = this.dom || [];
	this.dom.content = this.innerHTML;

	wc.groupEnd();
    };

    /**
     * Component attributes are _fetched and defaults are set if undefined
     * @private
     * @_fetchAttributes
     */
    _fetchAttributes() {
	wc.group("Msg._fetchAttributes");
	
	this.properties = {
	    uparam	: "",
	    cname	: "Msg",
	    author	: "Mel M. Heravi",
	    version	: "1.0"
	};
	
	// SAVE WIDGET SPECIFIC PROPERTIES
	this.propertiesW = [];
	
	// SAVE ALL OTHER PROPERTIES
	let attrs = wc.getAttributes(this)
	
 	for (var key in attrs) {
	    let attr = this.getAttribute(key) || this.properties.key;
	    this.properties[key]  = this.getAttribute(key);
	    this.propertiesW[key] = this.getAttribute(key);
	    wc.log(key + ": " + attrs[key]);
	}

	// SET ALL INITIAL ATTRIBUTES
 	for (var key in this.properties) {
	    switch(key) 
	    {
		case "header":
		break;
		
		default:
		break;
	    }
	}

	wc.log("ATTRIBUTES: ", this.properties);

	wc.groupEnd();
    };

    /**
     * configure the instance object and artifacts
     * @configure
     * @param {string} data use data if exist else use 'this.properties.cfg' parameter
     */
    configure(data) {
	wc.group("Msg.configure:", data);

	// IF JSON VARIABLE (data) IS PROVIDED
	if (data) {
	    this._process(data);
	} else {
	    let self = this;

	    $.getJSON(this.properties.cfg, function(data) {
		self._process(data);
	    }).fail(function(jqXHR, textStatus, errorThrown) {
		alert("ERROR: INCOMING TEXT " + jqXHR.responseText);
	    });
	}

	wc.groupEnd();
    };

    /**
     * _process the instance object and artifacts
     * @private
     * @_process
     */
    _process(data) {
	wc.group("Msg._process:", data);
	
	// DO WHATEVER WITH THE DATA
	
	wc.groupEnd();
    };

    /**
     * Initialize component
     * @private
     * @_initialize
     */
    _initialize() {
	wc.group("Msg._initialize:", this.id);

	// FETCH ALL INTERESTING ELEMENTS
	this._fetchElements();

	// FETCH ALL ATTRIBUTES
	this._fetchAttributes();
	
	wc.groupEnd();
    };

    /**
     * Save data for analytics and final wrap up
     * @private
     * @_finalize
     */
    _finalize() {
	wc.group("Msg._finalize:", this.id);

	this.classList.add("wc");

	// ADD ANALYTICS HERE
	wc.setStats(this, this.properties.cname, this.properties.version);
	
	// SHOW IT NOW (NO FLICKERS) 
	this.style.visibility = "visible";

	wc.groupEnd();
    };

    /**
     * Invoked When component is removed. Usually with a .remove() function call
     * @disconnectedCallback
     */
    disconnectedCallback() {
        wc.group("Msg.disconnectedCallback")

	// FREE MEMORY AND CLEANUP

        wc.groupEnd();
    };

    /**
     * Destroy the instance object and artifacts
     * @destroy
     */
    destroy() {
	wc.group("Msg.destroy");

	// FREE ALL MEMORY
	// you should delete all created objects here
	
	// FREE POINTER
	delete this;
	
	// REMOVE ITEM FROM DOM
	this.parentNode.removeChild(this);

	wc.groupEnd();
    };

    /**
     * @show
     *
     * options:
     *     id:      message id
     *     type:    info, error, warning
     *     html:    html text to display
     *     close:   show/hide close btn
     *     block:   block screen or not
     *	   spinner: show/hide spinner
     *     timer:   null or milliseconds to remove
     *
     * closeCB: when user clicks "x" to close
     */
    show(options, closeCB) {
	wc.group("Msg.show:", options);

	let self = this

	let id      = options.id      || "";
	let type    = options.type    || "info";
	let html    = options.html    || "NO MESSAGE SET";
	let block   = options.block   || false;
	let spinner = options.spinner || false;
	let timer   = options.timer   || null;
	let close   = options.close   || false;

	closeCB = closeCB || null;

	$(this).find(".wc-msg-logo").attr("src", "/tk/lib/components/misc/webpack/src/w/assets/icons/wc-msg-" + type + ".png");
	$(this).find(".wc-msg-container").html(`${id} ${html}`);
	$(this).find(".alert").removeClass("wc-msg-error wc-msg-warning wc-msg-info").addClass("wc-msg-" + type);
	$(this).slideDown();

	$(this).find(".wc-msg-icon").removeClass("wc-msg-error-icon wc-msg-warning-icon wc-msg-info-icon").addClass("wc-msg-" + type + "-icon");

	let closebtn = this.querySelector(".wc-btn-close");

	if (close) {
	    $(closebtn).show();
	} else {
	    $(closebtn).hide();
	}

	tkloading.hide();
	
	$(closebtn).on("click", () => {
	    this.hide();
	    tkloading.hide();
	    
	    if (closeCB) closeCB();
	});

	if (block) {
	    tkloading.show();

	    if (spinner == false) {
		$(".tkloading").css("background-image","url()")
	    }
	} else {
	    tkloading.hide();
	}

	if (timer) {
	    setTimeout(function() {
		self.hide();
		tkloading.hide();
	    }, timer);
	}

	wc.groupEnd();
    };

    /**
     * hide a page
     * @hide
     */
    hide() {
	wc.group("Msg.hide");

	tkloading.hide();
	$(this).slideUp();
	
	wc.groupEnd();
    }

    /**
     * for testing purposes
     * @test
     * example: Msg.test();
     */
    static test() {
        wc.group("Msg.test:");

	// FOR TESTING
	let msg = document.querySelector("#my-msg");

	wc.publish("my-msg", {
	    time: new Date().getTime(),
	    id: "my-msg",
	    action: "show",
	    msg: {id:"<b>I-0200</b> ", type:"info", html:"HELLO WORLD!", timer:3000, block:true, close:true, spinner:true}
	});

        wc.groupEnd();
        return true
    };
}

window.customElements.define('wc-msg', Msg);

// SO I CAN CALL THE STATIC METHOD GLOBALLY
window.Msg = Msg;