Reputation: 1247
I'm using TypeScript for the browser. I find myself writing a lot of code like this:
const button = document.getElementById(id);
if (!(button instanceof HTMLButtonElement)) {
throw new Error("TODO -- insert better error message here");
}
button.disabled = false;
The throw
is required. getElementById() returns type HTMLElement | null
, which does not support the disabled property. After the throw
the type correctly changes to HTMLButtonElement
. I could have done this with a type assertion, but this code also includes a runtime check.
Is there any way to move this into a function? Something like:
const button = verify(document.getElementById(id), HTMLButtonElement);
button.disabled = false;
or
const button = verify<HTMLButtonElement>(document.getElementById(id));
button.disabled = false;
But not
const button = verify<HTMLButtonElement>(document.getElementById(id), HTMLButtonElement);
button.disabled = false;
because then I'm typing the same word twice and I could easily make a mistake.
In Java or C# I'd say (HTMLButtonElement)document.getElementById(id)
instead of verify()
. In C++ I'd say dynamic_cast< HTMLButtonElement & >(document.getElementById(id))
. Again, I'm trying to do the runtime check and satisfy compiler. And I want to avoid as much typing as possible.
Upvotes: 0
Views: 627
Reputation: 1247
This is based on the accepted answer.
/**
* This is a wrapper around document.getElementById().
* This ensures that we find the element and that it has the right type or it throws an exception.
* Note that the return type of the function matches the requested type.
* @param id Look for an element with this id.
* @param ty This is the type we are expecting. E.g. HtmlButtonElement
*/
function getById<T extends Element>(id : string, ty: {new(): T}): T {
const found = document.getElementById(id);
if (!found) {
throw new Error("Could not find element with id " + id + ". Expected type: " + ty.name);
}
if (found instanceof ty) {
return found;
} else {
throw new Error("Element with id " + id + " has type " + found.constructor.name + ". Expected type: " + ty.name);
}
}
Upvotes: 0
Reputation: 10218
This is a little harder then I expected, but it works:
function verify<T extends Element>(element: Element | null, ty: {new(): T}): T {
if (element === null) {
throw new Error("TODO: element is null");
}
if (element instanceof ty) {
return element;
} else {
throw new Error("TODO: wrong type");
}
}
Here's a snippet to check the correctness:
function verify(element, ty) {
if (element === null) {
throw new Error("TODO: element is null");
}
if (element instanceof ty) {
return element;
} else {
throw new Error("TODO: wrong type");
}
}
function enableButton(id) {
const button = verify(document.getElementById(id), HTMLButtonElement);
button.disabled = false;
}
enableButton("button");
try {
enableButton("not_exist");
} catch (e) {
console.error(e);
}
try {
enableButton("not_a_button");
} catch (e) {
console.error(e);
}
button:disabled {
color: grey;
}
<div id="not_a_button"></div>
<button id="button" disabled>Enabled</button>
<button id="disabled_button" disabled>Disabled</button>
Upvotes: 1
Reputation: 160
export function isHTMLButtonElement(value: any): value is HTMLButtonElement
{
return value instanceof HTMLButtonElement;
}
Something like this, the return type specifies that it is a HTMLButtonElement. Code could be nicer but you get the jist :)
Or if you know for sure what you're querying for will be HTMLButtonElement you could just do.
const button = document.getElementById(id) as HTMLButtonElement;
Upvotes: 0