Have you ever hit a strange issue while doing frontend development and said WTF?
I thought it might be interesting to share these WTF moments and maybe learn something from each other and have a little laugh.
Here are some of the WTFs I've encountered in my 10+ years of frontend development.
document.querySelectorAll
does not return an actual array
document.querySelectorAll
does not return an actual array, but a NodeList
instance that only partially acts like an array: typeof document.querySelectorAll('a').map === 'undefined'
NodeList
has #forEach
method, but doesn't have #map
method. It's a mess. :)
Getting CSS styles of a DOM element in JS
There's el.style
, but that only works for inline styles in HTML: <div style="...">
. Then there's the global getComputedStyle(Element, pseudo)
which almost works, but not quite (depending on what you need):
body > .container {
background: none;
}
getComputedStyle(containerEl).background
// => "rgba(0, 0, 0, 0) none repeat scroll 0% 0% / auto padding-box border-box"
This happens because background
is a shorthand property for many other properties (such as backgroundColor
, etc.)
What if I want to get the exact CSS style from the CSS? I personally have no idea how to do that.
Super complicated to get URL params
window.location
is not a string. It's not a URL
either. It's...Location
. Let's see how we could get the URL params.
An instance of URL
class has: searchParams
method. We can create a URL
instance from Location
:
// imagine window.location is https://example.com/events/new?premium=true#buy
const url = new URL(window.location)
const whyIsThisCalledSearchParams = url.searchParams
whyIsThisCalledSearchParams.toString() // => "premium=true"
IT WORKS!!!! π
Not so fast, Mr. Gordon.
JSON.stringify(whyIsThisCalledSearchParams) // => "{}"
// WAT
Ok, well, there's #entries()
method. I guess that's like Object.entries()
, right? Let's use that:
Object.entries({ name: 'Viktor' })
// => [["name", "Viktor"]]
whyIsThisCalledSearchParams.entries()
// => Iterator {}
// WAT it's empty
Object.entries(new URL(location).searchParams)
// => []
// WAT it's empty
whyIsThisCalledSearchParams.get('premium')
// => "true"
// of course, true is a string here, but that's ok
The only way to get the url query parameters:
for (const [key, value] of whyIsThisCalledSearchParams) {}
Or you can do the old-fashioned way:
// because first character is "?" because why not :)
const query = window.location.search.substring(1)
query.split('&').....each(queryPair => queryPair.split('='))
And then you hope you won't need arrays in there...I also forgot to mention parsing any special characters...sorry! :)
Also, searchParams
is called query in the official RFCs. To me, "query" or "query params" make more sense than "search params".
In JavaScript you can't get all form field values through standard API
formEl.formValues
or similar doesn't exist. You have to do: formEl.querySelectorAll('input, select').map(el => el.value)
. But that won't work for several reasons. First, you can't use .map
, but we'll get to that in a moment.
Second, you can have input fields outside of the formEl
:
<input type="text" form="editform" name="id">
<form action="datamanager" id="editform"></form>
This works.
So you need to do all this extra mumbo jumbo similar to this:
formEl.querySelectorAll('input, select').forEach(el => {
if (el.type === 'checkbox' || el.type === 'radio') {
el.checked
} else {
el.value
}
}
if (formEl.id) {
document.querySelectorAll('input[form="' + formEl.id + '"]').forEach(...)
}
I lied. You can get the form values using the FormData
class and its #entries()
method. Maybe.
It might work, but it might fail silently (which is the worst possible way to fail, when you don't know it failed) if you decide to add an input element outside of the form tag programmatically:
const inputEl = document.createElement('input')
inputEl.name = 'firstName'
inputEl.form = 'customerForm'
inputEl.form
// => null
// WAT it just fails silently
inputEl.setAttribute('form', 'customerForm')
// => works as expected
dataset
vs data-*
vs dataset.kebabCase
vs data-kebab-case
In HTML you write: data-target-id="someid"
, while in JavaScript, to get that same attribute value, you say: el.dataset.targetId
. WAT.
CSS usually (on a good day) prefers class names with kebab case, but JS prefers pascalCase:
.container {
background-color: #ffffff;
`
containerEl.style.backgroundColor = '#ffffff'
Also check these great examples:
div {
display: inline-block; /* kebab case, as the rest of the CSS */
white-space: nowrap; /* nowrap? WAT no-wrap? WAT */
}
How about this:
button {
white-space: nowrap;
}
div {
white-space: no-pre; /* What the... π€― */
}
Getting element's X/Y position on the page from page top
This works:
el.getBoundingClientRect().top + window.pageYOffset
Or does it?
Well, sort-of. It works if the element is not in a inside some container that's position fixed. So you first have to find check all of the element's ancestors to see if any one of those is position fixed and only then you can calculate this correctly. Please correct me if I'm wrong here. I'd love to be wrong here.
Does this look familiar: z-index: 9999999999999999
?
You add z-index: 9999999999999999
to an element, but it's still not above all the other elements. Y U NO?
It's because z-index
is "stacking". So it works kind of like this:
<div id="header" style="z-index: 2"></div>
<div id="main-content" style="z-index: 1">
<div id="dis-one" style="z-index: 999"></div>
</div>
The #dis-one
element has an overall z-index of 1.999, while #header
has an overall z-index of 2. It stacks.
Well, yeah...maybe. But not always.
First of all, you need to add at least position: relative
for z-index
to have any effect. Secondly, new stacking contexts are also created when you apply a transform
. So, sometimes to show .container:before
behind .container
you need to actually add another HTML element (let's call it) .wrapper
around .container
.
Check out this answer:
https://stackoverflow.com/a/20852489/336806
<input type="submit">
and <button>
There's a special input type called "submit", which kind-of renders like a button. It also has a value
attribute, but it's usually not submitted to the backend. Remember our code for getting form values? Well, it does submit this value. That's another edge case you need to take care of.
It's also far more limiting than an actual <button>
Speaking of <button>
:
The <button>
Here's a part of a website's HTML:
<button>Submit</button>
...but it's not working. π€ The form doesn't get submitted when you click on this button.
You know that button
's default type is type="submit"
. And you see no JavaScript code that could prevent the submission.
Then you realize, you put it outside of the <form>
:
<form>
<!-- form fields -->
</form>
<button>Submit</button>
Well, if the <button>
tag is placed outside of <form>
tag, it behaves pretty much like <button type="button">
.
It's the same HTML syntax that acts differently whether it's inside of <form>
tag or not.
Similary, this is also a disaster in the making:
<form action="/events">
<form action="/events/5">
<button>Submit</button>
</form>
<button>Submit</button>
</form>
Similarly:
<label>
<input type="checkbox" name="terms">
I accept the <a href="/terms">Terms & Conditions</a>
</label>
Or (these examples are not for faint hearted):
<a href="/events/5">
<div>Best Ice-cream in town</div>
<a href="/events/5/more-info">
More info about Ice-cream party
</a>
<button>Submit</button>
</a>
Let's talk some more about JavaScript
typeof [] === 'object' // WAT
typeof null === 'object' // WAT
typeof undefined === 'undefined' // WAT
const add = (a = 1, b = 2) => {
if (a && b) {
return a + b
} else {
return 0
}
}
add(undefined, undefined) === 3 // hmmm...ok
add(2, null) === 0 // WAT
add(3, undefined) === 5 // WAT
I'll wrap up here. Please do share your experiences working on frontend. :) Thanks!
Top comments (9)
Thank me later :)
Idiomatic JavaScript, they say don't modify objects that you don't own, so I feel this is a bit of a sledge hammer really. Besides Array.from(someNodeList) does just fine.
What the...? :D I'm only guessing what that does, but if we weren't talking about this, I don't think I'd ever guess.
...Thank you! :)
For the form elements one, can you not use
formElement.elements
? That's how it used to work back in the IE 5 days, and to the best of my knowledge, it worked back when Netscape Navigator was still a thing.As for some of the WTF's that I've encountered:
100vh
doesn't work as expected only in Safari on iOS because the browsers footer/action bar is included as par of the available height ofvh
, meaning the bar covers contentleftpad
problem that broke builds everywhere?!)\p{L}
to select any letter (including those from non-ascii, such as Russian Cyrillic or Chinese). Mileage varies greatly between what selectors are supported, and polyfills here are basically a must. Unicode support in JS has always felt a little like a bit of an afterthough in places.Math.max()
(with no arguments) returns-Infinity
andMath.min()
returnsInfinity
, soMath.min() > Math.max()
, rather than doing something sane like returnNaN
or erroring out when there's no arguments.Hmm, I remember how I wasted 2 hours on trying to copy children nodes from one element to another querySelectorAll's children with forEach and for iteration, both didn't work either. The problem was that I initialized variable and assigned selectorAll to it, tried to manipulate children which resulted in error logs in console. However, I came up with another solution: directly typing "querySelectorAll(ParentElement).childNodes" - which didn't cause any bugs. But still, WTF?
I'm currently doing web development and it's a big WTF, period. That makes it both interesting and maddening at the same time. I run into weird "magic" things on a daily basis. It's often hard to figure out what's going on.. "oooh magic! NOOOOOO!!!!!"
A definite WTF the first time you come across it, is DOM's live collection, because it's so rarely seen elsewhere. This idea that if you get the collection of elements from, say, getElementsByClassName(), and loop over an index of the elements in a forward direction, removing each one from the DOM as you go, because the collection updates itself after each removal, you end up only removing every other element.
Behold!
It's a different color depending on your device.
URLSearchParams Can definitely helps the query string problem
developer.mozilla.org/en-US/docs/W...