Relying on jQuery for DOM selection and modification still doesn’t sit right with me – It makes my feel dirty and lazy. But was incredibly convenient during the initial build of the gallery app since it allowed me to quickly “sketch” out functionality.
I’m using this post to document the changes required as I refactor jQuery out of my application let some decent DOM code in. I’m adding new utility functions to my DOMUtils file.
Quick lessons learned – I didn’t prefix my jQuery variables in any special way. Looking back I need to do this next time. Something simple like “_$varName” would have been handy. However, it’s trivial to search the project for $’s to find library calls.
Element Selection
Generally, I’m selecting elements with their ID, so this is just a simple switch to document.getElementByID(). I needed to go back up and update my RxJS observers, because these were listening to jqEl[0] to get the HTML element rather than the jQuery object. All of my JavaScript animations are handled by GSAP which takes either HTML elements or jQuery objects as arguments.
More complicated sections can be handled by the element.querySelector or element.querySelectorAll methods. Note that the latter returns a node list (array) of all matched elements. If know there is only one, you just need the first element.
So this:
_mainSearchInputEl = $('.grid__header-search input');
Becomes:
_mainSearchInputEl = document.querySelector('.grid__header-search > input');
Be aware: there are differences in the older get* functions and the new query* functions – both in what kind of node list they return and performance.
Viewport metrics
Whenever the window is resized or scrolled, I’m firing off an event (to a command) with the new dimensions. This is used to realign the header and footer to the new size.
Currently, I’m using this:
_currentViewPortSize = {width: $(window).width(), height: $(window).height()};
But this is just as simple:
_currentViewPortSize = {width: window.innerWidth, height: window.innerHeight};
Because of the structured needed to create the drawer for the mobile view, I’m measuring scrollTop and scrollLeft on the div that contains the gallery grid, not the body element:
var left = $(_mainScrollEl).scrollLeft(),
top = $(_mainScrollEl).scrollTop();
Becomes:
var left = _mainScrollEl.scrollLeft,
top = _mainScrollEl.scrollTop;
Element Sizes
Rather than using $el.height() you can use el.clientHeight. el.offsetHeight returns he height including padding, borders and scroll bars.
Setting HTML
From:
$el.html('<p>some html</p>');
to:
htmlEl.innerHTML = '<p>some html</p>';
Adding and removing classes
http://youmightnotneedjquery.com/ provides functions for adding and removing classes. I added this block to my DOMUtils functions:
hasClass: function(el, className) {
if (el.classList) {
el.classList.contains(className);
} else {
new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className);
}
},
addClass: function(el, className) {
if (el.classList) {
el.classList.add(className);
} else {
el.className += ' ' + className;
}
},
removeClass: function(el, className) {
if (el.classList) {
el.classList.remove(className);
} else {
el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
},
toggleClass: function(el, className) {
if(this.hasClass(el, className)) {
this.removeClass(el, className);
} else {
this.addClass(el, className);
}
}
Adding and Removing DOM Nodes
All of the nodes that I’m adding to the DOM are the results of an HTML template from Underscore.
Starting with this …
var tagel = $(_tagTemplate({tag: tag}));
parentEl.append(tagel);
The first problem is that the result from the template is an HTML string, not an element. The string must be converted to an HTML element before it can be appended to the DOM.
The DOMParser method looks like a great solution, but in testing, I found that it has issues with IE9. I found a nice little technique on a StackOverFlow tread that I turned in to a function:
function HTMLStrToNode(str) {
var temp = document.createElement('div');
temp.innerHTML = str;
return temp.firstChild;
}
And this now works:
var taghtml = _tagTemplate({tag: tag}),
tagnode = HTMLStrToNode(taghtml);
parentEl.appendChild(tagnode);
Comparatively, node removal is straightforward
parentEl.removeChild(el);
Wrapping an element in another
Pretty easy in jQuery:
$(el).wrapInner('<div class="floatimage__wrapper" />');
To do it without jQuery, we leverage the HTMLStrToNode function above and come up with:
wrapElement: function(wrapperStr, el) {
var wrapperEl = DOMUtils.HTMLStrToNode(wrapperStr),
elParent = el.parentNode;
wrapperEl.appendChild(el);
elParent.appendChild(wrapperEl);
return wrapperEl;
}
Usage is easy:
DOMUtils.wrapElement('<div class="floatimage__wrapper" />', el);
Finding the closest match
jquery’s closest() function comes in really handy. I found a good native solution on this StackOverFlow thread that includes vendor prefixes since the matches() function is still relatively new.