Typescript type of MaterialTextfield - javascript

I am creating my first react typescript app.
I found a some sample in pure javascript and wan't to write sth similar in typescript.
In firebase javascript sample we can see
addCommentForm.onsubmit = function(e) {
e.preventDefault();
createNewComment(postId, firebase.auth().currentUser.displayName, uid, commentInput.value);
commentInput.value = '';
commentInput.parentElement.MaterialTextfield.boundUpdateClassesHandler();
};
I would like to do sth similar in typescript, I would like to create a function like below
function resetMaterialTextfield(element: HTMLInputElement) {
element.value = '';
element.parentNode.MaterialTextfield.boundUpdateClassesHandler();
}
My question is:
How should I declare element, with what type, so that it had parentNode, parentElement attribute, and that parent had MaterialTextfield on which I could call boundUpdateClassesHandler()?
From which library/framework does MaterialTextfield come from? What type is it? Where can I find documentation or some tutorial for this class and library?

Use type assertion.
(element.parentNode as TypeYouWant).MaterialTextfield.boundUpdateClassesHandler();

Related

Use parameterised functions defined in ES6 module directly in html

Functions defined inside an ES6 module embedded in an HTML script are not available to that script. Thus if you have a statement such as:
<button onclick="doSomething();">Do something</button>
in your HTML and your doSomething() function lives inside an ES6 module embedded in the HTML script, you will get a "doSomething() is undefined" error when you run the script.
Use functions defined in ES6 module directly in html suggests a great solution to the immediate problem, recommending that you "bind" your function to the button by amending your HTML thus:
<button id="dosomethingbutton">Do something</button>
and using the module itself to create a linkage thus:
document.getElementById('dosomethingbutton').addEventListener('click', doSomething);
This works fine, but what if your original button was a bit more sophisticated and was parameterised? For example:
<button onclick="doSomething('withThisString');">Do Something with String</button>
The most that the "binding" can provide seems to be limited to the circumstances relating to the event - I can find no way of associating it with data. I'm completely stuck trying to find a solution to this one and assistance would be much appreciated.
I'd like to add that, while this problem might seem a bit obscure, I think it will be of interest to anyone migrating to Firebase 9. Amongst other changes, migration requires you to move your javascript code into ES6 modules (where the namespace is not directly available to the HTML DOM) and so it's likely that the simplest HTML will immediately hit these issues. Advice would be most welcome.
This works fine, but what if your original button was a bit more sophisticated and was parameterised?
There are a couple of solutions to that:
A data-* attribute:
<button id="the-button" data-string="withThisString">Do Something with String</button>
document.getElementById("the-button").addEventListener("click", function() {
doSomething(this.getAttribute("data-string"));
});
(More on this below.)
or
Binding the string when you bind the event
<button id="the-button">Do Something with String</button>
document.getElementById("the-button").addEventListener("click", () => {
doSomething("withThisString");
});
There are lots of variations on the above, and if you use doSomething with multiple buttons with different strings you can do #1 with a class and a loop rather than with an id, but that's the general idea.
Re the data-* attribute thing: If you wanted to, you could make this process entirely HTML-driven via data-* attributes and a single function that hooks things up. For instance, say you had these buttons:
<button data-click="doThisx#module1">Do This</button>
<button data-click="doThat#module2">Do That</button>
<button data-click="doTheOther#module3">Do The Other</button>
You could have a single reusable function to hook those up:
class EventSetupError extends Error {
constructor(element, msg) {
if (typeof element === "string") {
[element, msg] = [msg, element];
}
super(msg);
this.element = element;
}
}
export async function setupModuleEventHandlers(eventName) {
try {
const attrName = `data-${eventName}`;
const elements = [...document.querySelectorAll(`[${attrName}]`)];
await Promise.all(elements.map(async element => {
const attrValue = element.getAttribute(`data-${eventName}`);
const [fname, modname] = attrValue ? attrValue.split("#", 2) : [];
if (!fname || !modname) {
throw new EventSetupError(
element,
`Invalid '${attrName}' attribute "${attrValue}"`
);
}
// It's fine if we do import() more than once for the same module,
// the module loader will return the same module
const module = await import(`./${modname}.js`);
const fn = module[fname];
if (typeof fn !== "function") {
throw new EventSetupError(
element,
`Invalid '${attrName}': no '${fname}' on module '${modname}' or it isn't a function`
);
}
element.addEventListener(eventName, fn);
}));
} catch (error) {
console.error(error.message, error.element);
}
}
Using it to find and hook up click handlers:
import { setupModuleEventHandlers } from "./data-attr-event-setup.js";
setupModuleEventHandlers("click")
.catch(error => {
console.error(error.message, error.element);
});
It's one-time plumbing but gives you the same attribute-based experience in the HTML (the event handlers could still get parameter information from another data-* attribute, or you could bake that into your setup function). (That example relies on dynamic import, but that's supported by recent versions of all major browsers and, to varying degrees, bundlers.
There are a couple of dozen ways to spin that and I'm not promoting it, just giving an example of the kind of thing you can readily do if you want.
But really, this is where libraries like React, Vue, Ember, Angular, Lit, etc. come into play.
Although T.J Crowder has already answered this question I thought I might add a few points that are difficult to squeeze in as comments.
Once I got further into my Firebase V9 conversion I began to find that some of the consequences of the "module namespacing" issue were quite profound. The example cited in my initial question is easily dealt with, as above, but I found that I also needed to work out what to do about "dynamic" HTML responding to variable circumstances derived from a database. In this case, where my javascript would originally have created a string containing a block of HTML such as:
realDiv = `
<div>
<button onclick = "function fn1 (param1a, param1b,...);">button1</button>
<button onclick = "function fn2 (param2a, param2b,...);">button2</button>
etc
</div>
`
and then thrown this into a "real" realdiv defined in the HTML skeleton of the application with a
document.getElementById("realdiv") = realDiv;
Now, for the reasons described above, once the javascript is in a module, this arrangement no longer works.
The pattern I learnt to adopt (thanks, once again to T.J Crowder) went along the following lines:
realDiv = `
<div>
<button id = "button1" data-param1a="param1a" data-param1b="param1b";">button1</button>
<button id = "button2" data-param2a="param2a" data-param2b="param2b";">button2</button>
etc
</div>
`
I would then throw the generated code into my HTML skeleton as before with
document.getElementById("realdiv") = readlDiv;
and now that you've got the code embedded into the DOM (and assuming that I've kept a count of the number of buttons you've generated) I would create bindings for them with a final bit of javascript like so:
for (let i = 0; i>buttonCount; i++) {
document.getElementById('button' + i).onclick = function() { fn1 (this.getAttribute('data-param1a'), this.getAttribute('data-param1b') };
etc
}
I found that creating onclicks with this pattern was particularly helpful in maintaining clarity when I needed to make the onclick launch a number of different functions.

Mithril template converter usage

I am trying to accomplish the exact thing that happens here: https://arthurclemens.github.io/mithril-template-converter/index.html
Basically, I am letting users insert a HTML string into a textbox, and I want to convert that string into an object that I can use with the m helper method.
For example I must convert this:
<div class="foo"><p>bar</p></div>
Into something like this:
m("div", {"class":"foo"},
m("p",
"bar"
)
)
Ideally, I am looking for some type of workflow such as this:
function myComponent() {
let myHTML = "<div class="foo"><p>bar</p></div>";
return(m(convertHTML(myHTML));
}
I have looked into various hypertext/vdom transpilers, but none of them create a tree in the exact format that Mithril expects, so I need very similar functionality of the above listed website for this to work.
I'm guessing this simply isn't possible because of the aspect of nested function calls to m.
I was able to use the templates on the fly with eval:
import { templateBuilder } from "mithril-template-builder"
const source = '<div class="foo"><p>bar</p></div>';
const template = templateBuilder({
source
});
function myComponent(props) {
let object = eval(template);
return(object);
}

Typescript doesn't type check on runtime

I am a beginner with typescript and playing with it on their Playground site. I have the following code:
class Queue<T> {
private data = [];
push = (item: T) => this.data.push(item);
pop = (): T => this.data.shift();
}
let button = document.createElement('button');
button.textContent = "Say Hello";
button.onclick = function () {
let q = new Queue<number>();
q.push("asdada");
alert(q.pop(0));
}
document.body.appendChild(button);
As you can see I created a Queue Object to just accept a number. However, I was able to pass string and able to alert it on my browser. I saw the compiled JS version and it doesn't type check my variable. Isn't typescript supposed to avoid these errors? Or am I mistaken?
Thanks!
Indeed, TypeScript does not check types at runtime. See the FAQ entry.
Typescript will still output regular javascript, so if its valid javascript it will continue to work. it will only yell at you in the editor you're using.
If you prefer typescript should not generate javascript if there is an error use --noEmitOnError as Matt McCutchen suggested in the comments.
If you want to check the types at runtime you can use User-Defined Type Guards.
See this answer for a quick example.

Add tinymce editor to element object instead of selector

I have a custom-element (Aurelia equivelent of a web component) that creates a tinymce editor. There is no way to select the textarea by using a selector (because there can exist any number of these custom-elements on a page). I need some way of initializing the tinymce instance by passing it the element object. Is there such a possibility? I haven't been able to find this functionality anywhere...
Thanks in advance.
Sorry that I'm a bit late. I had this exact same problem. I used an Angular directive, and I wanted to initialize TinyMCE on $element. It turns out you can use this syntax:
var element = getYourHTMLElementSomehow();
//...
tinymce.init({
target: element
});
So you don't use selector at all, and instead use target.
I had to look in the source code for this, because it doesn't seem to be explicitly documented anywhere.
Since TinyMCE seems to require you to use a selector and won't let you simply pass an element instance (and the developer doesn't seem to grasp the utility of this use-case, based on his forum responses), your best bet would be to do something like this:
View
<template>
<textarea id.one-time="uniqueId" ...other bindings go here...></textarea>
</template>
ViewModel
export class TinyMceCustomElement {
constructor() {
this.uniqueId = generateUUID();
}
attached() {
tinymce.init({
selector: `#${this.uniqueId}`,
inline: true,
menubar: false,
toolbar: 'undo redo'
});
}
}
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
My UUID function comes from here: Create GUID / UUID in JavaScript?

Using TypeScript With jQuery Without AMD?

Is it possible to use TypeScript and jQuery (or any other non-Node JavaScript library) without AMD?
Currently my code compiles fine, but the compiled code cannot see the jQuery's $ function.
Given this TypeScript code:
/// <reference path="typings/tsd.d.ts" />
import $ = require('jquery');
export class Widget {
foo() {
$(selector).bar()
}
}
I'd like the following JavaScript:
var Widget = (function ($) {
function Widget() { }
Widget.prototype.foo = function () { ... };
return Widget;
})(jQuery);
Currently my code does not have jQuery passed to the anonymous function.
UPDATE 1
Per #basarat
Do it by hand. If its valid JavaScript then its valid TypeScript
You're suggesting (and I was thinking) that I do this:
var Widget = (function($) {
class Widget {
foo() {
$('body').append('foo');
}
}
return Widget;
})(jQuery);
?
That's not valid TypeScript and without that, I'm left with prototypes.
UPDATE 2
Per #basarat the above is supported with TypeScript 1.6.
Given this TypeScript code , I'd like the following JavaScript
This is not how the TypeScript js emit is designed to work.
So in short : no. You can't get this javascript from that typescript.
I know I can do this by hand. I want to know if there's a TypeScript way to do this.
Do it by hand. If its valid JavaScript then its valid TypeScript. There isn't a specific TypeScript way to do this thing. The JavaScript way is the TypeScript way in this case.
Update
That's not valid TypeScript and without that, I'm left with prototypes.
It is valid TypeScript starting with TypeScript 1.6.

Categories