Need a callback when it is OK to access the DOM - javascript

I have an Angular component that fetches markdown, converts it to HTML, then inserts it into the DOM. After insertion it does some more DOM manipulation. So the template is this:
<div id="fixroot" [innerHTML]="content"></div>
and the component is like this:
content: string;
...
async getPage(url: string) {
try {
this.content = this.mdToHtml(await this.http.get(url, { responseType: 'text'}).toPromise();
setTimeout(() => this.fixupDOM(), 400);
....
fixupDom() {
const el = document.getElementById('fixroot');
// do stuff to the DOM element that are children of el
The use of setTimeout here is really awful, but without it the fixupDom method doesn't work
because the DOM is not ready when the Promise concludes. But it has to go.
What I need is a callback (probably from the HTMLElement
el) that calls my routine as soon as the DOM structure is ready. I haven't been able
to make anything work. Anyone know the right function?

You can try and use the ngAfterViewInit hook that fires after your content has been loaded.
If you use a template reference for your div then you can access it after the ngAfterViewInit fires.
This is the angular way of accessing the DOM which makes it framework agnostic in case you want to run in a web worker etc...
Since you are inserting HTML you made also need to pipe that HTML through a Sanitizer because angular has a sanitizing context due to XSS.

ngAfterContentInit lifecycle hook is called earlier during creation and rendering of component and directive.
You can implement it in your directive and access content to be reflected in the directive's view. For example,
ngAfterContentInit() {
const el = this.content.nativeElement;
// mutate el as desired
}

Related

How do I assign a variable to a DOM Node created by lit-html

I'm using lit-html to render html templates, but after they've rendered I need to assign a variable to a couple of nodes inside the template result. Currently, the only way I've found to do this is with a dirty timeout like this:
let myInput;
render(myTemplate(myData), myContainer);
setTimeout(() => {
myInput = myContainer.querySelector(".myInput");
}, 0);
Is there a nicer way to do this? It would be awesome if there was a way to assign the variable within the template itself.
Thanks
You can set properties on elements in your templates with the property binding syntax:
const myTemplate = (data) => html`<my-element .myProperty=${data.foo}></my-element>`;
See the docs here:
https://lit-html.polymer-project.org/guide/writing-templates#bind-to-properties
Also, render() is synchronous, so even in cases where you do need to query the rendered DOM, you don't need setTimeout.

ReactJS - render external DOM element with events

I use ReactJS 16.13.1 and now I want to render an external DOM element with its events.
So let's assume there is a
<button type="button" id="testBtnSiri" onclick="alert('Functionality exists');">Testbutton Siri</button>
Which has been generated by a library (with some other events as well).
Now I want:
To copy it (with events) & render this element in my ReactJS render function.
What is the most appropriate way to do this ?
I am not looking for ReactDOM.createPortal().
I want to show an external DOM element with its events in my React Component.
In order to use HTML within your React code you need to make use of dangerouslySetInnelHTML prop on an element.
function libraryReturnedHTML() {
return '<button type="button" id="testBtnSiri" onclick="alert('Functionality exists');">Testbutton Siri</button>'
}
function MyComponent() {
return <div dangerouslySetInnerHTML={__html: libraryReturnedHTML} />;
}
However you must know that this makes your code susceptible to XSS attacks. In order to prevent such scenarios its a good idea to first sanitize your HTML so that you have only the desired scripts and link tags. In order to sanitizehtml you can simply use normal Javascript functions and document.createTreeWalker
You can also make use of an existing library to sanitize your HTML. For example sanitize-html
However the sanitization is just an added precaution.
Create a wrapper over Node.addEventListener() which will catch all the event listeners added to the button when it's rendered by other library. It could look like this(this code have to be executed before the external library creates the button):
let ael = Node.prototype.addEventListener
let eventListeners = []
Node.prototype.addEventListener = function(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void {
if((this as HTMLElement).id === "buttonID")
eventListeners.push({type, listener, options})
this.addEventListener = ael
this.addEventListener(type, listener, options)
}
Create react component with your button - you can create a new one, or inject using dangerouslySetInnerHtml. Pass eventListeners array as a prop. On component mount and unmount, add/remove the event listeners to your button.
function ClonedButton({eventListeners}: any){
useEffect(()=>{
eventListeners.forEach((el) => document.getElementById("my-migrated-new-button")!.addEventListener(el.type, el.listener, el.options));
return () => {
eventListeners.forEach((el) => document.getElementById("my-migrated-new-button")!.removeEventListener(el.type, el.listener, el.options));
}
}, [])
return <button id="my-migrated-new-button"></button>
}

Access DOM when using hyper.Component

When using HyperHTMLElement it's possible to access the contents of the component by simply using this.children or this.querySelector(), since it's an element.
But how would I achieve similar behavior when using hyper.Component?
The hypothetical example I have in mind is from React docs: https://facebook.github.io/react/docs/refs-and-the-dom.html - I'd like to focus a specific node inside my DOM.
I have a codepen sandbox where I'm trying to solve this: https://codepen.io/asapach/pen/oGvdBd?editors=0010
The idea is that render() returns the same Node every time, so I could save it before returning and access it later as this.node:
render() {
this.node = this.html`
<div>
<input type="text" />
<input type="button" value="Focus the text input" onclick=${this} />
</div>
`;
return this.node;
}
But that doesn't look clean to me. Is there a better way to do this?
The handleEvent pattern is there to help you. The idea behind that pattern is that you never need to retain DOM references when the behavior is event-driven, 'cause you can always retrieve nodes via event.currentTarget, always pointing at the element that had the listener attached, or event.target, suitable for clicks happened in other places too within a generic click handler attached to the wrap element, in your demo case the div one.
If you'd like to use these information, you can enrich your components using an attribute to recognize them, like a data-is="custom-text-input" on the root element could be, and reach it to do any other thing you need.
onclick(e) {
var node = e.target.closest('[data-is=custom-text-input]');
node.querySelector('[type=text]').focus();
}
You can see a working example in a fork of your code pen:
https://codepen.io/WebReflection/pen/RLNyjy?editors=0010
As alternative, you could render your component and address its content once as shown in this other fork:
https://codepen.io/WebReflection/pen/LzEmgO?editors=0010
constructor() {
super().node = this.render();
}
at the end of the day, if you are not using custom elements but just basic, good'ol DOM nodes, you can initialize / render them whenever you want, you don't need to wait for any upgrade mechanism.
What is both nice and hopefully secure here, is that there's no way, unless you explicitly expose it, to address/change/mutate the instance related to the DOM element.
I hope these possibilities answered your question.
This is something I've worked on in the past via https://github.com/joshgillies/hypercomponent
The implementation is actually quite trivial.
class ElementalComponent extends hyper.Component {
constructor () {
super()
const _html = super.html
this.html = (...args) => {
this.node = _html.apply(this, args)
return this.node
}
}
}
class HelloWorld extends ElementalComponent {
render () {
return this.html`<div>Hello World!</div>`
}
}
This works really well and is inline with your question. However, it's worth noting hyperHTML can render not only a single node but also multiple nodes. As an example:
hyper`<div>Hello World!</div>` // returns a single DOM Node
hyper`<div>Hello</div> <div>World!</div>` // returns multiple DOM Nodes as an Array.
So this.node in the above ElementalComponent can be either a DOM Node, or Array based on what the renderer is doing.

Difference between the created and mounted events in Vue.js

Vue.js documentation describes the created and mounted events as follows:
created
Called synchronously after the instance is created. At this
stage, the instance has finished processing the options which means
the following have been set up: data observation, computed properties,
methods, watch/event callbacks. However, the mounting phase has not
been started, and the $el property will not be available yet.
mounted
Called after the instance has just been mounted where el is replaced
by the newly created vm.$el. If the root instance is mounted to an
in-document element, vm.$el will also be in-document when mounted is
called.
This hook is not called during server-side rendering.
I understand the theory, but I have 2 questions regarding practice:
Is there any case where created would be used over mounted?
What can I use the created event for, in real-life (real-code)
situation?
created() : since the processing of the options is finished you have access to reactive data properties and change them if you want. At this stage DOM has not been mounted or added yet. So you cannot do any DOM manipulation here
mounted(): called after the DOM has been mounted or rendered. Here you have access to the DOM elements and DOM manipulation can be performed for example get the innerHTML:
console.log(element.innerHTML)
So your questions:
Is there any case where created would be used over mounted?
Created is generally used for fetching data from backend API and setting it to data properties. But in SSR mounted() hook is not present you need to perform tasks like fetching data in created hook only
What can I use the created event for, in real-life (real-code) situation?
For fetching any initial required data to be rendered(like JSON) from external API and assigning it to any reactive data properties
data:{
myJson : null,
errors: null
},
created(){
//pseudo code
database.get().then((res) => {
this.myJson = res.data;
}).catch((err) => {
this.errors = err;
});
}
For the created() hook, the data after manipulation in the browser it not shown in the DOM before mounted. In simple words the data takes time to manipulate the DOm seen the browser .
The mounted() hook is called after the DOM has been mounted or rendered which enables you to have access to the DOM elements and you can perform DOM manipulation.The best use for the mounted hook is if you need to access the DOM immediately before or after the initial render.

How to see dom tree for specific component in JSX React

I have the following code in my component:
renderName: function(name) {
return (
{name}
)
}
then in render method I want to inspect what will be HTML DOM representation of running this method:
render: function() {
let namePart = this.renderName('Berlin');
...
}
How can I see code
<div>
Berlin
</div>
Maybe there is some method for compiling this object that I can see during inspection in DevTools into HTML?
If you want to reference this in React, one way is to use
ReactDOM.findDOMNode(this)
Here are the docs on it.
https://facebook.github.io/react/docs/react-dom.html#finddomnode
ReactDOM.findDOMNode(this)
If this component has been mounted into the DOM, this returns the corresponding native browser DOM element. This method is useful for reading values out of the DOM, such as form field values and performing DOM measurements. In most cases, you can attach a ref to the DOM node and avoid using findDOMNode at all.
It suggests using refs if you need to avoid using findDOMNode.

Categories

Resources