How to target Elements in a HTML Widget

You often need to access and use an element you have placed in the innerHTML of an HTML Widget

for example, a video element.

<video width="100%" id="myVideo" preload="auto" x-webkit-airplay="true" webkit-playsinline="true">
    <source src="${resourcesFolderName}/Untitled.mp4" type="video/mp4">
    
</video>

It may seem like you should just be able to use the normal hypeDocument Syntax to get the video element,

var myVid= hypeDocument.getElementById('myVideo');

or indeed the documents syntax,

var myVid= document.getElementById('myVideo');

But you will quickly find that this does not work as expected. You will either get an undefined returned and most likely an error on you next line.

Why…?

A HTML Widget is in effect an iFrame. Which is itself a completely separate web page from it’s parent.
The parent being your main web page.

As shown here.

This being the case the Parent does not have the same window,document or body context as the iFrame. It will NOT be able to access directly anything within the iFrame.

The HTML Inline Frame Element:

represents a nested browsing context, effectively embedding another HTML page into the current page.
In HTML 4.01, a document may contain a head and a body or a head and a frame-set, but not both a body and a frame-set. However, an iframe can be used within a normal document body. Each browsing context has its own session history and active document. The browsing context that contains the embedded content is called the parent browsing context. The top-level browsing context (which has no parent) is typically the browser window.


So how can we access stuff in the ** HTML Widget**.

The simplest way is to get the first child Element/Node of the widget. This will be the iFrame.

var iframe= hypeDocument.getElementById('theWidget').children[0];

Node.children is a read-only property that returns a live HTMLCollection of the child elements of Node.

Once we have the iframe as an object, we can then drill down into it.
First we need to get it’s Document content object.

var iframeDocument = iframe.contentDocument || iframe.contentWindow.document;

We return contentDocument or contentWindow.document depending on browser compatibility

And now that we have the document object we can finally get the element we are after.

var myVid =  iframeDocument.getElementById('myVideo')

If using an HTML Widget is not essential, then it would be simpler to use a Rectangle shape and edit it’s innerHtml.

The Rectangle can take the same video element code as above.

We do not even need to first get the Rectangle as an object first. Because it is in the same context as it’s parent node and does not correspond to a web page in itself. It is just a Div with content.

So to get the video element. We can access it directly by it’s id with one line of code.

var myVid= hypeDocument.getElementById('myVideo') 

Widget project:
WidgetVideoElement.hype.zip (2.7 MB)

Rectangle project
RectVideoElement copy.hype.zip (2.7 MB)


6 Likes

This is a great tip of the month candidate.

Cheers Greg, I found a lot of people seemed to be trying to do this.

1 Like

Really Great Info. Thank you! I remember reading something in documentation about the rectangles inside symbols must be referenced in a special way as well. I can’t find that information anymore. I think it’s something like:
var myVid= hypeDocument.symbolInstance.element(.).getElementById('myVideo')

Can anyone enlighten me?

The fundamental issue with items in symbols is that if you have multiple instances of that symbol, then the id becomes meaningless as you have two elements with the same id. So instead of using an id for an element in a symbol, you should use a class. You can use standard getElementsByClassName() function. If you know which symbol you want to look into (which can have its own ID), then your code would look something like:

var videos = hypeDocument.getSymbolInstanceById("mySymbolID").getElementsByClassName("myVideoClassName");
var myVideo = videos[0]; // unsafe, make sure there really is video here
1 Like

Hello, I implemented your code but I get the error:

Error in undefined: TypeError: Cannot read property 'play' of null

Please click on the rocket.
Do you have an idea how to solve it? How is the right way to autoplay (with audio) the video at a scene?

Thanks

It looks like you’re trying to play a video in its own <video> element:

<video id="myVideo" poster="${resourcesFolderName}/weltall_preview.jpg" playsinline="" width="100%" height="100%" preload="auto">
      <source src="${resourcesFolderName}/Weltall_2.mp4" type="video/mp4">
</video>

Instead of placing this in a HTML widget, you can place this in a regular rectangle and avoid all the complexity here. This will allow you to just use this code to play it:

var theVideo = document.getElementById("myVideo");
theVideo.play();

A bit more thorough information on this here: Creating Play & Pause Buttons for Video

Hi Daniel, thanks, but a rectangle is not working under android with cordova. I tried this before. The video is not playing. Because of that I tried to do it with a widget. How is the right way to autoplay (with audio) at the start of a scene?

The MediaPlaybackRequiresUserAction item in the Config will let you do this for iOS but I don’t know how to do this same thing for Android. This might be a workaround:

2 Likes

Thank you very much.