Paul Paul - 26 days ago 3
CSS Question

Variable scope in javascript - not being picked up by event listener

I have a question that started as a practical one as I want an element containing a video collapsing when the video finishes, for which I need to add an event listener, but as it wasn't working I started doing some testing and I don't understand how is JavaScript accessing variables.

Ok, so in my project I have a video inside an iframe, like so:

html

<p> ...some tex...
<span id="clickable" class="link">click me to watch video</span>.<span><iframe id="frame" class="rect" src="iframe.html" scrolling="no" marginwidth=0 marginheight=0></iframe></span>
...some more tex...</p>


the iframe just has a video

iframe:

<!DOCTYPE html>
<html>
<head>
<title></title>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="script2.js"></script>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<video id="myVid" width="350" height="200" >
<source src="someVideo.mp4" type="video/mp4">
</video>
</body>
</html>


the JavaScript is like so:

$(document).ready(function(){


$("#clickable").click(function(){

var rect = $(this).next().find('.rect');

if (rect.hasClass( "open" )){

rect.removeClass("open");

rect.contents().find("#myVid").get(0).pause();

} else {

rect.addClass("open");

rect.contents().find("#myVid").get(0).play();
}
});


and the css

.rect{
float: left;
height: 0px;
width: 350px;
display: block;
margin: 0px;
padding: 0px;
opacity: 0;

transition-property: all;
transition-duration: 2s;
transition-timing-function: ease-in-out;
}

.open {
height: 200px;
width: 350px;
opacity: 1;
padding-bottom: 10px;
padding-top: 10px;
}


Ok so this works. When i click the link "clickable" the javascript adds the class "open" to the the iframe which makes the height go from 0 to 200px and so the video slides open. Then when i click again the video closes.
So what I was trying to do is to add a function that would also close the video when the video finishes with this function:

$('#myVid').on('ended',function(){
rect.removeClass("open");
alert('finished');
});


And here is when the problem starts. The question is where to place this function. If I place it outside "clickable" the function, it does get triggered when the video finishes (the alert box shows up), but the video doesn't collapse, so I concluded that it couldn't reach the video. Then I modified the event listener like so:

$('#rickieVid').on('ended',function(){

if ($(rect).hasClass("open")){alert('has class')}
else {alert('has not');}
});


And the alert box shows: "has not". So this really confuses me as the class "open" was clearly added with the "clickable" event. Can someone help me understand why this is not working? Many thanks
P

===================================edit==================================

I perhaps should mention that I did try to put the "rect" variable outside the "clickable" function to make it global, like so:

$(document).ready(function(){

var rect;

$("#rickie").click(function(){

rect = $(this).next().find('.rect');

etc...


And then modifying my function to access the global variable like so:

$('#rickieVid').on('ended',function(){

if (rect.hasClass("open")){alert('has class')}

else {alert('has not');}

});


Which still doesn't work as it comes with this error:

TypeError: undefined is not an object (evaluating 'rect.hasClass')

Answer

You can achieve this by attaching the load event handler to the iframe jquery object as the main document is ready even if the iframe source is loading in it and also sometimes the video control takes time to load as well.

Anyway, In that event, you need to set the global player object and also attach the ended event handler to the player.

jQuery code to use:

$(document).ready(function(){

        var frame = $("#frame");
        var player;

        frame.bind("load", function () {
            player = $(this).contents().find("#myVid");
            player.on('ended', function () {
                frame.removeClass("open");
                alert('finished');
            });
        });

        $("#clickable").click(function(){
            if (frame.hasClass("open")) 
            {
                frame.removeClass("open");
                player[0].pause();
            } 
            else {
                frame.addClass("open");
                player[0].play();
            }
        });
    });

One thing to note here. If the iframe source is a cross domain url then the .contents() will throw a security error. This only works if the iframe source page is within the same domain.

Comments