3

I am facing a problem that I am sure is pretty common. I found many solutions to this problem, and they all have their pros and cons. I'll post what I found here (which I believe will be useful to others), and I hope you'll point me in the right direction.

Essentially, this is my scenario:

  1. I have a webpage in PHP: http://example.com/page_with_content_a_or_b.php.
  2. This page returns Content A when no POST parameters are specified, and Content B if there are.
  3. Assume a user connects to my page typing the previous URL in her browser (a GET request).
  4. My server returns the page with Content A.
  5. The user's web browser, via JavaScript, decides to replace Content A with Content B.

The question: How does the JavaScript replace the contents?

Well, as I've said, I've been looking for different solutions, but none seems perfect.

In order to discuss each possible solution, let me introduce you the resulting HTML code of each version:

HTML of Content A

<html>
  <head>
     <link rel="stylesheet" type="text/css" href="style_A.css">
     <script type="text/javascript" src="jquery.min.js"></script>
     <script type="text/javascript" src="gallery-animator.js"></script>
  </head>
  <body>
     <div class="gallery"><!-- images here --></div>
     <p>Content A</p>
     <script type="text/javascript" src="content-b-loader.js"></script>
  </body>
</html>

HTML of Content B

<html>
  <head>
     <link rel="stylesheet" type="text/css" href="style_B.css">
     <script type="text/javascript" src="jquery.min.js"></script>
     <script type="text/javascript" src="gallery-animator.js"></script>
  </head>
  <body>
     <div class="gallery"><!-- images here --></div>
     <p>Content B</p>
  </body>
</html>

Differences between both versions

As you can see, in the example both versions are quite similar, but not identical. In general, these are the differences I might encounter:

  • All or some or none imported stylesheets may be different.
  • All or some or none imported javascripts may be different.
  • There might be differences with inline stylesheets and/or javascripts.
  • The content is different, but it may differ a little bit only or be completely different.
  • Content B does not includes the script for loading itself (last script in Content A).

Possible Solutions

Replacing Content with document.open(), document.write(), and document.close()

The first solution I implemented is the following:

content-b-loader.js (option 1)

$(document).ready(function() {
    $.ajax({
        type:  'POST',
        url:   window.location.href,
        data: { load_content_b: 'true' },
        success: function( html ) {
            document.open();
            document.write(html);
            document.close();
        }
    });
});

Apparently, the solution works properly. However, there are situations in which I have problems. In my example, for instance, both contents load a script named gallery-animator.js. Assuming this script is the following:

gallery-animator.js

var galleryInterval = setInterval(function() {
   // Fade out current image and fade in the next one
   $("body > div.gallery > img")...
}, 5000);

after executing the script in content-b-loader.js there are two timeouts animating the gallery. As a result, the animation looks like a mess (two images moving at the same time, or not working at all).

It looks like the sequence document.open(), document.write(html), and document.close() does not stop and replace the original scripts.

Redirecting with POST data (using a form)

Another solution is doing a redirection described in this previous question. The solution works like a charm: on the one hand, I load the URL I need to load with the required POST data, which means I'll get Content B, and on the other hand, Content B is loaded in a "new page", which means that the scripts loaded by Content A are no longer there.

The problem here? If I refresh the page with Content B, I get a warning stating that "POST data is about to be resubmitted". This is undesirable, because it looks confusing for the user (I don't want her to know she had been redirected to a new page).

I know there's a solution called PRG (Post-Redirect-Get) which avoids this particular issue. However, it requires Content B to be accessible using a GET request (using GET params or COOKIES, neither of which I can use).

Using iframes

The last solution I've found is also interesting. Basically, it hides (or empties) the body from Content A, adds an iframe in the page, and loads Content B inside it:

content-b-loader.js (option 3)

$(document).ready(function() {
    $.ajax({
        type:  'POST',
        url:   window.location.href,
        data: { load_content_b: 'true' },
        success: function( html ) {
            $("body").css('', ''); // Remove all paddings and margins
            $("body").empty();
            $("body")append('<iframe id="content" seamless="seamless"' +
                'src="anchor.html"></iframe>');
            // Initialize vars in anchor.html and call the redirect functions
            document.getElementById('content').contentWindow.url = window.location.href;
            document.getElementById('content').contentWindow.options = {
                load_content_b: 'true'
            };
            document.getElementById('content').contentWindow.redirect();
        }
    });
});

anchor.html

This page implements the second solution (it uses a POST redirect).

<html>
    <head>
        <script type="text/javascript" src="jquery.min.js"></script>
        <script type="text/javascript" src="jquery.redirect.min.js"></script>
        <script type="text/javascript">
            var url;
            var options;
            function redirect(){
                $().redirect(url, options, 'POST');
            }
        </script>
    </head>
    <body></body>
</html>

By making the iframe as big as the window view, I can show the alternative content. If the user refreshes the webpage, the page that is refreshed is the "container" page (the one that originally had Content A), so no warnings appear at all.

The problems I am facing with this solution, however, are:

  • Users can disable iframes. How do I detect whether iframes are enabled or disabled?
  • When the user clicks a link in the frame (or submits a form), the "new page" is opened inside the iframe. This is not what I want: I want it to be opened in the main window. How do I do this? I know that there is the base directive for links... but what about forms? And JavaScript code that performs a redirection?
  • Does everything work properly inside iframes? Responsive themes, javascripts, ... As far as I can tell, they do, but I ignore whether users can limit what iframes can do.

TL;DR - Conclusions

I have a page http://example.com/page_with_content_a_or_b.php. The page returns Content A when accessed using a GET request, and returns Content B when accessed using POST. When user types the URL, she gets Content A, and Content B is loaded using JavaScript.

All solutions entail problems:

  • With document.open(), document.write(), document.close(), scripts get messed.
  • With POST redirections, refreshing the page popups a warning
  • With iframes, they are not always available, and I am somehow "stucked" inside them.

Any ideas? Am I missing something? Is there a preferred way to do what I'm trying to do?

Thank you very much!

Community
  • 1
  • 1
David
  • 33
  • 4
  • While I appreciate the amount of effort spent on this post, I don't feel like I fully understand the problem this is trying to solve. You listed 5 steps in the beginning of the question, but could you go back and edit those to show if any of those are requirements that you must adhere to? I feel like jumping into part two (alternate css stylesheets) and then part three (ajax requests) and part four (whoa, iframes!?) don't address the real problem, but I'm not sure I know what the problem is. Could you tag numbers 1-5 with "required by legacy code" or "I want it to do this" and "wish list" etc? – brock Mar 20 '14 at 00:44
  • A jsfiddle would be immensely helpful, even if it is a stripped down version of the problem. – brock Mar 20 '14 at 00:45

3 Answers3

1

There's a hacky fix for the problem with messy script behavior in your first solution. Assuming that the script-related problem only occurs when using timeouts (I don't know if there are other scenarios in which things can go wrong....), there's an easy way to clear all timeouts. Just add this code:

var id = window.setTimeout(function() {}, 0);
while (id--)
    window.clearTimeout(id);
Community
  • 1
  • 1
avillegasn
  • 76
  • 7
  • Thanks! This looks like a good workaround for the timeouts problem. I'll look into it! Nonetheless, I'd like to know what other people think about the different possible solutions... I just want to make sure that we choose the most appropriate solution ;-) – David Mar 18 '14 at 16:48
0

A POST request should do things which change or affect data. Thus it makes sense to prevent the user from refreshing and that should be desirable behavior, I am confused whether you're doing an actual post request or otherwise why is redirecting to a POST page is so problematic?

Either way, regarding your <iframe> solution, let me give some suggestions. "Disabling iframes" is indeed technically possible on some browsers, but to do such a thing you have to either dive in about:config or very deep in the IE menu and it's entirely impossible on Chrome by default. So yeah, truth be said, I would not concern myself with that whatsoever, just like nowadays most sites do not concern themselves with users who disable javascript (any user with disabled iframes will probably have javascript disabled as well btw), simply because it's too rare to concern yourself with and it's one of those choices where you have to face the consequences yourself. Now, I was planning on directing you to the <base> tag as well, just realizing now that you already mentioned it in your post. Either way, the tag will work for forms as well, but not for javascript 'links'.

All in all I would advice you to rethink whether the page should be POST, if so, then you should just go with the warning and otherwise rework it to be just another GET page.

David Mulder
  • 26,123
  • 9
  • 51
  • 114
  • Thanks for your quick response! In my example, there was only `Content A` and `Content B`. In reality, however, there might be "more contents", and I need a `POST` to ask for the concrete content. In general, this would be achieved using `COOKIES`, but I cannot rely on them... Therefore, I have to send this information using either a `POST` param or a `GET` param. I chose a `POST` param because I don't want the user to know that she is loading an alternative content. – David Mar 18 '14 at 13:13
  • Oh! I forgot to mention: would you recommend using iframes, then? I think they are the best option among the three I mentioned, but I am not sure enough... And, moreover, I cannot assume they'll work everywhere; I need to know when iframes are disabled. – David Mar 18 '14 at 13:18
  • If you are requesting different content you should just add a flag like `?all` or `?content=b` to request `Content B`. A URI should uniquely identify content and a GET should request that content, a POST request should only acknowleding the result of the action. – David Mulder Mar 18 '14 at 13:38
0

I guess I'm missing something, because I don't clearly understand what do you mean by "The user's web browser, via JavaScript, decides to replace Content A with Content B", but:

If you know in advance the parameters of the elements whose content you'd like to replace, wouldn't a simple case by case replacement work?

<html>
  <head>
     <link rel="stylesheet" type="text/css" href="style_A.css">
  </head>
  <body>
     <div class="gallery"><!-- images here --></div>
     <p class="content">Content A</p>
  </body>

  <!-- This could be content-b-loader.js -->
  <script>
    var $stylesheet = document.getElementsByTagName('link')[0];
        $content = document.querySelectorAll('.content')[0];

    if ( $stylesheet && $content ) {
      if ( $stylesheet.getAttribute('href') == 'style_A.css'  ) {
        $stylesheet.setAttribute('href', 'style_B.css');
      }

      $content.innerHTML = 'Content B';
    }
  </script>
</html>
  • I know that what I'm trying to do is somehow strange... but I don't know how to explain it clearer! Your approach is interesting, but it does not solve my real problem (it is not as easy as replacing one single .css file with another). Thanks for the tip, though! – David Mar 25 '14 at 14:14