Define Web Component inside IFrame - javascript

To enhance my tinyMCE Editor, I would like to add some web components and use them inside the editor. Something similar has been done by others, but with older versions of the editor and javascript, which is not valid anymore.
I am aware, that I have to register the tag inside the editor configuration so this works already and my tag makes it to the dom. The problem is, that given components are not defined inside the editors IFrame.
I was not able to add them to the iframes window, the error message doesn't really tell why though. For Testing purposes, I also added the web component to the regular window, where it works flawlessly.
The IFrame:
<iframe srcdoc="<demo-cmp></demo-cmp>" onload="registerComponent(this)"></iframe>
<demo-cmp></demo-cmp>
The Web Component Definition:
class DemoComponent extends HTMLElement {
connectedCallback() {
alert(window.location.href);
}
}
window.customElements.define('demo-cmp', DemoComponent);
function registerComponent(iframe) {
// Exception thrown here
iframe.contentWindow.customElements.define('demo-cmp', DemoComponent);
}
The Error:
Uncaught TypeError: Custom element constructor returned a wrong element
The Fiddle:
https://jsfiddle.net/ynpks9m3/10/

Related

Navigating back causes change detection to break in Angular elements

I am using Angular elements to use my components inside of custom ThingsBoard widgets. The elements are created inside ngDoBootstrap() like this:
const newCustomElement = createCustomElement(CustomElementComponent, {
injector: this.injector,
});
customElements.define("new-custom-element", newCustomElement);
In a ThingsBoard widget I can then import the compiled JavaScript code of my elements via the "Ressources" tab and instantiate them in the onInit() function of the widget like this:
self.onInit = function() {
element = document.createElement(
"new-custom-element");
self.ctx.$container.append(element);
}
While this works just fine and enables me to use Angular components in my widgets, I noticed a strange problem which sometimes occurs when navigating from a ThingsBoard dashboard state back to another dashboard state. Sometimes after navigating back, my widget would seem to load just fine but then the entire change detection seems to be broken. As long as I do not manually call detectChanges() in my component, the component will sometimes appear to freeze entirely in the UI and no longer react to any user interaction (such as a click event). Only refreshing the page will fix this problem.
What could cause this problem and is there anything I can do in order to fix this?
Have you ever tried like this:
ngAfterViewChecked() {
this.cd.detectChanges();
}
or
constructor(private appRef: ApplicationRef) { }
ngAfterViewInit() {
this.appRef.bootstrap(CustomElementComponent);
}
Good luck.

How to properly unload and kill a web component?

im having a simple web component running on my page. By a button click i simply remove the element expecting that the element and the class behind it is getting killed. But actually it keeps running, for example event listeners are still running even after removing it from the DOM.
This is how i add it to the DOM and make it load:
import { LightningElement, createElement } from 'lwc';
import App from 'my/app';
export default class App extends LightningElement {
...
const appinner = createElement('my-app', { is: App });
document.body.appendChild(app);
...
}
Then i simply remove it by this:
const app = document.querySelector('my-app');
app.remove(); // or app.parentNode.removeChild(app);
Everything in my "App" class is still running even when its gone. How can i really make it unload (or deconstruct) or even kill so no logic keeps running.
Update: Missed to mention i am using LWC library from lwc.dev. And the proper way of injecting an element is described here: Link
Update2: Added more code to show how its really done using the LWC.dev library

How To Tell If a View Component Has Completely Loaded

I am having an issue with view components in .NET Core 2.0. I need to be able to detect when a view component has finished loading in the parent view.
Once the view component has loaded, I need to set focus on a specific field that is part of the view component.
Currently, I am using JQuery window.onload(). However, in window.onload() the view component and any subsequent JavaScript has not fully render yet.
Since it has not fully rendered the window.onload event can't find the specific field in the view component.
If I use setTimeout and set it's ms between 1000 and 3000, thus giving the view component time to finish loading, it works fine.
The problem with using setTimeout is that it isn't consistent. Depending on how long the page takes to load it may or may not set the focus on the specified field.
Here is the code jquery code.
var setSearchFocus = function () {
if ($("#divSearch").is(":visible")) {
$('#Diagnosis_Search').focus();
}
}
window.onload = function () {
setTimeout(setSearchFocus, 1000);
}
divSearch is the div in the parent view where the view component is rendered.
Diagnosis_Search is the name of the field in the view component that needs to receive focus.
Appreciate any help with an alternate way to determine when a page has completely loaded or the ablity to detect when a view component has finished loading.
Thanks!
If you're up to using jQuery, you're better off using the document ready event:
$(document).ready(function() {
setTimeout(setSearchFocus, 1000);
});
OnLoad will fire before the entire document/page is ready. Using the $(document).ready(... approach will wait until your page is ready (in other words, when the DOM elements you want to interact with are present and rendered).
This is one of the most common problems on web development. You're using window.onload which is not jquery. It is part of the Document Object Model (DOM) and as you have noticed it doesn't work as expected. This is why the guys # jquery came up with document ready:
$( document ).ready(function() {
console.log( "ready!" );
});
or just
$(function() { console.log("ready!"); });
https://learn.jquery.com/using-jquery-core/document-ready/
UPDATE: as per the comment on the other answer, I get that you've got to wait until an iframe loads your view component. Is that so? If it is, then try to listen to the iframe's document object, an iframe is like another whole webpage embedded on your site, so there's another document object for it. You can access this object from the parent by using
document.getElementById('divSearch').contentWindow.document
you see a document that contains other document, which is actually what we're doing with the iframe. Beware this line isn't going to work if the document you're loading on the iframe is not on the same origin (not part or the same web or in the same TLD), but as you told us that this is a viewComponent this isn't the case probably.
To sum up, try with $(iframe#youriframeid).ready(function() { console.log("ready!"); }) instead.

this is pointing to the quill editor toolbar

I use Quilljs for a textarea on my website. The standard editor don't support image upload to the server, so i have to implement a custom handler. In the documentation is written the following:
Handler functions will be bound to the toolbar (so using this will
refer to the toolbar instance) and passed the value attribute of the
input if the corresponding format is inactive, and false otherwise.
This is actually a problem for me and i dont know how to resolve it in a clean and "right" way. I built an angular application and i have written a custom handler for image uploading. This custom image handler should upload the image with the help of an angular service to the server. The data service is globally provided in the app and a member of my component and i can access it with this.dataService. But after clicking the image upload icon in the toolbar, this is bound to the toolbar, i can't access my data service anymore. Can i avoid this boundary to the toolbar?
In explicit. Assume i have created a quill editor with the following code:
this.editor = new Quill('#editor', {
modules: { toolbar: "#toolbar"},
placeholder: 'Some Placeholder',
theme: 'snow'
});
Now i add a custom handler to the image icon with
this.editor.getModule("toolbar").addHandler("image", imageHandler);
and my handler function for instance:
imageHandler(event) {
this.dataService.addImage(event.file);
}
which uses the dataService which i've already implemented and tested. But this.dataService don't exists because this is now bind to the toolbar. I initialized the service with the constructor of the component.
constructor(private dataService: DataService) {
}
When i call this.dataService in the constructor, then it can be found and the boundary is fine, but i need to call it in my image handler function to send the file to the server.
Best regards,
Sven
The easiest way to solve this problem is to change
this.editor.getModule("toolbar").addHandler("image", imageHandler);
into
this.editor.getModule("toolbar").addHandler("image", imageHandler.bind(this));
now you can access your components variables/members in your image handler function. The toolbar have not anymore the focus.

Problems using window.opener

I have a simple ajax application
From this, a popup is launched, with a form.
Both the form resultpage, and the ajax application hava a javascript file in common.
From the popup window, in the form resultpage, I am trying to call a method from the common javascript file, to apply to the parent window.
My javascript file contains an updateLayer method, which when caleld from the parent window, works fine. I get nothing when trying to call it from the popup window.
The resultpage in the popup window has
<script type="text/javascript" src="x.js">window.opener.updateLayer("Layer3", "380118179930"); </script>
before any html.
Nothing happens in the parentwindow. I have also tried window.parent.
What is the reason and solution for/to this?
I assume this is related to this question, asked by another user that also happens to be named Josh.
In my answer to that question, I tried to explain that the functions from a Javascript file included in your parent window would be attached to the window object, so you use window.opener to get access to that window object to call them.
It looks like you've almost got this solved, but the problem here is that by including src="x.js" in the script tag from your form response, you're effectively overwriting any code placed inside the script. Plus, since x.js is included in the parent window, there's no need to have it in the popup at all, anyway.
The code for your form response should look like this:
<script type="text/javascript">
window.opener.updateLayer("Layer3", "380118179930");
</script>
I've removed the src="x.js" attribute, which would otherwise prevent code between the <script></script> tags from being executed.
Your problem can be that you have two JavaScript files with the same content, while no namespaces are applied.
First, your parent includes the file.js where your updateLayer() is defined. Then the parent opens the child window, which also includes that file.js. If you do that, you have two threads running, where each of them may have it's own functions and objects without bothering the other. I assume that your function is global. This can cause problems, if no namespaces are used. Also it can happen that your big ajax library creates iframes and things like that, and you won't see anything from that because it happens under the hood.
So try: top.window.opener.updateLayer("Layer3", "380118179930");
If that doesn't help, try to open a blank window with no included file.js and call that function from the opener. If that works, wrap the contents of that file.js in a namespace like myNamespace = {....big file content inbetween....}, make two versions of that (or better dynamically include the content) and make sure you have two different namespace. JavaScript is most often not working the way you think it should.
Also, make very sure that the url for your opened window has exactly the same domain. It can cause security issues so that the browser disallows access from a child window to it's parent.
Josh,
Can you determine if the function is triggered at all, like annakata suggests? E.g. by putting an alert box on the first line of the function?
Otherwise: how is the function updateLayer defined in x.js?
If it's defined like this:
function updateLayer(layer, result) {
// ...
}
...then it should work fine.
If it's defined as follows:
var updateLayer = function(layer, result) {
// ...
}
then it will not be available as property of the window object (and thus not available as property of window.opener either). In Firefox, at least; I haven't tested this in IE or other browsers.
Edit: why is this question tagged 'ajax'? AFAICS, all of the problem resides on the client side of the application; no ajax is involved.
Try following:
parent.window.updateLayer();
in a separate <script> tag.
I'm not quite sure whether it works with both src=some.js and inline script at the same time.
Since you have given the script element a src attribute, the results of x.js will be parsed as JS and the text content of the element will be ignored.
<script type="text/javascript" src="x.js"></script>
<script type="text/javascript">
window.opener.updateLayer("Layer3", "380118179930");
</script>
Create a new updateLayer function in you parent html file. Rename it different and call the original updateLayer from it.
e.g.
function updateLayerPage(arg1, arg2)
{
updateLayer(arg1, arg2);
}
and then call this new function from the child page
window.opener.updateLayerPage("Layer3", "380118179930");

Categories

Resources