Skip to main content

Azul Coding -
Make Your Own Lighter jQuery in TypeScript

TypeScript

// AZUL CODING ---------------------------------------
// Make Your Own Lighter jQuery in TypeScript
// https://youtu.be/QYRASC-i0Y0


function $<T extends Element = Element>(
    selector: string | T | T[] | NodeListOf<T>,
): AzulQuery<T> {
    return AzulQuery.create<T>(selector);
}

class AzulQuery<T extends Element = Element> {
    private elements: T[];
    [K: number]: T;

    private constructor(selector: string | T | T[] | NodeListOf<T>) {
        if (typeof selector === "string") {
            this.elements = Array.from(document.querySelectorAll<T>(selector));
        } else if (selector instanceof Element) {
            this.elements = [selector as T];
        } else if (selector instanceof NodeList) {
            this.elements = Array.from(selector);
        } else {
            this.elements = selector;
        }
    }

    static create<T extends Element = Element>(selector: string | T | T[] | NodeListOf<T>): AzulQuery<T> {
        const instance = new AzulQuery<T>(selector);
        return new Proxy(instance, {
            get(target, prop, receiver) {
                if (typeof prop === "string" && /^\d+$/.test(prop)) {
                    const index = Number.parseInt(prop);
                    return target.elements[index];
                }

                if (prop in target) {
                    return Reflect.get(target, prop, receiver);
                }
                return undefined;
            },
        });
    }

    *[Symbol.iterator](): Iterator<T> {
        for (const element of this.elements) yield element;
    }

    get(): T[] {
        return this.elements;
    }

    get length(): number {
        return this.elements.length;
    }

    addClass(className: string): AzulQuery<T> {
        for (const el of this.elements) {
            el.classList.add(className);
        }
        return this;
    }

    removeClass(className: string): AzulQuery<T> {
        for (const el of this.elements) {
            el.classList.remove(className);
        }
        return this;
    }

    toggleClass(className: string, force?: boolean): AzulQuery<T> {
        for (const el of this.elements) {
            el.classList.toggle(className, force);
        }
        return this;
    }

    hasClass(className: string): boolean {
        return this.elements[0]?.classList.contains(className) || false;
    }

    text(): string;
    text(content: string): AzulQuery<T>;
    text(content?: string): string | AzulQuery<T> {
        if (content === undefined) {
            return this.elements[0]?.textContent || "";
        }
        for (const el of this.elements) {
            el.textContent = content;
        }
        return this;
    }

    html(): string;
    html(content: string): AzulQuery<T>;
    html(content?: string): string | AzulQuery<T> {
        if (content === undefined) {
            return this.elements[0]?.innerHTML || "";
        }
        for (const el of this.elements) {
            el.innerHTML = content;
        }
        return this;
    }

    attr(name: string): string | undefined;
    attr(name: string, value: string): AzulQuery<T>;
    attr(attrs: Record<string, string>): AzulQuery<T>;
    attr(name: string | Record<string, string>, value?: string): string | undefined | AzulQuery<T> {
        if (typeof name === "string" && value === undefined) {
            return this.elements[0]?.getAttribute(name) || undefined;
        }

        if (typeof name === "string" && value !== undefined) {
            for (const el of this.elements) {
                el.setAttribute(name, value);
            }
        } else if (typeof name === "object") {
            for (const [key, val] of Object.entries(name)) {
                for (const el of this.elements) {
                    el.setAttribute(key, val);
                }
            }
        }
        return this;
    }

    removeAttr(name: string): AzulQuery<T> {
        for (const el of this.elements) {
            el.removeAttribute(name);
        }
        return this;
    }

    prop(name: string): any;
    prop(name: string, value: any): AzulQuery<T>;
    prop(props: Record<string, any>): AzulQuery<T>;
    prop(name: string | Record<string, any>, value?: any): any | AzulQuery<T> {
        if (typeof name === "string" && value === undefined) {
            return this.elements[0] ? (this.elements[0] as any)[name] : undefined;
        }

        if (typeof name === "string" && value !== undefined) {
            for (const el of this.elements) {
                (el as any)[name] = value;
            }
        } else if (typeof name === "object") {
            for (const [key, val] of Object.entries(name)) {
                for (const el of this.elements) {
                    (el as any)[key] = val;
                }
            }
        }
        return this;
    }

    removeProp(name: string): AzulQuery<T> {
        for (const el of this.elements) {
            delete (el as any)[name];
        }
        return this;
    }

    css(prop: string): string;
    css(prop: string, value: string): AzulQuery<T>;
    css(props: Record<string, string>): AzulQuery<T>;
    css(prop: string | Record<string, string>, value?: string): AzulQuery<T> | string {
        if (typeof prop === "string" && value === undefined) {
            const firstEl = this.elements[0];
            if (!firstEl) return "";
            return window.getComputedStyle(firstEl).getPropertyValue(prop);
        }

        if (typeof prop === "string" && value !== undefined) {
            for (const el of this.elements) {
                if (el instanceof HTMLElement || el instanceof SVGElement) {
                    el.style.setProperty(prop, value);
                }
            }
        } else if (typeof prop === "object") {
            for (const [key, val] of Object.entries(prop)) {
                for (const el of this.elements) {
                    if (el instanceof HTMLElement || el instanceof SVGElement) {
                        el.style.setProperty(key, val);
                    }
                }
            }
        }
        return this;
    }

    on<K extends keyof EventMapFor<T>>(event: K, handler: (this: T, ev: EventMapFor<T>[K]) => any): AzulQuery<T>;
    on(event: string, handler: (this: T, ev: Event) => any): AzulQuery<T>;
    on(event: string, handler: ((this: T, ev: Event) => any) | EventListener): AzulQuery<T> {
        for (const el of this.elements) {
            el.addEventListener(event, handler);
        }
        return this;
    }

    show(): AzulQuery<T> {
        for (const el of this.elements) {
            if (el instanceof HTMLElement || el instanceof SVGElement) {
                el.style.display = "";
            }
        }
        return this;
    }

    hide(): AzulQuery<T> {
        for (const el of this.elements) {
            if (el instanceof HTMLElement || el instanceof SVGElement) {
                el.style.display = "none";
            }
        }
        return this;
    }

    toggle(): AzulQuery<T> {
        for (const el of this.elements) {
            if (el instanceof HTMLElement || el instanceof SVGElement) {
                el.style.display = el.style.display === "none" ? "" : "none";
            }
        }
        return this;
    }

    find<U extends Element = Element>(selector: string): AzulQuery<U> {
        const found = new Set<U>();
        for (const el of this.elements) {
            for (const match of el.querySelectorAll<U>(selector)) found.add(match);
        }
        return AzulQuery.create<U>(Array.from(found));
    }

    parent<U extends HTMLElement = HTMLElement>(): AzulQuery<U> {
        const parents = this.elements
            .map((el) => el.parentElement)
            .filter((p) => p !== null) as U[];
        return AzulQuery.create<U>(parents);
    }

    prev<U extends Element = Element>(selector?: string): AzulQuery<U> {
        const prevElements: U[] = [];
        for (const el of this.elements) {
            let prev = el.previousElementSibling as U;
            while (prev) {
                if (!selector || prev.matches(selector)) {
                    if (!prevElements.includes(prev)) {
                        prevElements.push(prev);
                    }
                    break;
                }
                prev = prev.previousElementSibling as U;
            }
        }
        return AzulQuery.create<U>(prevElements);
    }

    next<U extends Element = Element>(selector?: string): AzulQuery<U> {
        const nextElements: U[] = [];
        for (const el of this.elements) {
            let next = el.nextElementSibling as U;
            while (next) {
                if (!selector || next.matches(selector)) {
                    if (!nextElements.includes(next)) {
                        nextElements.push(next);
                    }
                    break;
                }
                next = next.nextElementSibling as U;
            }
        }
        return AzulQuery.create<U>(nextElements);
    }

    append(content: string | Element | AzulQuery): AzulQuery<T> {
        for (const el of this.elements) {
            if (typeof content === "string") {
                el.insertAdjacentHTML("beforeend", content);
            } else if (content instanceof Element) {
                el.appendChild(content.cloneNode(true));
            } else if (content instanceof AzulQuery) {
                for (const child of content) el.appendChild(child.cloneNode(true));
            }
        }
        return this;
    }

    val(): string;
    val(value: string): AzulQuery<T>;
    val(value?: string): string | AzulQuery<T> {
        if (value === undefined) {
            return this.elements[0] ? (this.elements[0] as any).value || "" : "";
        }

        for (const el of this.elements) {
            (el as any).value = value;
        }
        return this;
    }

    remove(): AzulQuery<T> {
        for (const el of this.elements) {
            el.remove();
        }
        return this;
    }

    empty(): AzulQuery<T> {
        for (const el of this.elements) {
            el.innerHTML = "";
        }
        return this;
    }
}

type EventMapFor<T> = 
    T extends HTMLElement ? HTMLElementEventMap :
    T extends SVGElement ? SVGElementEventMap :
    ElementEventMap;

(window as any).$ = $;
export default $;

Help support the channel

JavaScript

// AZUL CODING ---------------------------------------
// Make Your Own Lighter jQuery in TypeScript
// https://youtu.be/QYRASC-i0Y0


function $(selector) {
    return AzulQuery.create(selector);
}

class AzulQuery {
    constructor(selector) {
        if (typeof selector === "string") {
            this.elements = Array.from(document.querySelectorAll(selector));
        } else if (selector instanceof Element) {
            this.elements = [selector];
        } else if (selector instanceof NodeList) {
            this.elements = Array.from(selector);
        } else {
            this.elements = selector;
        }
    }

    static create(selector) {
        const instance = new AzulQuery(selector);
        return new Proxy(instance, {
            get(target, prop, receiver) {
                if (typeof prop === "string" && /^\d+$/.test(prop)) {
                    const index = Number.parseInt(prop);
                    return target.elements[index];
                }

                if (prop in target) {
                    return Reflect.get(target, prop, receiver);
                }
                return undefined;
            }
        })
    }

    *[Symbol.iterator]() {
        for (const element of this.elements) yield element;
    }

    get() {
        return this.elements;
    }

    get length() {
        return this.elements.length;
    }

    addClass(className) {
        for (const el of this.elements) {
            el.classList.add(className);
        }
        return this;
    }

    removeClass(className) {
        for (const el of this.elements) {
            el.classList.remove(className);
        }
        return this;
    }

    toggleClass(className, force) {
        for (const el of this.elements) {
            el.classList.toggle(className, force);
        }
        return this;
    }

    hasClass(className) {
        return this.elements[0]?.classList.contains(className) || false;
    }

    text(content) {
        if (content === undefined) {
            return this.elements[0]?.textContent || "";
        }
        for (const el of this.elements) {
            el.textContent = content;
        }
        return this;
    }

    html(content) {
        if (content === undefined) {
            return this.elements[0]?.innerHTML || "";
        }
        for (const el of this.elements) {
            el.innerHTML = content;
        }
        return this;
    }

    attr(name, value) {
        if (typeof name === "string" && value === undefined) {
            return this.elements[0]?.getAttribute(name) || undefined;
        }

        if (typeof name === "string" && value !== undefined) {
            for (const el of this.elements) {
                el.setAttribute(name, value);
            }
        } else if (typeof name === "object") {
            for (const [key, val] of Object.entries(name)) {
                for (const el of this.elements) {
                    el.setAttribute(key, val);
                }
            }
        }
        return this;
    }

    removeAttr(name) {
        for (const el of this.elements) {
            el.removeAttribute(name);
        }
        return this;
    }

    prop(name, value) {
        if (typeof name === "string" && value === undefined) {
            return this.elements[0] ? this.elements[0][name] : undefined;
        }

        if (typeof name === "string" && value !== undefined) {
            for (const el of this.elements) {
                el[name] = value;
            }
        } else if (typeof name === "object") {
            for (const [key, val] of Object.entries(name)) {
                for (const el of this.elements) {
                    el[key] = val;
                }
            }
        }
        return this;
    }

    removeProp(name) {
        for (const el of this.elements) {
            delete el[name];
        }
        return this;
    }

    css(prop, value) {
        if (typeof prop === "string" && value === undefined) {
            const firstEl = this.elements[0];
            if (!firstEl) return "";
            return window.getComputedStyle(firstEl).getPropertyValue(prop);
        }

        if (typeof prop === "string" && value !== undefined) {
            for (const el of this.elements) {
                if (el instanceof HTMLElement || el instanceof SVGElement) {
                    el.style.setProperty(prop, value);
                }
            }
        } else if (typeof prop === "object") {
            for (const [key, val] of Object.entries(prop)) {
                for (const el of this.elements) {
                    if (el instanceof HTMLElement || el instanceof SVGElement) {
                        el.style.setProperty(key, val);
                    }
                }
            }
        }
        return this
    }

    on(event, handler) {
        for (const el of this.elements) {
            el.addEventListener(event, handler);
        }
        return this;
    }

    show() {
        for (const el of this.elements) {
            if (el instanceof HTMLElement || el instanceof SVGElement) {
                el.style.display = "";
            }
        }
        return this;
    }

    hide() {
        for (const el of this.elements) {
            if (el instanceof HTMLElement || el instanceof SVGElement) {
                el.style.display = "none";
            }
        }
        return this;
    }

    toggle() {
        for (const el of this.elements) {
            if (el instanceof HTMLElement || el instanceof SVGElement) {
                el.style.display = el.style.display === "none" ? "" : "none";
            }
        }
        return this;
    }

    find(selector) {
        const found = new Set();
        for (const el of this.elements) {
            for (const match of el.querySelectorAll(selector)) found.add(match);
        }
        return AzulQuery.create(Array.from(found));
    }

    parent() {
        const parents = this.elements
            .map(el => el.parentElement)
            .filter(p => p !== null);
        return AzulQuery.create(parents);
    }

    prev(selector) {
        const prevElements = [];
        for (const el of this.elements) {
            let prev = el.previousElementSibling;
            while (prev) {
                if (!selector || prev.matches(selector)) {
                    if (!prevElements.includes(prev)) {
                        prevElements.push(prev);
                    }
                    break;
                }
                prev = prev.previousElementSibling;
            }
        }
        return AzulQuery.create(prevElements);
    }

    next(selector) {
        const nextElements = [];
        for (const el of this.elements) {
            let next = el.nextElementSibling;
            while (next) {
                if (!selector || next.matches(selector)) {
                    if (!nextElements.includes(next)) {
                        nextElements.push(next);
                    }
                    break;
                }
                next = next.nextElementSibling;
            }
        }
        return AzulQuery.create(nextElements);
    }

    append(content) {
        for (const el of this.elements) {
            if (typeof content === "string") {
                el.insertAdjacentHTML("beforeend", content);
            } else if (content instanceof Element) {
                el.appendChild(content.cloneNode(true));
            } else if (content instanceof AzulQuery) {
                for (const child of content) el.appendChild(child.cloneNode(true));
            }
        }
        return this;
    }

    val(value) {
        if (value === undefined) {
            return this.elements[0] ? this.elements[0].value || "" : "";
        }

        for (const el of this.elements) {
            el.value = value;
        }
        return this;
    }

    remove() {
        for (const el of this.elements) {
            el.remove();
        }
        return this;
    }

    empty() {
        for (const el of this.elements) {
            el.innerHTML = "";
        }
        return this;
    }
}

window.$ = $;

HTML

<!-- AZUL CODING --------------------------------------- -->
<!-- Make Your Own Lighter jQuery in TypeScript -->
<!-- https://youtu.be/QYRASC-i0Y0 -->


<!DOCTYPE html>
<html>
    <head>
        <title>Azul Coding</title>
        <style>
            body {
                margin: 30px;
                background-color: #03506E;
            }
            * {
                font-family: 'Golos Text', sans-serif;
                font-weight: 500;
                font-size: 18px;
            }
            strong {
                font-weight: 700;
            }
            .square {
                display: flex;
                align-items: center;
                justify-content: center;
                width: 100px;
                height: 100px;
            }
            .white {
                background: white;
            }
            .blue {
                background: #49C8FC;
            }
        </style>
        <script src="azulquery.js"></script>
    </head>
    <body>
        <div id="container" style="display:flex; gap:25px;">
            <div class="white square"></div>
            <div class="blue square"></div>
        </div>
        <br>
        <button id="btn">Click me</button>
        <script>
            /* // Try these examples:

            $("#container").css({
                "gap": "50px",
                "transform": "rotate(45deg)"
            });

            $(".blue.square").removeClass("blue").addClass("white");

            $(".blue.square").append("<strong>Text</strong>");

            $("#btn").on("click", () => {
                $(".blue.square").empty();
            });

            */
        </script>
    </body>
</html>