For one of our current projects (Aiko) we are building a site influenced by the Bloomberg L.P., company site, by Frog Design and the website, Pinterest. The design dictates that most of the site content is presented using a dropdown menu approach. Normally, a dropdown functions best when all of the content is already loaded on the page, and just relies on javascript to animate the display and hiding of each dropdown element. For this design, it is pretty obvious that requiring all of the overlay content to load along with the initial page is excessive for both load times, as well as complicated for the CMS organization and structure.
AJAX to the rescue. By loading each of the individual content nodes "on demand" we can cut the unnecessary burden of loading everything up front, and only load the content that is actually requested by the visitor. Now the question becomes... how do we best do this in Drupal 7?
My solution involves the following approach:
- Add a class selector to all links that are meant to open in the overlay (class="overlay").
- Use jQuery to find all of these links in the document, and attach the click behavior of loading href into the desired overlay element via ajax, animate the effects, and return false to avoid firing the actual link to a separate page.
- Normally the link destination would load the entire page all over again within the overlay element, thus creating an infinite nesting problem (as well as breaking the design). This is avoided by asking drupal to load these special "overlay" designated links in a different html.tpl.php and page.tpl.php so we can control which parts of the surrounding page are loaded into the overlay. This is done using the theme's template.php preprocess functions and altering the href sent through ajax.
- We also want the url to correctly reflect which overlay page is currently open, and allow direct linking to a specific overlay. This is handled with jQuery/javascript and dynamic updating of the page hashtag. Additionally, when a page is first loaded an existing hash is parsed to determine what to display immediately.
- Lastly, each overlay may contain a slideshow of content. If so, we also include this additional information in the hash to also allow for direct linking.
The following modules were used to implement this solution:
menu attributes: to add the class designation to selected menu links
CODE
mytheme/template.php
<?php
function mytheme_preprocess_page(&$vars) {
if ( isset($_GET['overlay']) && $_GET['overlay'] == 'true' ) {
$vars['theme_hook_suggestions'][] = 'page__overlay';
}
}
function mytheme_preprocess_html(&$vars) {
if ( isset($_GET['overlay']) && $_GET['overlay'] == 'true' ) {
$vars['theme_hook_suggestions'][] = 'html__overlay';
}
}
?>
mytheme/script.js
// JavaScript Document
jQuery(document).ready(function () {
// set overlay-wrap to be hidden from view
jQuery('#overlay-wrap').css('height', '0px');
// find all overlay links and add overlay functionality
jQuery('a.overlay').each( function() {
jQuery(this).click( function() {
var href=jQuery(this).attr('href'); // page url to load
var name=jQuery(this).attr('name'); // new page name
var rel =jQuery(this).attr('rel'); // new page group
updateHash(name);
loadPage(href);
updateActive(href, rel);
return false; // important to keep the page from loading in a new window
});
});
// If hashtag exists, load the appropriate page and slide on page load.
if(hash = parseHash()) {
if(hash['page']) {
var href = jQuery('a[name="'+hash['page']+'"]').attr('href');
var rel = jQuery('a[name="'+hash['page']+'"]').attr('rel');
var slide = hash['slide'];
loadPage(href, rel, slide);
updateActive(href, rel);
}
}
});
/* UTILITY FUNCTIONS FOR OVERLAY MANAGEMENT */
// function to update which link is active for styling purposes
function updateActive(href, rel) {
// remove active from all links
jQuery('a.active').removeClass('active');
// add active to all links with same destination
jQuery('a[href= "'+href+'"]').addClass('active');
// add active to main level link regardless of destination
jQuery('#main-menu a[name="'+rel+'"]').addClass('active');
}
// function to update the Hash after the content has changed
function updateHash(page, slide) {
var list = 'page='+page; // add page hash
if (slide) list += '&slide='+slide; // add slide hash if exists
window.location.hash = list; // update hash
}
// function to load the content via ajax and animate the overlay
function loadPage(href, rel, slide) {
// fade out current content
jQuery('#overlay').stop(true,true).fadeTo('fast', 0, function() {
// determine if overlay is already open, if not animate it immediately
if (!jQuery('#overlay-wrap').height()) {
jQuery('#overlay-wrap').stop(true,true).animate({ height: 200 }, 'slow');
}
/**
* NOTE: the additional '?overlay=true' is added to trigger the new
* templates in the template.php file
**/
jQuery('#overlay').load( href+'?overlay=true', function () {
if (slide) jQuery('#'+slide).click(); // cycle to correct slide
// animate the height to fit the new content (within callback after load)
jQuery('#overlay-wrap').stop(true,true).animate({
height: jQuery('#overlay').height()+10
}, 'fast', function() {
jQuery('#overlay').stop(true,true).fadeTo('fast', 1.0); // fade in
});
});
});
}
// function to extract data from the existing hash
function parseHash() {
hash = window.location.hash.substring(1); // load hash string
if (!hash) return false; // return false if no hash
var varlist= hash.split("&"); // break hash into variables
for (i=0; i < varlist.length; i++) { // cycle through variables
var vars = varlist[i].split("="); // split variable name from value
varlist[vars[0]] = vars[1]; // assign variable value to array
// indexed by variable name
}
return varlist; // return variable array
}
// function to close the overlay
function closePage() {
jQuery('#overlay').stop(true,true).fadeTo('fast', 0, function() {
jQuery('#overlay-wrap').stop(true,true).animate({
height: 0
}, 'fast', function() {
jQuery('#overlay').html('');
jQuery('a.active').removeClass('active');
window.location.hash = '';
});
});
}
mytheme/html--overlay.tpl.php
<div id="overlay-close"></div>
<?php print $page; ?>
<script type="text/javascript">
// close button
jQuery('#overlay-close').click(function() { closePage() });
</script>
mytheme/page--overlay.tpl.php
<div class="overlay-content">
<?php print render($page['content']); ?>
</div>
mytheme/page.tpl.php (add this code where you want the modal overlay to be located)
<div id="overlay-wrap"><div id="overlay"></div></div>