Passing Message Exercise: 'msg.js:15 Uncaught TypeError: Cannot set property 'onclick' of null'

I’m doing a small exercise on learning how to pass messages between a couple of HTML documents, but am seeing an error in the console. The two HTML pages are as follows:

##msg-send.html:

<!DOCTYPE HTML>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Messaging Application</title>
    <script src="http://192.168.0.12/jsies/msg.js"></script>
    <link type="text/css" rel="stylesheet" href="../css/style.css">
  </head>
  <body>
    <div id="main">
      <p id="host">Main Page Domain: </p>
      <iframe id="ifr" src="msg-receive.html" width="450" height="120"></iframe>
      <button id="btn">Send Message</button>
    </div>
  </body>
</html>

##msg-receive.html:

<!DOCTYPE HTML>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Messaging Application</title>
    <link type="text/css" rel="stylesheet" href="../css/style.css">
  </head>
  <body>
    <div id="main">
      <p id="host">Iframe Page Domain: </p>
      <p id="para"></p>
    </div>
    <script src="msg.js"></script>
  </body>
</html>

And the JavaScript code is…
##msg.js

function sendMsg() {
  var win = document.getElementById( 'ifr' ).contentWindow;
  win.postMessage( 'Message Received From:' + document.domain, 'http://192.168.0.12' );
}

function readMsg( event ) {
  if( event.origin === 'http://localhost' ) {
    document.getElementById( 'para' ).innerHTML = ( event.data );
  }
}

function init() {
  document.getElementById( 'host' ).innerHTML += document.domain;
  window.addEventListener( 'message', readMsg, false );
  document.getElementById( 'btn' ).onclick = sendMsg;
}
document.addEventListener( 'DOMContentLoaded', init, false );

The error in the console is msg.js:15 Uncaught TypeError: Cannot set property 'onclick' of null

This is being run off my VM, hence the LAN IP address in there. Can anyone see anything I might have missed?

Edit: This is a screenshot of the result. If running correctly, there should be a second line of text in the iframe that reads “Message Received From: localhost”.

I don’t know how the domain IP is getting there OK (loading iframe content has precedence?)

But the “null” suggests that the <script> tag should be moved to just before the </body>

This is probably where this particular book has let me down (again). It is though, teaching me way more than the actual words it contains ever imagined.

I’ll move the script and see what happens.

No change. Same error in the console and same result in the viewport.

In trying it on my localhost I noticed the DOMContentLoaded event listener, so now I think it should be OK where it was.

But I notice both HTML examples look the same and I can’t find an element with a “para” Id.

Are you sure you posted correct example code?

Whoops! My mistake… :flushed:

I’ve added in the correct markup for msg-receive page above.

OK, with a few changes (all files in the same folder, no CSS file, changing 192.168.0.12) I think I found the problem.

Both HTML files were calling in msg.js
The “null” is because “receive” does not have a “btn” so to use the same file in both I changed the script to see if it exists before it tries to use it. .

<!DOCTYPE HTML>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Messaging Application</title>
    <script src="msg.js"></script>
  </head>
  <body>
    <div id="main">
      <p id="host">Main Page Domain: </p>
      <iframe id="ifr" src="msg-receive.html" width="450" height="120"></iframe>
      <button id="btn">Send Message</button>
    </div>
  </body>
</html>```
<!DOCTYPE HTML>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Messaging Application</title>
    <script src="msg.js"></script>
  </head>
  <body>
    <div id="main">
      <p id="host">Iframe Page Domain: </p>
      <p id="para"></p>
    </div>
  </body>
</html>
function sendMsg() {
  var win = document.getElementById( 'ifr' ).contentWindow;
  win.postMessage( 'Message Received From:' + document.domain, 'http://localhost' );
}

function readMsg( event ) {
  if( event.origin === 'http://localhost' ) {
    document.getElementById( 'para' ).innerHTML = ( event.data );
  }
}

function init() {
  document.getElementById( 'host' ).innerHTML += document.domain;
  window.addEventListener( 'message', readMsg, false );
  var send_btn = document.getElementById( 'btn' );
  if (send_btn) {
    send_btn.onclick = sendMsg;
  }
}
document.addEventListener( 'DOMContentLoaded', init, false );

Thanks for that. That would have been something that wouldn’t have occurred to me at all as a potential problem. The book the markup and script comes from makes no attempt to do anything like that.

I’d come to the conclusion that I’d messed up my conversion of localhost/FQDN URLs to represent my own environment - the book uses things like example.com, which are pretty much useless for getting the code to run.

I’ll test your solution out tomorrow, as it’s pretty late here now. It will be some time after my shipping arrives back from Riyadh, so it’s anyone’s guess how well my brain will be working by then… :tired_face:

Thanks again for your help.

Hey @chrisofarabia

Sounds like your expanding your javascript knowledge! If postMessage is something you’re looking to use in something you hope to build, you should check out Paypal’s open source library [Post-Robot] (https://github.com/krakenjs/post-robot) The API for postMessage is really limiting and kind of a mess in browsers. Post-Robot tries to normalize browser behavior and add additional useful functionality. You can also check out Daniel’s article about it here: https://medium.com/@bluepnume/introducing-post-robot-smart-cross-domain-messaging-from-paypal-bebf27c8619e#.npv3t74sa

I got there in the end, but the problem wasn’t as I was understanding it. I needed two servers running to get it to work! What I was trying to do was get it all to function from my VM (all files in the same folder etc.). When the penny finally dropped that I needed to create a localhost on my laptop, I installed UniformServer on my C:\drive, and copied the files across as follows:

##localhost:

  • msg-send.html
  • msg.js
  • css/style.css

##VM: 192.168.2.10/jsies/

  • msg-receive.html

Having re-jigged the URLs appropriately, it now all works as expected, with only one minor oddity.

When msg-send.html is first loaded, the <iframe> looks like this, with the [ object Object ] message at the bottom.

On clicking the button though, this is replaced by the correct message “Message Received From:localhost”. My guess is that the sendMessage() is being triggered regardless of whether the button has been clicked.

Other than that, it looks like I’m just about there.

The currently running markup/code is:

###msg-send.html

<!DOCTYPE HTML>

<html lang="en">

  <head>
    <meta charset="UTF-8">
    <title>Messaging Application</title>
    <link type="text/css" rel="stylesheet" href="css/style.css">
  </head>

  <body>
    <div id="main">
      <p id="host">Main Page Domain: </p>
      <iframe id="ifr" src="http://192.168.2.10/jsies/msg-receive.html" width="450" height="120"></iframe>
      <button id="btn">Send Message</button>
    </div>
    <script src="http://localhost/msg.js"></script> <!-- DON'T TOUCH -->
  </body>

</html>

###msg-receive.html

<!DOCTYPE HTML>

<html lang="en">

  <head>
    <meta charset="UTF-8">
    <title>Messaging Application</title>
    <link type="text/css" rel="stylesheet" href="../css/style.css">
  </head>
  <body>
    <div id="main">
      <p id="host">Iframe Page Domain: </p>
      <p id="para"></p>
    </div>
    <script src="http://localhost/msg.js"></script>
  </body>

</html>

###msg.js

function sendMsg() {
  'use strict';
  var win = document.getElementById( 'ifr' ).contentWindow;
  win.postMessage( 'Message Received From:' + document.domain, 'http://192.168.2.10' );
}

function readMsg( event ) {
  'use strict';
  if( event.origin === 'http://localhost' ) {
    document.getElementById( 'para' ).innerHTML = ( event.data );
  }
}

function init() {
  'use strict';
  document.getElementById( 'host' ).innerHTML += document.domain;
  window.addEventListener( 'message', readMsg, false );
  var send_btn = document.getElementById( 'btn' );
  if (send_btn) {
    send_btn.onclick = sendMsg;
  }
}
document.addEventListener( 'DOMContentLoaded', init, false );

I’m thinking it might have something to do with either using a numeric IP instead of the string localhost, the absolute URL for the iframe src, or a difference in how the browser deals with the message event.

What browser are you using?

Chrome, latest version. I’ll give it a go with a few others and see if I get any different results.

No odd message on FF, Opera, IE11, or Brave. Still getting the message on Chrome, and Edge doesn’t seem able to access the page in the iframe.

I can’t get [object Object] to show.

I tried using an absolute iframe src instead of relative - no “object”
I replaced “localhost” with “124.0.0.1” where you have “192.168.2.10” - no “object”
I tried different browsers - Edge, IE11, Chrome, Opera Dev, Vivaldi - no “object”

The only thing I can think of is that it might have something to do with how the VM is working.

The “[object Object]” looks like how JavaScript might display “event.data” when it exists but doesn’t have a value.

You could put the two conditionals into one, but try

function readMsg( event ) {
  if( event.origin === 'http://localhost' ) {
    if (typeof event.data === "string") {
      document.getElementById( 'para' ).innerHTML = ( event.data );
    }
  }
}

* also, unless you are passing HTML, textContent would be sufficient. eg.

//      document.getElementById( 'para' ).innerHTML = ( event.data );
      document.getElementById( 'para' ).textContent = ( event.data );

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.