0

I'm fairly new to Javascript in general, and I cobbled together a small script from things found mostly on this site to try to get a small iframe to cycle through a bunch of links, which it does brilliantly. However, I also want it to stop cycling when the user clicks on the iframe or any of its contents.

Here is the code I have so far. There is only one iframe on the HTML page.

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript" charset="utf-8">
<!--
var sites = [
  "side/html5.html",
  "side/silverlight.html",
  "side/wordpress.html",
  "side/mysql.html",
  "side/php.html",
  "side/css3.html"
];
var currentSite = sites.length;
var timerId;
var iframeDoc = $("iframe").contents().get(0);

$(document).ready(function ()
{
  var $iframe = $("iframe").attr("src","side/html5.html");

  timerId = setInterval(function()
  {
    (currentSite == 0) ? currentSite = sites.length - 1 : currentSite = currentSite -1;
    $iframe.attr("src",sites[currentSite]);
  }, 4000);

  $(iframeDoc).bind('click', function()
  {
    clearInterval(timerId);
  });

});
//-->
</script>
</head>

<body>

<sidebar>
<iframe name="sideframe" src="side/html5.html"></iframe>
</sidebar> ..etc..

Please help, I am trying to learn Javascript as fast as I can but as far as I can see, it should work, but it really doesn't.

Thanks for any help you can give me, it's really appreciated.


EDIT:

Okay, I've got a new script now, mostly based off of Elias' work, but it doesn't work either. I've pinned it down in Firebug and it's saying that the timerCallback.currentSite value IS updating properly, though I can't find the $iframe's src value to check for it explicitly. As far as I can tell, it is updating the variables properly, it's just not updating the iframe properly. Am I calling/setting the iframe correctly in this code? Also, is the linked in jquery library sufficient for my purposes? I'm a little lost of all these libraries to link to...

<script type="text/javascript" src="http://code.jquery.com/jquery.js"></script>
<script type="text/javascript" charset="utf-8">

<!--

var sites = 
[
  "side/html5.html",
  "side/silverlight.html",
  "side/wordpress.html",
  "side/mysql.html",
  "side/php.html",
  "side/css3.html"
];

var $iframe = $("iframe").attr("src","side/html5.html");

function timerCallback()
{
    timerCallback.currentSite = (timerCallback.currentSite ? timerCallback.currentSite : sites.length) -1;
    $iframe.attr("src",sites[timerCallback.currentSite]);

    $($('iframe').contents().get('body')).ready(function()
    {
        $(this).unbind('click').bind('click',function()
        {
            var theWindow = (window.location !== window.parent.location ? window.parent : window);
            theWindow.clearInterval(theWindow.timerId);
        });
    });

}

var timerId = setInterval(timerCallback, 4000);

//-->

</script> 
Sarah Carter
  • 86
  • 2
  • 3
  • How about also pausing it when the user hovers the mouse on the iframe? – d_inevitable Apr 20 '12 at 11:17
  • the `$('iframe')` bit won't work, it'll return an array of 1 (or more, depending on the number of iframes on the page). Sorry, but it's pseudo-code. Try using `$('iframe')[0]` or give the iFrame an id, and use the id selector `$('#iFrameId')` – Elias Van Ootegem Apr 20 '12 at 13:54
  • I tried both of your suggestions and `$('#sideframe')` and got `$('iframe')[0] is undefined`, as an error (and likewise for the others). In full: `var $iframe = $('iframe')[0].attr("src","side/html5.html");` - I will put this online and let you see it in action. EDIT: See it at http://www.gandcdesign.co.uk/ – Sarah Carter Apr 20 '12 at 14:36
  • `$('iframe').attr('src');` works in console for me, try that (since there is only 1 iFrame, a standard jQuery object is returned due to the fact that a jQuery wrapper is sort of an array) - my mistake – Elias Van Ootegem Apr 20 '12 at 14:51

2 Answers2

1

I think your code is not working because of this

var iframeDoc = $("iframe").contents().get(0);

This could be getting the header of the iFrame because you are saving the iframeDoc value to the first child of the iframe, the head tag, actually if you have more than 1 iframe in your window iframeDoc would be undefined because $("iframe") gets all the iframes of your document.

BTW your currentSite value condition is wrong, you asign the same value for both conditions

Now I give you an example:

<iframe id="myFrame" src="http://www.google.com/"></iframe>

and the script:

<script type="text/javascript" src="http://code.jquery.com/jquery.js"></script>
<script type="text/javascript">

var sites = [
"site1",
"site2"
];

var myFrame = document.getElementsByTagName('iframe')[0];
var currentSite = myFrame.getAttribute('src');
var timerId;

var myFrameDoc = window.frames[0].document;

$(document).ready(function()
{
    myFrame.setAttribute('src', 'side/html5.html');

    timerId = setInterval(function()
    {
        //WRONGG
        (currentSite == 0) ? currentSite = sites.length - 1 : currentSite = currentSite -1;
        myFrame.setAttribute('src', sites[currentSite]);
        $(myFrame).off("click").on("click", clearInterval(timerId));
    }, 4000);

    //Won't work because you are trying to get events from an inside of a iframe
    //$(myFrameDoc).on("click", clearInterval(timerId));
    //This may work
    $(myFrameDoc).off("click").on("click", clearInterval(timerId));
});

</script>

When you try to track the events of an iframe you have to be carefull because an iframe contains a totally different document for javascript purprouses so basically you have to get inside the new document, unbind the events you need to use, and bind them again against your functionality, as @Elias points out. but be aware that you are changing constantly the src of your iframe, so if yu really need to do that you will have to separate the code that unbinds and binds again your clearInterval, and for that matter maybe $.on() could work for you.

EDIT: Calling to the iframe should work this way IF the src of the iframe is inside the same domain, with the same port and with the same protocol:

var myFrameDoc = window.frames[0].document;

I Added a new variable because we want to bind and unbind the click event to the iframe's document, not to the iframe, we use for that the window.frames collection property, but modern browsers throw an exception and denies acces if you try to access to a frame and you are not on the same domain with using the same port and the same protocol...

Additionaly the use of $.on() and $.off() instead of $.bind and $.unbind() is because the first ones are the new ones and despite we are not using it here, they are capable of watch constantly the DOM for new elements to bind if added; that could be useful to this case if this code still doesn't work. If that is the case you can still change this:

var myFrameDoc = window.frames[0].window;

and this:

$(myFrameDoc).off("click", "document").on("click", "document", clearInterval(timerId));

This will re-bind the event handler to new documents additions. Not tested but could work.

David Diez
  • 1,165
  • 1
  • 11
  • 23
  • I've tried both your solutions, David and Elias, but unfortunately it doesn't seem to even cycle through the pages any more, making it a bit difficult to test the stopping functionality. I can sort of see where you are coming from vis-a-vis the difficulty of the iframe calling it's parent window. I'm trying to use Firebug to debug it, but it's refusing to hit the breakpoints, it's almost like it's not running at all... – Sarah Carter Apr 20 '12 at 12:51
  • EDIT: On David's code, it's saying that myFrame is undefined, even after it rolls through the getElementsByTagName method. – Sarah Carter Apr 20 '12 at 12:57
  • @SarahCarter: do you have access to the site's your loading in the iFrame, or are they external? otherwise, the quickest fix would be to write a js file, and include that in all of those sites... or use ajax, to fetch the content server-side, and add the js there – Elias Van Ootegem Apr 20 '12 at 13:38
  • Oh, I have complete control of all the files on the site and most on the server. So I put the code in a separate .js file and include it as `` on all the sidebar websites that the iframe is calling. I'm guessing that this will require a code rewrite as well, since it will be split up and will be calling from different sections. – Sarah Carter Apr 20 '12 at 13:52
  • not quite, it's the same situation as the `$().load(function(){})` I suggested... no matter what you do, JavaScript will have to switch back and forth between two `window` and, therefore, two `document` objects. I only have so many characters here, so I can't really get into that. I might edit my answer for a couple of more suggestions after work, though... :P – Elias Van Ootegem Apr 20 '12 at 15:14
1

If I were you, I'd play it safe. Since you say you're fairly new to JS, it might prove very informative.

function timerCallback()
{
    timerCallback.currentSite = (timerCallback.currentSite ? timerCallback.currentSite : sites.length) -1;
    $iframe.attr("src",sites[timerCallback.currentSite]);
}
var timerId = setInterval(timerCallback, 4000);
$($('iframe').contents().get('body')).unbind('click').bind('click', function()
{
    var theWindow = (window.location !== window.parent.location ? window.parent : window);
    theWindow.clearInterval(theWindow.timerId);
});

Now I must admit that this code is not tested, at all. Though I think it provides a couple of things to get you on your way:

1) the interval is set using a callback function, because it's just better for tons of reasons

1b) in that Callback, I took advantage of the fact that functions are objects, and created a static var, that is set to either the length of your sites array (when undefined or 0), in both cases 1 is substracted

2) jQuery's ,get() method returns a DOM element, not a jquery object, that's why I wrapped it in $(), a new jQ obj, giving you the methods you need.

3) since you're manipulating the dom inside the iFrame, it's best to unbind events you want to bind

4) inside the iFrame, you don't have direct access to the parent window, where your interval is.

You might want to read up on how to deal with iFrames, because they can be a bit of a faff from time to time

EDIT: David Diez is right, easiest way around this is to incorporate the binding in the timeout function:

function timerCallback()
{
    timerCallback.currentSide = ...//uncanged
    //add this:
    $($('iframe').contents().get('body')).ready(function()
    {
        $(this).unbind('click').bind('click',function()
        {
            //this needn't change
        });
    });
}

In theory, this should bind the click event to the body after it has been loaded

Edit2

I've been messing around a bit, you could keep your code, as is. just add a function:

function unsetInterval()
{
    window.clearInterval(window.timerId);
}

and add this to your setInterval function:

$('#idOfIframe').load(function()
{
    var parentWindow = window.parent;
    $('body').on('click',function()
    {
        parentWindow.clearInterval();
    });
});

this will get triggered as soon as the iFrame content is loaded, and bind the click event and unset the timer, like you wanted to

Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
  • 1
    The `$('iframe').contents().get('body')).unbind('click').bind` is a great and neat trick but beware that this wont work because his code is changing constantly the DOM inside of the iframe, so basically he cannot track the events inside the iframe! If the functionality is separated could work with `$.on()` instead of `$.bind()` – David Diez Apr 20 '12 at 11:52
  • @DavidDiez: added a theoretical workaround, not tested, but the basic idea is to bind on the `ready` event, which should have the same effect as `on` – Elias Van Ootegem Apr 20 '12 at 12:39