Search code examples
javascriptevent-handlingdom-eventsevent-listenerdynamic-html

Custom Javascript library: Add event listener to dynamically added html


I'm trying to build a Javascript library like jQuery just to learn Javascript more. So far, I've developed this:

window.jsLib = function (selector) {
    var about = {
        Version: 0.1
    };
    if (selector) {
        if (window === this) {
            return new jsLib(selector);
        }
        if (typeof selector === 'string') {
            var nodes = document.querySelectorAll(selector);
            for (var i = 0; i < nodes.length; i++) {
                this[i] = nodes[i];
            }
            this.length = nodes.length;
        } else if (typeof selector === 'object') {
            this[0] = selector;
            this.length = 1;
        }
        return this;
    } else {
        return about;
    }
};

And for methods, I've developed some like:

jsLib.fn = jsLib.prototype = {
    css: function (key, value) {
        if (value !== undefined) {
            for (var i = 0; i < this.length; i++) {
                this[i].style[key] = value;
            }
            return this;
        } else {
            for (var i = 0; i < this.length; i++) {
                return this[i].style[key];
            }
        }
    },
    html: function (value) {
        if (value !== undefined) {
            for (var i = 0; i < this.length; i++) {
                this[i].innerHTML = value;
            }
            return this;
        } else {
            for (var i = 0; i < this.length; i++) {
                return this[i].innerHTML;
            }
        }
    },
    on: function (type, callback) {
        console.log(window.event);
        for (var i = 0; i < this.length; i++) {
            this[i].addEventListener(type, callback, false);
        }
        return this;
    },
    trigger: function (type) {
        var event = new Event(type);
        for (var i = 0; i < this.length; i++) {
            this[i].dispatchEvent(event);
        }
        return this;
    },
    append: function(value) {
        var old = this.html();
        this.html(old + '' + value);
        return this;
    }
};

You may have noted that I've defined a method on like jQuery.

Whenever I'm calling like jsLib('div#foo').on('click', someFunc);, it is working fine.

But, suppose I have appended some html like jsLib('body').append('<a id="#bar" href="#">Click</a>');

And then I want to provide an API to add event listener to #bar like jsLib('body').on('click', '#bar', someOtherFunc);.

But I'm not sure how to implement this listener.

Kindly help.


Solution

  • From your comments I suppose you request a live implementation?

    If that is the case, i wold suggest you to add a data method to your object, remember all events to be registered and register them from append method, when content is appended to the current element.

    I extended your library with the .data and .live methods and queued an event registration for the next span to be added in body. See the modified code snippet and check out the console to validate.

    window.jsLib = function (selector) {
        var about = {
            Version: 0.1
        };
        if (selector) {
            if (window === this) {
                return new jsLib(selector);
            }
            if (typeof selector === 'string') {
                var nodes = document.querySelectorAll(selector);
                for (var i = 0; i < nodes.length; i++) {
                    this[i] = nodes[i];
                }
                this.length = nodes.length;
                this._selector = selector;
            } else if (typeof selector === 'object') {
                this[0] = selector;
                this.length = 1;
            }
            return this;
        } else {
            return about;
        }
    };
    
    jsLib.fn = jsLib.prototype = {
        css: function (key, value) {
            if (value !== undefined) {
                for (var i = 0; i < this.length; i++) {
                    this[i].style[key] = value;
                }
                return this;
            } else {
                for (var i = 0; i < this.length; i++) {
                    return this[i].style[key];
                }
            }
        },
        html: function (value) {
            if (value !== undefined) {
                for (var i = 0; i < this.length; i++) {
                    this[i].innerHTML = value;
                }
                return this;
            } else {
                for (var i = 0; i < this.length; i++) {
                    return this[i].innerHTML;
                }
            }
        },
        on: function (type, callback) {
            for (var i = 0; i < this.length; i++) {
                this[i].addEventListener(type, callback, false);
            }
            return this;
        },
        trigger: function (type) {
            var event = new Event(type);
            for (var i = 0; i < this.length; i++) {
                this[i].dispatchEvent(event);
            }
            return this;
        },
        append: function(value) {
            var old = this.html(),
                pendingEvents = this.data('jsLib_Future_Events') || [],
                registered = {};
            
            this.html(old + '' + value);
            
            // Attach pending events to newly added childs (if any match found)
            pendingEvents.forEach(function (evConf, i) {
            	[].slice.call(jsLib(this._selector + ' ' + evConf.selector), 0).forEach(function (el) {
                	jsLib(el).on(evConf.type, evConf.callback);
                    registered[i] = true;
                });
            }.bind(this));
            
            // Clear list of pending requests of any registered events
            this.data('sLib_Future_Events', pendingEvents.filter(function (evConf, i) { return !!registered[i]; }));
            
            return this;
        },
        _data: {},
        data: function (key, value) {
            if (arguments.length > 1) this._data[key] = arguments[1];  	// Setter
            return key ? this._data[key] : this._data;					// Getter of key or all
        },
        live: function (type, selector, callback) {
            this.data('jsLib_Future_Events', (this.data('jsLib_Future_Events') || []).concat({type: type, selector: selector, callback: callback}));
            return this;
        }
    };
    
    jsLib('body').live('click', 'span', function () { console.debug('event triggered on appendend content after live registration of event handle'); });
    
    jsLib('body').append('<br><span>dynamic content</span>');
    <div>existing content</div>

    Considerations:

    • you will have to make a similar implementation for html method
    • if you want to make a real live clone you will have to keep pre-registered event definitions and register any new elements matching the selector (at this moment once an element matches the selector and the event is registered the event definition is removed in append - to make it universal you will have to mark your bound elements, use data for this)