Re-implementing jQuery methods in the HTMLElement prototype
Tags: beginners, jquery
Published: 2018-12-06 12:22:51

---
title: Re-implementing jQuery methods in the HTMLElement prototype
published: true
description: I thought it'd be fun and educational for n00b programmers.
cover_image: https://cdn-images-1.medium.com/max/1600/1*_QxjmFagsMhkiOKAMoLFlQ.png
tags: beginner, jquery 
---

It is almost 2019 and people have generally come to the agreement that jQuery is deprecated. I for one have a very different perspective on that but that is for some other time. So why do people still use it? Old habits? Convenience? It turns out that the majority of people that use jQuery only use it for a select few methods.

I thought it'd be fun and educational for n00b programmers to re-implement those jQuery methods onto the HTMLElement prototype.

## QuerySelector

Lets first define a shortcut for the document querySelectors. _$_ is for a single element and _$$_ is for all matches. We want to be able to provide a second _context_ argument like jQuery that defaults to the whole document. I am assuming ES6+ where default values in function declarations are supported.


```js
/**
 * $ for document.querySelector
 * $$ for document.querySelectorall
 * with optional context just like jQuery (defaults to document)
 */
window.$ = (query, ctx = document) => ctx.querySelector(query)
window.$$ = (query, ctx = document) => ctx.querySelectorAll(query)
``` 

```js
$('h2') // will return single _&lt;h2&gt;_ element
$$('h2') // will return array with all _&lt;h2&gt;_ elements
``` 

Using the context argument we can select all &lt;p&gt; elements within another element (&lt;article&gt;&lt;/article&gt;) 

```js 
$$('p', $('article'))
```

## NodeList iteration
You have to admit, the _jQuery.prototype.each_ is pretty neat too. Add a property _each_ to the NodeList prototype and set the value to a function that casts the _NodeList_ to an _Array_ and then iterates it using the callback function provided.

```js
/**
 * This allows you to "forEach" a NodeList returned by querySelectorAll or $$
 * similar to jQuery.prototype.each
 * use: $$('li').each(callback)
 */
Object.defineProperty(NodeList.prototype, 'each', {
    value: function (fn) {
        return Array.from(this).forEach((node, index) => fn(node, index))
    }
})
``` 

## Attributes
Another common jQuery method is _attr_. We can use _attr_ to read and write attributes of elements in the DOM using a single method.  We will add one tiny feature to our method. It returns all attributes when no arguments are provided.
With _attr_ also comes _removeAttr_ (remove attribute) and _has_ to determine an argument exists.

```js 
/** 
 * single method to get/set/list attributes of HTMLElement. 
 * get argument id:     $('div').attr('id')
 * set argument id:     $('div').attr('id', 'post123')
 * list all arguments:  $('div').attr()  // Fuck yeah
 */
HTMLElement.prototype.attr = function (key, value) {
    if (!value) {
        if (!key) {
            return this.attributes
        }
        return this.getAttribute(key)
    }
    this.setAttribute(key, value)
    return this
}

/**
 * remove attribute from HTMLElement by key
 */
HTMLElement.prototype.removeAttr = function (key) {
    this.removeAttribute(key)
    return this
}

/**
 * check whether a DOM node has a certain attribute.
 */
HTMLElement.prototype.has = function(attribute) {
    return this.hasAttribute(attribute)
}
``` 

## innerText and innerHTML
```js
/** 
 * single function to get and set innerHTML
 * get:  $('body').html()
 * set:  $('body').html('<h1>hi!</h1>')
 */
HTMLElement.prototype.html = function (string) {
    if (!string)
        return this.innerHTML
    this.innerHTML = string
    return this
}

/** 
 * single function to get and set innerText
 * get:  $('body').text()
 * set:  $('body').text('hi!')
 */
HTMLElement.prototype.text = function (string) {
    if (!string)
        return this.textContent
    this.innerText = string
    return this
}
``` 

## Append and prepend
The following _append_ method allows you to insert a HTML element at the end of the specified target element. The _prepend_ method will insert it just before.

```js 
/**
 * append HTMLElement to another HTMLElement
 * like jQuery append()
 */
HTMLElement.prototype.append = function (child) {
    if (child instanceof HTMLElement) {
        this.appendChild(child)
        return this
    }
    this.append(child)
    return this
}

/**
 * prepend HTMLElement to another HTMLElement
 * like jQuery prepend()
 */
HTMLElement.prototype.prepend = function (sibling) {
    if (sibling instanceof HTMLElement) {
        this.parentNode.insertBefore(sibling, this)
        return this
    }
    this.parentNode.insertBefore(sibling, this)
    return this
}
``` 

## Removing elements 

Removing an element in JavaScript happens by accessing its parent node to call removeChild(). Yeah weird I know.

```js 
HTMLElement.prototype.remove = function() {
    this.parentNode.removeChild(this)
}
```
As you probably know, you *can not use* arrow functions in jQuery. However

```js
$('#foo').remove()
// or
$$('div').each(element => element.remove()) 
```

## Parent
Get the parent of a node.

```js
/** 
 * get a HTMLElement's parent node
 * use: $('h1').parent()
 */
HTMLElement.prototype.parent = function () {
    return this.parentNode
}
``` 

## Events 

Modern JavaScript libraries implement _on_, _off_ and _emit_ to get, set and dispatch events.

```js 
/**
 * add event listener to HTMLElement
 * $(document).on('click', event => ...)
 */
HTMLElement.prototype.on = function (event, callback, options) {
    this.addEventListener(event, callback, options)
    return this
}

/**
 * remove event listener from HTMLElement
 * $(document).off('click', callback)
 */
HTMLElement.prototype.off = function (event, callback, options) {
    this.removeEventListener(event, callback, options)
    return this
}

/**
 * dispatch an event on HTMLElement without needing to instanciate an Event object.
 * $(document).emit('change', { foo: 'bar' })
 */
HTMLElement.prototype.emit = function (event, args = null) {
    this.dispatchEvent(event, new CustomEvent(event, {detail: args}))
    return this
}
``` 

## DataSet
And last but not least a nice method to access _data_ attributes. 

```js 
/**
 * single method to get/set/list HTMLElement dataset values
 * get:  $('div').data('color')     assuming <div data-color="..."></div>
 * set:  $('div').data('color', '#0099ff')
 */
HTMLElement.prototype.data = function (key, value) {
    if (!value) {
        if (!key) {
            return this.dataset
        }
        return this.dataset[key]
    }
    this.dataset[key] = value
    return this
}
``` 

## Define
This is unrelated to jQuery but still a nice shortcut.

```js 
/**
 * Convenient shortcut 
 * use:   define('property', { ...descriptor })
 */
Object.defineProperty(window, 'define', {
    value: (property, ...meta) => meta.length == 2 ? Object.defineProperty(meta[0], property, meta[1]) : Object.defineProperty(window, property, meta[0]),
    writable: false,
    enumerable: true
})
```

Now we can do for instance this:

```js
/** 
 * now | single statement accessor that returns current time
 * @returns {number} 
 */
define('now', {
    get: Date.now
})
``` 

The identifier _now_ will return the current time. You don't have to call it as a function, simply accessing it will do.

```js 
setInterval(() => console.log(now), 10)
/*
1543930325785
1543930325795
1543930325805
1543930325815
1543930325825
1543930325835
*/
```

## Gist 

For your convenience, a gist with all that is above.
<a href="https://gist.github.com/jochemstoel/856d5b2735c53559372eb7b32c44e9a6" target="_blank">https://gist.github.com/jochemstoel/856d5b2735c53559372eb7b32c44e9a6</a>