8

I have a shadowbox script. When I load the page everything works fine, but when I call this jquery load function and then try to trigger the shadowbox by clicking on the image, the large image opens in new window instead. Here's the code:

<link href="CSS/main.css" rel="stylesheet" type="text/css" />
<script type="text/javascript"  src="shadowbox-3.0.3/shadowbox.js"></script>
<script type="text/javascript">
Shadowbox.init();
</script>

<p id="compas"><a href="images/coverage.jpg" rel="shadowbox" title="Coverage map"></a></p>

Any idea why this is happening?

Igor
  • 1,532
  • 4
  • 23
  • 44
  • What does the error console (Firebug, Chrome console) report? You will probably want to use Persist as well. – Jared Farrish Feb 04 '12 at 21:13
  • Nothing shows. The images opens in new ordinary window. – Igor Feb 04 '12 at 21:14
  • What I'm seeing, I don't spot anything wrong per se. Do you have a link? – Jared Farrish Feb 04 '12 at 21:17
  • No link, but I can send you the code? Or I can add the jquery function here. – Igor Feb 04 '12 at 21:21
  • Make a http://jsfiddle.net if you can. – Jared Farrish Feb 04 '12 at 21:22
  • I also made a fiddle: http://jsfiddle.net/kPE2z/ That seems to work, as well. I'll take a look at your fiddle. – Jared Farrish Feb 04 '12 at 21:32
  • Just to be clear. Mine work well until I call the jquery load function that changes the content div. – Igor Feb 04 '12 at 21:38
  • Yes, it does work when you comment out the `$.load()` calls: http://jsfiddle.net/uyqwz/1/ – Jared Farrish Feb 04 '12 at 21:42
  • This sounds awfully familiar: http://stackoverflow.com/questions/2192095/jquery-shadowbox-and-ajax – Jared Farrish Feb 04 '12 at 21:42
  • @JaredFarrish Yeah it does, but still doesn't explain how to fix it. And the html that I'm loading doesn't contain javascript in it. – Igor Feb 04 '12 at 21:47
  • This works: http://jsfiddle.net/uyqwz/4/ I had to use a different URL for the `$.load()` so it would work in jQuery, but otherwise it's "essentially" the same. You didn't need all of those `$(document).ready()`s, you can put all of those calls into one callback. See the fiddle I linked for what I mean. – Jared Farrish Feb 04 '12 at 21:51
  • Also!, [this tutorial](http://titancms.com/KB/HowtoIntegrateShadowbox.htm) says to put the `Shadowbox.init()` within the `$(document).ready()` block. – Jared Farrish Feb 04 '12 at 21:52
  • @JaredFarrish I did that. Still opens in new window. I'll take a look at the tutorial now. – Igor Feb 04 '12 at 22:00
  • Please see this: http://jsfiddle.net/uyqwz/11/ – Igor Feb 04 '12 at 22:05
  • I'm not sure what to tell you; this is working just fine: http://jsfiddle.net/uyqwz/13/, and it's not too different from your demo (I just linked in all the Shadowbox resource files and did some cleanup, including a stray `` after your jQuery include). – Jared Farrish Feb 04 '12 at 22:06
  • You had a ` – Jared Farrish Feb 04 '12 at 22:10
  • @JaredFarrish When I click on 'Services' and then 'home' and then 'see coverage map' - opens it in new window. Otherwise it's fine. It probably works in the fiddle becouse $.load doesn't load anything. – Igor Feb 04 '12 at 22:12
  • That's because you're still using your relational links in the `$.load()`s and JSFiddle can't work with that (that's why I replaced them with [`$(".content").load('/echo/html/');`](http://jsfiddle.net/uyqwz/4/), which is a special page setup by JSFiddle for mock AJAX calls). I'm going to write up an answer based on what I've seen. I believe the issue is that you're code is a little confused and whatnot, but without seeing the live page with the problem itself (not a fiddle, which I can fix with little effort), it's hard to say for sure. – Jared Farrish Feb 04 '12 at 22:17
  • You can put it in a comment and I'll let you know when I get it, then you can delete the comment. – Jared Farrish Feb 04 '12 at 22:24
  • @JaredFarrish you there? – Igor Feb 04 '12 at 23:21
  • Yes, I was trying to hammer some stuff down and write an answer. See below. – Jared Farrish Feb 04 '12 at 23:23

3 Answers3

11

EDIT

So, we finally get the bottom of this. 15 hours after first commenting on this issue, and at least 50 iterations later, we finally have identified what the problem is and how to fix it.

It actually struck me suddenly when I was creating local aaa.html and bbb.html on my server. That was when it hit me that the element nodes for the content that was being replaced was being removed altogether from the DOM when $.load() runs the callback function. So, once the #menu-home content elements were replaced, they were removed from the DOM and no longer had Shadowbox applied to them.

Once I figured this out, it was just a matter of a single web search and I found:

Nabble-Shadowbox - Reinit Shadowbox

Specifically, the response from mjijackson. What he describes is how to "restart" (reinitialize) Shadowbox using:

Shadowbox.clearCache();
Shadowbox.setup();

So once the #menu-home content was reloaded, what needs to happen is the Shadowbox cache needs to be cleared (essentially, shutting it down on the page), then the Shadowbox.setup() is run, which will detect the elements all over again. You don't run the Shadowbox.init() method again either.

I noticed that you had tried to copy/paste the Shadowbox.setup() in after the $.load(), at least sequentially in the code. However, this wasn't going to work, due to the cache clearing that needs to happen first, and primarily because the .clearCache() and .setup() functions need to be run after the $.load() completes (finishes and runs any callbacks). Those two functions need to be run in the $.load() callback handler; otherwise, you're running it's immediately, but the $.load() is asynchronous and will complete at some later time.

I'm going to go over some other changes I made, just so you understand what, why and wherefore.

Note, I'm not sure if you're familiar with <base>, but the following is at the top of the HEAD element:

<base href="http://62.162.170.125/"/>

This just let's me use the resource files on your computer. You'll not want to use this on your actual site more than likely. If you copy/paste, make sure and remove this line.

<div id="menu">
  <ul>
    <li><a id="menu-home" href="index.html" rel="http://jfcoder.com/test/homecontent.html">Home</a></li>
    <li><a id="menu-services" href="services.html" rel="http://jfcoder.com/test/servicescontent.html">Services</a></li>
    <li><a id="menu-tour" href="tour.html" rel="http://jfcoder.com/test/tourcontent.html">Tour</a></li>
    <li><a id="menulogin" href="login.html">Login</a></li>
  </ul>
</div>

Here, you'll notice I have a relative url in the HREF attribute, and a link to some pages on my server. The reason for the links to my server is that I couldn't access your aaa.html and bbb.html files through AJAX due to cross-site scripting limitations. The links to my website should be removed as well.

Now, the reason I'm using the rel attribute here is that I want allow for the links by way of the href attribute to continue to work in case the JS doesn't function correctly or there's some other error. If you have separate files, one for full HTML document and another for just the fragments, this is what you'll want to do. If you can serve both the full document AND the content-only from the linked file, then you probably don't need the rel attribute, but you'll need to manage the request so the server knows how to respond (full document or just the content part).

var boxInitialize = function(){
    try {
        if (!Shadowbox.initialized) {
            Shadowbox.init();
            Shadowbox.initialized = true;
        } else {
            Shadowbox.clearCache();
            Shadowbox.setup();
        }
    } catch(e) {
        try {
            Shadowbox.init();
        } catch(e) {};
    }
};

All I've done here is create a central location for the initialization/setup requests. Fairly straightforward. Note, I added the Shadowbox.initialized property so I could keep track of if the Shadowbox.init() had run, which can only be run once. However, keeping it all in one spot is a good idea if possible.

I also created a variable function which can be called either as a regular function:

boxInitialize();

Or as a function reference:

window.onload = boxInitialize; // Note, no () at the end, which execute the function

You'll probably notice I removed the $() and replaced them with jQuery() instead. This can turn into a real nightmare if you end up with an environment with multiple frameworks and libraries competing for $(), so it's best to avoid it. This actually just bit me real good the other day.

Since we have a closure scope within the .ready() callback, we can take advantage of that to save several "private" variables for ow use at different times in the scripts execution.

var $ = jQuery,
    $content = jQuery("#content"), // This is "caching" the jQuery selected result
    view = '',
    detectcachedview = '',
    $fragment,
    s = Object.prototype.toString,
    init;

Note the , at the end of all but the last line. See how I "imported" the $ by making it equal to the jQuery variable, which means you could actually use it in that#.

var loadCallback = function(response, status, xhr){
    if (init != '' && s.call(init) == '[object Function]') {
        boxInitialize();
    }

    if (xhr.success() 
          && view != '' 
            && typeof view == 'string' 
              && view.length > 1) {
        $fragment = $content.clone(true, true);
        cacheContent(view, $fragment);
    }
};

This runs when the $.load() completes the process of the AJAX request. Note, the content returned in the request has already been placed on the DOM by the time this runs. Note as well that we're storing the actual cached content in the $content.data(), which should never be removed from the page; only the content underneath it.

var cacheContent = function(key, $data){
    if (typeof key == 'string'
          && key.length > 1
            && $data instanceof jQuery) {
        $content.data(key, $data.html());
        $content.data(detectcachedview, true);
    }
};

cacheContent() is one a method you may not want; essentially, if it was already loaded on a previous request, then it will be cached and then directly retrieved instead of initiating another $.load() to get the content from the server. You may not want to do this; if so, just comment out the second if block in the menuLoadContent() function.

var setContent = function(html){
    $content.empty().html(html);

    if (init != '' && s.call(init) == '[object Function]') {
        boxInitialize();
    }
};

What this does is first empty the $content element of it's contents/elements, then add the specified string-based markup that we saved earlier by getting the $content.html(). This is what we'll re-add when possible; you can see once the different links have been clicked and loaded, reclicking to get that to redisplay is really quick. Also, if it's the same request as currently loaded, it also will skip running the code altogether.

(We use $content like because it is a reference to a variable containing a jQuery element. I am doing this because it's in a closure-scope, which means it doesn't show up in the global scope, but will be available for things like event handlers.

Look for the inline comments in the code.

var menuLoadContent = function(){
    // This is where I cancel the request; we're going to show the same thing
    // again, so why not just cancel?
    if (view == this.id || !this.rel) {
        return false;
    }

    // I use this in setContent() and loadCallback() functions to detect if
    // the Shadowbox needs to be cleared and re-setup. This and code below
    // resolve the issue you were having with the compass functionality.
    init = this.id == 'menu-home' ? boxInitialize : '';
    view = this.id;
    detectcachedview = "__" + view;

    // This is what blocks the superfluous $.load() calls for content that's
    // already been cached.
    if ($content.data(detectcachedview) === true) {
        setContent($content.data(view));
        return false;
    }

    // Now I have this in two different spots; there's also one up in 
    // loadCallback(). Why? Because I want to cache the content that
    // loaded on the initial page view, so if you try to go back to
    // it, you'll just pickup what was sent with the full document.
    // Also note I'm cloning $content, and then get it's .html() 
    // in cacheContent().
    $fragment = $content.clone(true, true);
    cacheContent(view, $fragment);

    // See how I use the loadCallback as a function reference, and omit
    // the () so it's not called immediately?
    $content.load(this.rel, loadCallback);

    // These return false's in this function block the link from navigating
    // to it's href URL.
    return false;
};

Now, I select the relevant menu items differently. You don't need a separate $.click() declaration for each element; instead, I select the #menu a[rel], which will get each a element in the menu that has a rel="not empty rel attribute". Again, note how I use menuLoadContent here as a function reference.

jQuery("#menu a[rel]").click(menuLoadContent);

Then, at the very bottom, I run the boxInitialize(); to setup Shadowbox.

Let me know if you have any questions.


I think I might be getting to the bottom of this. I think the flaw is the way you're handling the $.load() of the new content when clicking a menu item, coupled with an uncaught exception I saw having to do with an iframe:

Uncaught exception: Unknown player iframe

This Nabble-Shadowbox forum thread deals with this error. I'm actually not getting that anymore, however I think it came up with I clicked on the tour menu item.

Now, what you're doing to load the content for the menu items really doesn't make any sense. You're requesting an entire HTML document, and then selecting just an element with a class="content". The only benefit I can see for doing this is that the page never reloads, but you need to take another approach to how to get and display the data that doesn't involve downloading the entire page through AJAX and then trying to get jQuery to parse out just the part you want.

I believe handling the content loading this way is the root cause of your problem, hence the $.load() toggling of menu views breaks your page in unexpected ways.

Question: Why don't you just link to the actual page and skip all the $.load() fanciness? Speed-wise, it won't make that much of an impact, if any at all. It just doesn't make sense to use AJAX like this, when you could just link them to the same content without issue.

There are two alternatives that would allow you to prevent roundtrip page reloads:

  1. Setup your AJAX calls to only request the .content portion of the markup if you have the ?contentonly=true flag in the URL, not the entire HTML document. This is how it's traditionally done, and is usually relative simple to do if you have a scripting environment.

    $(".content").load('index.html?contentonly=true');
    

    Then your server responds only with the content view requested.

  2. Serve all of the content views within the same HTML document, then show as appropriate:

    var $content = $('.content');
    $content.find('.content-view').hide();
    $content.find('#services-content').show();
    

    It doesn't look like you have a whole lot of content to provide, so the initial page load probably won't have that much of an impact with this particular approach. You might have to look into how to preload images, but that's a very well known technique with many quality scripts and tutorials out there.

Either one of these techniques could use the #! (hashbang) technique to load content, although I believe there are some issues with this for search engines. However, here is a link to a simple technique I put together some time ago:

http://jfcoder.com/test/hash.html

Also, this is just a tip, but don't refer to your "content" element with a class, ie, .content. There should only be one content-displaying element in the markup, right? There's not more than one? Use an id="content"; that's what ID attributes are for, to reference a single element. classes are meant to group elements by some characteristic they share, so above when I .hide() the inline content views (see #2), I look for all of the class="content-view" elements, which are all similar (they contain content view markup). But the $content variable should refer to $('#content');. This is descriptive of what the elements are.

Jared Farrish
  • 48,585
  • 17
  • 95
  • 104
  • I would rather link the actual pages, but that's not in the requirements. The client wants the content to change without the page reloading. I'm requesting an entire HTML page and selecting the content from it because I was thinking of putting the actual link in the menu items' href, so if some user's browser doesn't support javascript, it would still work. – Igor Feb 05 '12 at 00:05
  • AJAX isn't going to fall through to a link-like behavior, it'll not do anything (including not triggering a full page load). You need to accommodate responding back with either the full HTML document (regular GET roundtrip request, like a link was clicked), or just the fragment to display (the content part) during an AJAX request. You can accomplish this with a GET flag on the AJAX; just omit the flag from the `href` attribute URL, so it will fall through to the full page. Then, on the server script, detect whether you need to respond with either the full HTML document or just the content. – Jared Farrish Feb 05 '12 at 00:07
  • View [this demo](http://www.asp.net/ajaxLibrary/jQueryCodeSamplesWebForms/jQueryLoad/MainContentForm.aspx) with Firebug or Chrome's Net console open, and when the request is made for the page fragment, view what the Response text was. You'll see it's just an HTML fragment, not a full document. – Jared Farrish Feb 05 '12 at 00:15
  • Also note that it's considered a responsibility of the OP (you, in this case) to review the answers(s) provided and if appropriate, upvote any good answers and check one (1) answer as the accepted answers (it's an outlined checkmark next to the answer, click it to accept the adjacent answer). This is how you reward the people who have taken the time to answer your questions; the points are "rep". – Jared Farrish Feb 05 '12 at 00:21
  • No problem. It was an interesting issue to deal with; took me a while to clue in on it. – Jared Farrish Feb 05 '12 at 00:26
  • @JaredFarish I like the first alternative better and first thing I did was change the 'content' to ID instead of class and now the $.load() function doesn't work. Nothing happens when I click on the menu item links. I hate javascript :). Help! – Igor Feb 05 '12 at 10:30
  • Anywhere where you were referencing `.content` (including in your CSS) needs to be updated to be `#content`. I see you still have `$(".content")` in the `$(document).ready()` that sets the `.load()` click handlers that then select the `.content` element. This should be `$("#content")`. – Jared Farrish Feb 05 '12 at 10:35
  • That was weird, I was sure I updated the files on the server. One more thing. I wanna try the first alternative you suggested. What would be in the $.load('') in my case? – Igor Feb 05 '12 at 10:57
  • The URL to the page that will give you the content you're requesting (it can be another page that runs a server-side script to detect which content to return, or can be something along the lines like in my example, where you use `services.html?contentonly=true`, which will tell that page's script to send only the content for the `services.html`, not the full HTML document. – Jared Farrish Feb 05 '12 at 11:01
  • to skip that I tried removing everything from the services.html, but the content-holder div. Edited the $.load to $("#content").load('services.html') and the shadows box still doesn't work. You think that a proper test replacement for your suggestion? – Igor Feb 05 '12 at 11:10
  • So you're clicking on services, then back to home to try the Shadowbox? I see the `home` menu item retrieves a full HTML document. You would need to switch out all of the pages to load to properly test it. So what you did with the `services` menu item `$.load()`, do that for the `home` menu item too. – Jared Farrish Feb 05 '12 at 11:16
  • Just did, still the same. I guess there is only the second alternative to try? – Igor Feb 05 '12 at 12:02
  • @user1188712 - Ha ha! Finally. Try this demo while I update the answer: http://jfcoder.com/test/shadowbox.html – Jared Farrish Feb 05 '12 at 12:20
  • @user1188712 - Note, I'm going to try something, so if you experience issues over the next few minutes, just wait until I notify you I'm done. It shouldn't take long. – Jared Farrish Feb 05 '12 at 12:29
  • Unless you're in a hurry to get it, I think you'll appreciate what I'm working on. I already know how to fix it, I'm just working on how it's all setup. I'm also having fun working with it. I should be done here not too long, next ten minutes or so. – Jared Farrish Feb 05 '12 at 14:20
  • Take your time I'm not in a hurry – Igor Feb 05 '12 at 14:40
  • Still working, but I thought you might find this useful: http://www.w3.org/TR/html4/struct/global.html#h-7.5.2 Note how they describe the meaningfulness of `id` and `class` attributes. – Jared Farrish Feb 05 '12 at 15:05
  • @user1188712 - Ok, I believe I've got it all worked out. Try this link again: http://jfcoder.com/test/shadowbox.html I've got to go run some errands etc., so I'll get the answer hammered out later on. Just be aware, I've changed it quite a bit, and probably need to explain it to you. – Jared Farrish Feb 05 '12 at 17:37
  • Thanks a lot but seems like it still doesn't work. I hope we can hammer it out when u get back. – Igor Feb 05 '12 at 17:52
  • @user1188712 - No, I accidentally switched the home and services files (and I just fixed it). It's working for me now, including the Shadowbox, when you click from services to tour and back home. – Jared Farrish Feb 05 '12 at 17:55
  • did you save it? It still shows the home tab under services when I open it. – Igor Feb 05 '12 at 17:59
  • Do `ctrl+r` or try to view the actual content page links and do `ctrl+r`. They're cached in the browser; they did the same thing to me for some reason. – Jared Farrish Feb 05 '12 at 18:06
  • Sorry still opens the image in a new window. – Igor Feb 05 '12 at 18:55
  • What browser/version? Works fine in FF9. – Jared Farrish Feb 05 '12 at 19:37
  • @user1188712 - Also make sure you're not inadvertently back on your real site. I did that several times; they look literally identical. – Jared Farrish Feb 05 '12 at 19:41
  • I guess it takes some time for the browser to clear it's cache. It works now. Thanks, I'll try to figure out how it works now. – Igor Feb 05 '12 at 19:43
  • On IE9, Chrome latest and Firefox 9, they all work as expected. You let the page load (http://jfcoder.com/test/shadowbox.html), click on the services link in the menu, then go back to home, and when you click on the compass, you get redirected to the image instead of the inline box? – Jared Farrish Feb 05 '12 at 19:45
  • I've got about half of the edit done; I'm trying to describe what I did. It should be too much longer for that. – Jared Farrish Feb 05 '12 at 19:47
  • Any way to further increase your reputation, other than choosing the answer? – Igor Feb 05 '12 at 19:49
  • @user1188712 - I added the updated answer. Let me know if you have any questions or run into any issues. And as far as your comment above, not that I know of. – Jared Farrish Feb 05 '12 at 20:45
  • Wooooow! what a long answer. just these two lines fixed my problem. `Shadowbox.clearCache(); Shadowbox.setup();` Thanks a lot. – Milad.Nozari Oct 04 '14 at 12:30
1

This worked for us, we made a site that used vertical tabs and called in the pages with our shadowbox images using jQuery.load

Just give all of your anchor tags the class="sbox" and paste this script in the header.

<script>
    Shadowbox.init({
    skipSetup:true,
}); 
$(document).ready(function() {
    Shadowbox.setup($('.sbox'));//set up links with class of sbox
    $('a.sbox').live('click',function(e){
        Shadowbox.open(this);
        //Stops loading link
        e.preventDefault();
    });
});
</script>

Note: we had to put the .sbox class on all our rel="shadowbox" anchors as well as the on the anchor for the tab that called the .load

Thanks to this guy-> http://www.webmuse.co.uk/blog/shadowbox-ajax-and-other-generated-content-with-jquery-and-javascript/

Shem
  • 11
  • 1
0

Well, based on Shem's answer, this is my solution. Every click on specific class, setup and open shadowbox with elements from same class:

jQuery('.sb-gallery a').live('click',function(e){
    Shadowbox.setup(jQuery('.sb-gallery a'));
    Shadowbox.open(this);
    //Stops loading link
    e.preventDefault();
});

Thanks to all