ChristopherBull/Clear-Links

Remove JQuery

Closed this issue · 1 comments

The included JQuery is outdated. Rather than upgrading it, I want to remove it and replace its usage with modern native JS equivalents.

This will reduce extension size, give minor performance improvements (e.g., load times; not that this was an issue before), and reduce reliance on dependencies.

Remove JQuery from:

  • Background
  • Content Script (scripts injected into web pages)
  • Options page

Would this help ? I just wrote it from scratch, feel free of using it.

  • In contentScript.js and options.js, replace $ by CL$, remove jQuery, and add on the first line import { CL$ } from './CL.js';.

  • Create a new file CL.js

export default class CL
{
    static parseHTML(html)
    {
        let tmp = document.createElement('cl-tmp');
        tmp.innerHTML = html;
        return new CL(tmp.childNodes);
    }
    static contains(host,element)
    {
        if(!element.parentNode)
            return false;
        if(element.parentNode === host)
            return true;
        return CL.contains(host,element.parentNode);
    }

    static onecallback(callback,event)
    {
        if(callback.done)
            return;
        callback.done = true;
        callback.call(this,event);
    }
    static datacallback(callback,data,event)
    {
        if(data !== undefined)
            event.data = data;
        callback.call(this,event);
    }

    constructor(selector,context)
    {
        if(selector.constructor === this.constructor)
        {
            this.selector = selector.selector;
            this.context = selector.context;
            this.elements = selector.elements;
        }
        else if(typeof(selector) == 'function')
        {
            selector();
            this.context = undefined;
            this.elements = [];
        }
        else if(typeof(selector) == 'string')
        {
            this.selector = selector;
            this.context = context;
            this.elements = Array.from((context?(typeof(context)=='string'?document.querySelector(context):context):document).querySelectorAll(selector));
        }
        else
        {
            if(selector.constructor === NodeList)
                selector = Array.from(selector);
            this.selector = undefined;
            this.context = undefined;
            this.elements = selector?(selector.constructor===Array?selector:[selector]):[];
        }
        this.dataStore = {};

        for(let pos=0; pos<this.elements.length; pos++)
            this[pos] = this.elements[pos];
    }

    children()
    {
        let childNodes = [];
        this.elements.forEach(element=>element.childNodes.forEach(childNode=>childNodes.push(childNode)));
        return new CL(childNodes);
    }
    first()
    {
        return new CL(this.elements[0]);
    }
    next()
    {
        return new CL(this.elements[0].nextElementSibling);
    }
    last()
    {
        return new CL(this.elements[this.elements.length-1]);
    }

    one(eventName,selector,data,callback)
    {
        if(callback === undefined)
        {
            if(typeof(selector) == 'string')
            {
                callback = data;
                data = undefined;
            }
            else
            {
                callback = data;
                data = selector;
                selector = undefined;
            }
        }
        if(callback === undefined)
        {
            callback = data;
            data = undefined;
        }

        this.on(eventName,selector,data,CL.onecallback.bind(this,callback));
    }
    on(eventName,selector,data,callback)
    {
        if(callback === undefined)
        {
            if(typeof(selector) == 'string')
            {
                callback = data;
                data = undefined;
            }
            else
            {
                callback = data;
                data = selector;
                selector = undefined;
            }
        }
        if(callback === undefined)
        {
            callback = data;
            data = undefined;
        }

        if(selector)
            this.elements.forEach(element=>element.querySelectorAll(selector).forEach(child=>child.addEventListener(eventName,CL.datacallback.bind(child,callback,data),false)));
        else
            this.elements.forEach(element=>element.addEventListener(eventName,CL.datacallback.bind(element,callback,data),false));
        return this;
    }
    unbind(eventName,callback)
    {
        this.elements.forEach(element=>element.removeEventListener(eventName,callback.bind(element),false));
    }
    click(data,callback)
    {
        return this.on('click',data,callback);
    }
    mousemove(data,callback)
    {
        return this.on('mousemove',data,callback);
    }
    change()
    {
        console.warn('WIP trigger onchange');
        return this;
    }

    data(propertyName,propertyValue)
    {
        if(propertyValue === undefined)
            return this.dataStore[propertyName];
        this.dataStore[propertyName] = propertyValue;
        return this;
    }
    attr(propertyName,propertyValue)
    {
        if(propertyValue === undefined)
            return this.elements[0].getAttribute(propertyName);
        this.elements.forEach(element=>element.setAttribute(propertyName,propertyValue));
        return this;
    }
    prop(propertyName,propertyValue)
    {
        if(propertyValue === undefined)
            return this.elements[0][propertyName];
        this.elements.forEach(element=>element[propertyName]=propertyValue);
        return this;
    }
    val()
    {
        return this.elements[0].value;
    }
    height()
    {
        if(this.elements[0].innerHeight)
            return this.elements[0].innerHeight;

        let hidden = this.elements[0].style.display !== 'block';
        if(hidden)
            this.elements[0].style.display = 'block';
        let connected = this.elements[0].parentNode;
        if(!connected)
            document.body.appendChild(this.elements[0]);
        let height = this.elements[0].offsetHeight;
        if(!connected)
            document.body.removeChild(this.elements[0]);
        if(hidden)
            this.elements[0].style.display = 'none';
        return height;
    }
    width()
    {
        if(this.elements[0].innerWidth)
            return this.elements[0].innerWidth;

        let hidden = this.elements[0].style.display !== 'block';
        if(hidden)
            this.elements[0].style.display = 'block';
        let connected = this.elements[0].parentNode;
        if(!connected)
            document.body.appendChild(this.elements[0]);
        let width = this.elements[0].offsetWidth;
        if(!connected)
            document.body.removeChild(this.elements[0]);
        if(hidden)
            this.elements[0].style.display = 'none';
        return width;
    }

    text(textContent)
    {
        this.elements[0].textContent = textContent;
        return this;
    }
    html(innerHTML)
    {
        this.elements[0].innerHTML = innerHTML;
        return this;
    }
    append(element)
    {
        if(element.constructor === CL)
            element.elements.forEach(child=>this.elements[0].appendChild(child));
        else
            this.elements[0].appendChild(element);
        return this;
    }

    css(propertyName,propertyValue)
    {
        if(typeof(propertyName) === 'string')
        {
            if(propertyValue === undefined)
                return this.elements[0].style[propertyName];
            this.elements.forEach(element=>element.style[propertyName]=propertyValue);
            return this;
        }

        for(let tmppropertyName in propertyName)
            this.css(tmppropertyName,propertyName[tmppropertyName]);
        return this;
    }
    show()
    {
        return this.css('display','block');
    }
    hide()
    {
        return this.css('display','none');
    }

    delay()
    {
        console.warn('WIP delay');
        return this;
    }
    fadeIn(duration)
    {
        if(!duration)
            duration = 500;

        clearTimeout(this.fadeintimeout1);
        clearTimeout(this.fadeouttimeout1);
        clearTimeout(this.fadeouttimeout2);

        this.css('transition','none');
        this.css('opacity',0);
        this.show();
        this.css('transition','opacity '+(duration/1000)+'s');
        this.fadeintimeout1 = setTimeout((function(){
            this.css('opacity',1);
        }).bind(this),200);
        return this;
        return this;
    }
    fadeOut(duration)
    {
        if(!duration)
            duration = 500;

        clearTimeout(this.fadeintimeout1);
        clearTimeout(this.fadeouttimeout1);
        clearTimeout(this.fadeouttimeout2);

        this.css('transition','none');
        this.css('opacity',1);
        this.show();
        this.css('transition','opacity '+(duration/1000)+'s');
        this.fadeouttimeout1 = setTimeout((function(){
            this.css('opacity',0);
            this.fadeouttimeout2 = setTimeout(this.hide.bind(this),duration);
        }).bind(this),200);
        return this;
    }
    focusout()
    {
        console.warn('WIP focus out');
        return this;
    }
    finish()
    {
        console.warn('WIP finish');
        return this;
    }
    stop()
    {
        console.warn('WIP stop');
        return this;
    }

    tabs()
    {
        new CLTabUI(this.selector);
        return this;
    }

    accordion(options)
    {
        new CLAccordionUI(this.selector);
        return this;
    }
}
export const CL$ = (selector,context)=>new CL(selector,context);
CL$.parseHTML = CL.parseHTML;
CL$.contains = CL.contains;

class CLTabUI
{
    static
    {
        let css = [];
        css.push('cltabui-css{display:none;}');
        css.push('[cltabui-host]{}');
        css.push('[cltabui-host] ul{list-style-type:none;display:flex;}');
        css.push('[cltabui-handle]{cursor:pointer;padding:0.5em 1em;margin:1px 0.2em 0 0;}');
        css.push('[cltabui-handle] a{text-decoration:none;color:inherit;}');
        css.push('[cltabui-handle="active"]{background:#007fff;border:1px solid #003eff;color:#ffffff;}');
        css.push('[cltabui-handle="inactive"]{background:#f6f6f6;border:1px solid #c5c5c5;color:#333333;}');
        css.push('[cltabui-content]{}');
        css.push('[cltabui-content="active"]{display:block;}');
        css.push('[cltabui-content="inactive"]{display:none;}');
        
        let style = document.createElement('cltabui-css');
        style.innerHTML = '<style type="text/css">'+"\n"+css.join("\n")+"\n"+'</style>';
        document.body.appendChild(style);
    }
    constructor(selector)
    {
        let host = document.querySelector(selector);
        host.setAttribute('cltabui-host','');

        let handles = document.querySelectorAll(selector+'>ul>li');
        let contents = [];
        for(let pos=0; pos<handles.length; pos++)
        {
            let handle = handles[pos];
            let link = handle.querySelector('a[href^="#"]');
            let content = document.querySelector(selector+'>'+link.getAttribute('href'));
            handle.setAttribute('cltabui-handle',pos==0?'active':'inactive');
            content.setAttribute('cltabui-content',pos==0?'active':'inactive');
            handle.onclick = this.onclick.bind(this,handle,content);
            contents.push(content);
        }
        this.handles = handles;
        this.contents = contents;
    }
    onclick(currenthandle,currentcontent,event)
    {
        event.stopPropagation();
        event.preventDefault();

        let handles = this.handles;
        let contents = this.contents;
        for(let pos=0; pos<handles.length; pos++)
        {
            let handle = handles[pos];
            let content = contents[pos];
            handle.setAttribute('cltabui-handle',handle===currenthandle?'active':'inactive');
            content.setAttribute('cltabui-content',content===currentcontent?'active':'inactive');
        }
    }
}

class CLAccordionUI
{
    static
    {
        let css = [];
        css.push('claccordionui-css{display:none;}');
        css.push('[claccordionui-host]{}');
        css.push('[claccordionui-handle]{cursor:pointer;padding:0.5em 0.5em 0.5em 0.7em;margin:2px 0 0 0;}');
        css.push('[claccordionui-handle="active"]{background:#007fff;border:1px solid #003eff;color:#ffffff;}');
        css.push('[claccordionui-handle="active"]:before{content:\'v\';}');
        css.push('[claccordionui-handle="inactive"]{background:#ededed;border:1px solid #cccccc;color:#2b2b2b;}');
        css.push('[claccordionui-handle="inactive"]:before{content:\'>\';}');
        css.push('[claccordionui-content]{padding:1em 2.2em;background:#ffffff;border:1px solid #dddddd;border-top:0;color:#333333;}');
        css.push('[claccordionui-content="active"]{display:block;}');
        css.push('[claccordionui-content="inactive"]{display:none;}');
        
        let style = document.createElement('claccordionui-css');
        style.innerHTML = '<style type="text/css">'+"\n"+css.join("\n")+"\n"+'</style>';
        document.body.appendChild(style);
    }
    constructor(selector)
    {
        let host = document.querySelector(selector);
        host.setAttribute('claccordionui-host','');

        let handles = document.querySelectorAll(selector+'>h3');
        let contents = document.querySelectorAll(selector+'>div');
        for(let pos=0; pos<handles.length; pos++)
        {
            let handle = handles[pos];
            let content = contents[pos];
            handle.setAttribute('claccordionui-handle','inactive');
            content.setAttribute('claccordionui-content','inactive');
            handle.onclick = this.onclick.bind(this,handle,content);
        }
    }
    onclick(currenthandle,currentcontent,event)
    {
        event.stopPropagation();
        event.preventDefault();

        currenthandle.setAttribute('claccordionui-handle',currenthandle.getAttribute('claccordionui-handle')=='inactive'?'active':'inactive');
        currentcontent.setAttribute('claccordionui-content',currentcontent.getAttribute('claccordionui-content')=='inactive'?'active':'inactive');
    }
}