dev-resources.site
for different kinds of informations.
Webdev WTF
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!
Featured ones: