summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAngie Byron <webchick@24967.no-reply.drupal.org>2010-01-06 04:03:39 +0000
committerAngie Byron <webchick@24967.no-reply.drupal.org>2010-01-06 04:03:39 +0000
commit9a79135333aa3017d77546edc6645d1ff490c762 (patch)
treeac7fb5a44ccb3bc8582e35fd5a815765ce1aafed
parentdb4fa95f375627a309a97bf468a2eb44ebd2d4b8 (diff)
downloadbrdo-9a79135333aa3017d77546edc6645d1ff490c762.tar.gz
brdo-9a79135333aa3017d77546edc6645d1ff490c762.tar.bz2
#668104 by casey and David_Rothstein: Make overlay respect other click handlers.
-rw-r--r--modules/overlay/overlay-child.js59
-rw-r--r--modules/overlay/overlay-parent.js320
-rw-r--r--modules/overlay/overlay.module6
-rw-r--r--modules/toolbar/toolbar.js15
4 files changed, 229 insertions, 171 deletions
diff --git a/modules/overlay/overlay-child.js b/modules/overlay/overlay-child.js
index e3e1cb933..93ae1f34a 100644
--- a/modules/overlay/overlay-child.js
+++ b/modules/overlay/overlay-child.js
@@ -87,54 +87,23 @@ Drupal.overlayChild.behaviors.scrollToTop = function (context, settings) {
};
/**
- * Modify links and forms depending on their relation to the overlay.
+ * Capture and handle clicks.
*
- * By default, forms and links are assumed to keep the flow in the overlay.
- * Thus their action and href attributes respectively get a ?render=overlay
- * suffix. Non-administrative links should however close the overlay and
- * redirect the parent page to the given link. This would include links in a
- * content listing, where administration options are mixed with links to the
- * actual content to be shown on the site out of the overlay.
- *
- * @see Drupal.overlay.isAdminLink()
+ * Instead of binding a click event handler to every link we bind one to the
+ * document and handle events that bubble up. This also allows other scripts
+ * to bind their own handlers to links and also to prevent overlay's handling.
*/
-Drupal.overlayChild.behaviors.parseLinks = function (context, settings) {
- var closeAndRedirectOnClick = function (event) {
- // We need to store the parent variable locally because it will
- // disappear as soon as we close the iframe.
- var parentWindow = parent;
- if (parentWindow.Drupal.overlay.close(false)) {
- parentWindow.Drupal.overlay.redirect($(this).attr('href'));
- }
- return false;
- };
- var redirectOnClick = function (event) {
- parent.Drupal.overlay.redirect($(this).attr('href'));
- return false;
- };
-
- $('a:not(.overlay-exclude)', context).once('overlay', function () {
- var href = $(this).attr('href');
- // Skip links that don't have an href attribute.
- if (href == undefined) {
- return;
- }
- // Non-admin links should close the overlay and open in the main window.
- else if (!parent.Drupal.overlay.isAdminLink(href)) {
- $(this).click(closeAndRedirectOnClick);
- }
- // Open external links in a new window.
- else if (href.indexOf('http') > 0 || href.indexOf('https') > 0) {
- $(this).attr('target', '_new');
- }
- // Open admin links in the overlay.
- else {
- $(this)
- .attr('href', parent.Drupal.overlay.fragmentizeLink(this))
- .click(redirectOnClick);
- }
- });
+Drupal.overlayChild.behaviors.addClickHandler = function (context, settings) {
+ $(document).bind('click.overlay-event', parent.Drupal.overlay.clickHandler);
+};
+/**
+ * Modify forms depending on their relation to the overlay.
+ *
+ * By default, forms are assumed to keep the flow in the overlay. Thus their
+ * action attribute get a ?render=overlay suffix.
+ */
+Drupal.overlayChild.behaviors.parseForms = function (context, settings) {
$('form', context).once('overlay', function () {
// Obtain the action attribute of the form.
var action = $(this).attr('action');
diff --git a/modules/overlay/overlay-parent.js b/modules/overlay/overlay-parent.js
index 526540262..713f4c430 100644
--- a/modules/overlay/overlay-parent.js
+++ b/modules/overlay/overlay-parent.js
@@ -4,61 +4,28 @@
/**
* Open the overlay, or load content into it, when an admin link is clicked.
+ *
+ * http://docs.jquery.com/Namespaced_Events
*/
Drupal.behaviors.overlayParent = {
attach: function (context, settings) {
- var $window = $(window);
-
- // Alter all admin links so that they will open in the overlay.
- $('a', context).filter(function () {
- return Drupal.overlay.isAdminLink(this.href);
- })
- .once('overlay')
- .each(function () {
- // Move the link destination to a URL fragment.
- this.href = Drupal.overlay.fragmentizeLink(this);
- });
-
- // Simulate the native click event for all links that appear outside the
- // overlay. jQuery UI Dialog prevents all clicks outside a modal dialog.
- $('.overlay-displace-top a:not(.overlay-displace-no-click)', context)
- .add('.overlay-displace-bottom a:not(.overlay-displace-no-click)', context)
- .click(function () {
- window.location.href = this.href;
- });
-
- // Resize the overlay when the toolbar drawer is toggled.
- $('#toolbar a.toggle', context).once('overlay').click(function () {
- setTimeout(function () {
- // Resize the overlay, if it's open.
- if (Drupal.overlay.isOpen) {
- Drupal.overlay.outerResize();
- }
- }, 10);
- });
-
// Make sure the onhashchange handling below is only processed once.
if (this.processed) {
return;
}
this.processed = true;
+ // Bind event handlers to the parent window.
+ $(window)
// When the hash (URL fragment) changes, open the overlay if needed.
- $window.bind('hashchange', function (e) {
- // If we changed the hash to reflect an internal redirect in the overlay,
- // its location has already been changed, so don't do anything.
- if ($.data(window.location, window.location.href) === 'redirect') {
- $.data(window.location, window.location.href, null);
- }
- // Otherwise, change the contents of the overlay to reflect the new hash.
- else {
- Drupal.overlay.trigger();
- }
- });
-
+ .bind('hashchange.overlay-event', Drupal.overlay.hashchangeHandler)
// Trigger the hashchange event once, after the page is loaded, so that
// permalinks open the overlay.
- $window.trigger('hashchange');
+ .trigger('hashchange.overlay-event');
+ // Instead of binding a click event handler to every link we bind one to the
+ // document and only handle events that bubble up. This allows other scripts
+ // to bind their own handlers to links and also to prevent overlay's handling.
+ $(document).bind('click.overlay-event', Drupal.overlay.clickHandler);
}
};
@@ -187,7 +154,7 @@ Drupal.overlay.create = function () {
$body.addClass('overlay-open');
// Adjust overlay size when window is resized.
- $window.bind('resize', delayedOuterResize);
+ $window.bind('resize.overlay-event', delayedOuterResize);
if (self.options.autoFit) {
$body.addClass('overlay-autofit');
@@ -228,7 +195,7 @@ Drupal.overlay.create = function () {
self.isClosing = true;
// Stop all animations.
- $window.unbind('resize', delayedOuterResize);
+ $window.unbind('resize.overlay-event', delayedOuterResize);
clearTimeout(self.resizeTimeoutID);
};
@@ -241,6 +208,8 @@ Drupal.overlay.create = function () {
// When the iframe is still loading don't destroy it immediately but after
// the content is loaded (see self.load).
if (!self.isLoading) {
+ // As the iframe is being removed we need to remove all load handlers, not
+ // just the ones namespaced with overlay-event.
self.$iframe.unbind('load');
self.destroy();
}
@@ -321,7 +290,7 @@ Drupal.overlay.load = function (url) {
self.$dialog.removeClass('overlay-loaded');
self.$iframe
.css('visibility', 'hidden')
- .load(function () {
+ .bind('load.overlay-event', function () {
self.isLoading = false;
// Only continue when overlay is still open and not closing.
@@ -413,6 +382,18 @@ Drupal.overlay.bindChild = function (iframeWindow, isClosing) {
// Make sure the parent window URL matches the child window URL.
self.syncChildLocation(iframeWindow.document.location);
+ // Unbind the mousedown handler installed by ui.dialog because the
+ // handler interferes with use of the scroll bar in Chrome & Safari.
+ // After unbinding from the document we bind a handler to the dialog overlay
+ // which returns false to prevent event bubbling.
+ // @see http://dev.jqueryui.com/ticket/4671
+ // @see https://bugs.webkit.org/show_bug.cgi?id=19033
+ // Do the same for the click handler as prevents default handling of clicks in
+ // displaced regions (e.g. opening a link in a new browser tab when CTRL was
+ // pressed while clicking).
+ $(document).unbind('mousedown.dialog-overlay click.dialog-overlay');
+ $('.ui-widget-overlay').bind('mousedown.dialog-overlay click.dialog-overlay', function (){return false;});
+
// Reset the scroll to the top of the window so that the overlay is visible again.
window.scrollTo(0, 0);
@@ -429,15 +410,12 @@ Drupal.overlay.bindChild = function (iframeWindow, isClosing) {
// If the shortcut add/delete button exists, move it to the dialog title.
var $addToShortcuts = self.$iframeWindow('.add-or-remove-shortcuts');
if ($addToShortcuts.length) {
- // Make the link overlay-friendly.
- var $link = $('a', $addToShortcuts);
- $link.attr('href', Drupal.overlay.fragmentizeLink($link.get(0)));
// Move the button markup to the title section. We need to copy markup
// instead of moving the DOM element, because Webkit and IE browsers will
// not move DOM elements between two DOM documents.
- var shortcutsMarkup = '<div class="' + $($addToShortcuts).attr('class') + '">' + $($addToShortcuts).html() + '</div>';
- self.$dialogTitlebar.find('.ui-dialog-title').after(shortcutsMarkup);
- self.$iframeWindow('.add-or-remove-shortcuts').remove();
+ $addToShortcuts = $(self.$iframeWindow('<div>').append($addToShortcuts).remove().html());
+
+ self.$dialogTitlebar.find('.ui-dialog-title').after($addToShortcuts);
}
// Remove any existing tabs in the title section.
@@ -448,7 +426,7 @@ Drupal.overlay.bindChild = function (iframeWindow, isClosing) {
// Move the tabs markup to the title section. We need to copy markup
// instead of moving the DOM element, because Webkit and IE browsers will
// not move DOM elements between two DOM documents.
- $tabs = $(self.$iframeWindow('<div>').append($tabs.clone()).remove().html());
+ $tabs = $(self.$iframeWindow('<div>').append($tabs).remove().html());
self.$dialogTitlebar.append($tabs);
if ($tabs.is('.primary')) {
@@ -545,7 +523,7 @@ Drupal.overlay.bindChild = function (iframeWindow, isClosing) {
clearTimeout(self.resizeTimeoutID);
self.resizeTimeoutID = setTimeout(delayedResize, 150);
}
-
+
// Scroll to anchor in overlay. This needs to be done after delayedResize().
if (iframeWindow.document.location.hash) {
window.scrollTo(0, self.$iframeWindow(iframeWindow.document.location.hash).position().top);
@@ -582,22 +560,7 @@ Drupal.overlay.unbindChild = function (iframeWindow) {
*/
Drupal.overlay.isAdminLink = function (url) {
var self = this;
- // Create a native Link object, so we can use its object methods.
- var link = $(url.link(url)).get(0);
- var path = link.pathname;
- // Ensure a leading slash on the path, omitted in some browsers.
- if (path.substr(0, 1) != '/') {
- path = '/' + path;
- }
- path = path.replace(new RegExp(Drupal.settings.basePath), '');
- if (path == '') {
- // If the path appears empty, it might mean the path is represented in the
- // query string (clean URLs are not used).
- var match = new RegExp("(\\?|&)q=(.+)(&|$)").exec(link.search);
- if (match && match.length == 4) {
- path = match[2];
- }
- }
+ var path = self.getPath(url);
// Turn the list of administrative paths into a regular expression.
if (!self.adminPathRegExp) {
@@ -688,39 +651,126 @@ Drupal.overlay.outerResize = function () {
};
/**
- * Add overlay rendering GET parameter to the given href.
+ * Click event handler.
+ *
+ * Instead of binding a click event handler to every link we bind one to the
+ * document and handle events that bubble up. This allows other scripts to bind
+ * their own handlers to links and also to prevent overlay's handling.
+ *
+ * This handler makes links in displaced regions work correctly, even when the
+ * overlay is open.
+ *
+ * This click event handler should be bound any document (for example the
+ * overlay iframe) of which you want links to open in the overlay.
+ *
+ * @see Drupal.overlayChild.behaviors.addClickHandler
*/
-Drupal.overlay.addOverlayParam = function (href) {
- return $.param.querystring(href, {'render': 'overlay'});
- // Do not process links with an empty href, or that only have the fragment or
- // which are external links.
- if (href.length > 0 && href.charAt(0) != '#' && href.indexOf('http') != 0 && href.indexOf('https') != 0) {
- var fragmentIndex = href.indexOf('#');
- var fragment = '';
- if (fragmentIndex != -1) {
- fragment = href.substr(fragmentIndex);
- href = href.substr(0, fragmentIndex);
+Drupal.overlay.clickHandler = function (event) {
+ var self = Drupal.overlay;
+
+ var $target = $(event.target);
+
+ if (self.isOpen && $target.closest('.overlay-displace-top, .overlay-displace-bottom').length) {
+ // Click events in displaced regions could potentionally change the size of
+ // that region (e.g. the toggle button of the toolbar module). Trigger the
+ // resize event to force a recalculation of overlay's size/position.
+ $(window).triggerHandler('resize.overlay-event');
+ }
+
+ // Only continue by left-click or right-click.
+ if (!(event.button == 0 || event.button == 2)) {
+ return;
+ }
+
+ // Only continue if clicked target (or one of its parents) is a link and does
+ // not have class overlay-exclude. The overlay-exclude class allows to prevent
+ // opening a link in the overlay.
+ if (!$target.is('a') || $target.hasClass('overlay-exclude')) {
+ $target = $target.closest('a');
+ if (!$target.length) {
+ return;
+ }
+ }
+
+ var href = $target.attr('href');
+ // Only continue if link has an href attribute.
+ if (href != undefined) {
+ // Open admin links in the overlay.
+ if (self.isAdminLink(href)) {
+ href = self.fragmentizeLink($target.get(0));
+ // Only override default behavior when left-clicking and user is not
+ // pressing the ALT, CTRL, META (Command key on the Macintosh keyboard)
+ // or SHIFT key.
+ if (event.button == 0 && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
+ // Redirect to a fragmentized href. This will trigger a hashchange event.
+ self.redirect(href);
+ // Prevent default action and further propagation of the event.
+ return false;
+ }
+ // Otherwise only alter clicked link's href. This is being picked up by
+ // the default action handler.
+ else {
+ $target.attr('href', href);
+ }
+ }
+ // Open external links in a new window.
+ else if ($target.get(0).hostname != window.location.hostname) {
+ // Add a target attribute to the clicked link. This is being picked up by
+ // the default action handler.
+ $target.attr('target', '_new');
+ }
+ // Non-admin links should close the overlay and open in the main window.
+ // Only handle them if the overlay is open and the clicked link is inside
+ // the overlay iframe, else default action will do fine.
+ else if (self.isOpen) {
+ var inFrame = false;
+ // W3C: http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-UIEvent-view
+ if (event.view && event.view.frameElement != null) {
+ inFrame = true;
+ }
+ // IE: http://msdn.microsoft.com/en-us/library/ms534331%28VS.85%29.aspx
+ else if (event.target.ownerDocument.parentWindow && event.target.ownerDocument.parentWindow.frameElement != null) {
+ inFrame = true;
+ }
+
+ // Add a target attribute to the clicked link. This is being picked up by
+ // the default action handler.
+ if (inFrame) {
+ // Make the link to be opening in the immediate parent of the frame.
+ $target.attr('target', '_parent');
+ }
}
- href += (href.indexOf('?') > -1 ? '&' : '?') + 'render=overlay' + fragment;
}
- return href;
};
/**
* Open, reload, or close the overlay, based on the current URL fragment.
*/
-Drupal.overlay.trigger = function () {
+Drupal.overlay.hashchangeHandler = function (event) {
+ var self = Drupal.overlay;
+
+ // If we changed the hash to reflect an internal redirect in the overlay,
+ // its location has already been changed, so don't do anything.
+ if ($.data(window.location, window.location.href) === 'redirect') {
+ $.data(window.location, window.location.href, null);
+ return;
+ }
+
// Get the overlay URL from the current URL fragment.
var state = $.bbq.getState('overlay');
if (state) {
// Append render variable, so the server side can choose the right
// rendering and add child modal frame code to the page if needed.
- var linkURL = Drupal.overlay.addOverlayParam(Drupal.settings.basePath + state);
+ var linkURL = Drupal.settings.basePath + state;
+ linkURL = $.param.querystring(linkURL, {'render': 'overlay'});
+
+ var path = self.getPath(linkURL);
+ self.resetActiveClass(path);
// If the modal frame is already open, replace the loaded document with
// this new one.
- if (Drupal.overlay.isOpen) {
- Drupal.overlay.load(linkURL);
+ if (self.isOpen) {
+ self.load(linkURL);
}
else {
// There is not an overlay opened yet; we should open a new one.
@@ -729,19 +779,17 @@ Drupal.overlay.trigger = function () {
onOverlayClose: function () {
// Clear the overlay URL fragment.
$.bbq.pushState();
- // Remove active class from all header buttons.
- $('a.overlay-processed').each(function () {
- $(this).removeClass('active');
- });
+
+ self.resetActiveClass(self.getPath(window.location));
}
};
- Drupal.overlay.open(overlayOptions);
+ self.open(overlayOptions);
}
}
// If there is no overlay URL in the fragment and the overlay is (still)
// open, close the overlay.
- else if (Drupal.overlay.isOpen && !Drupal.overlay.isClosing) {
- Drupal.overlay.close();
+ else if (self.isOpen && !self.isClosing) {
+ self.close();
}
};
@@ -755,32 +803,19 @@ Drupal.overlay.trigger = function () {
* /node/1#overlay=admin/config).
*/
Drupal.overlay.fragmentizeLink = function (link) {
+ var self = this;
// Don't operate on links that are already overlay-ready.
var params = $.deparam.fragment(link.href);
if (params.overlay) {
return link.href;
}
- // Determine the link's original destination, and make it relative to the
- // Drupal site.
- var path = link.pathname;
- // Ensure a leading slash on the path, omitted in some browsers.
- if (path.substr(0, 1) != '/') {
- path = '/' + path;
- }
- path = path.replace(new RegExp(Drupal.settings.basePath), '');
+ // Determine the link's original destination. Set ignorePathFromQueryString to
+ // true to prevent transforming this link into a clean URL while clean URLs
+ // may be disabled.
+ var path = self.getPath(link, true);
// Preserve existing query and fragment parameters in the URL.
- var fragment = link.hash;
- var querystring = link.search;
- // If the query includes ?render=overlay, leave it out.
- if (querystring.indexOf('render=overlay') !== -1) {
- querystring = querystring.replace(/render=overlay/, '');
- if (querystring === '?') {
- querystring = '';
- }
- }
-
- var destination = path + querystring + fragment;
+ var destination = path + link.search + link.hash;
// Assemble the overlay-ready link.
var base = window.location.href;
@@ -838,6 +873,65 @@ Drupal.overlay.refreshRegions = function (data) {
};
/**
+ * Reset the active class on links in displaced regions according to given path.
+ *
+ * @param activePath
+ * Path to match links against.
+ */
+Drupal.overlay.resetActiveClass = function(activePath) {
+ var self = this;
+
+ $('.overlay-displace-top, .overlay-displace-bottom')
+ .find('a[href]')
+ // Remove active class from all links in displaced regions.
+ .removeClass('active')
+ // Add active class to links that match activePath.
+ .each(function () {
+ var windowDomain = window.location.protocol + window.location.hostname;
+ var linkDomain = this.protocol + this.hostname;
+ var linkPath = self.getPath(this);
+
+ if (linkDomain == windowDomain && linkPath == activePath) {
+ $(this).addClass('active');
+ }
+ });
+};
+
+/**
+ * Helper function to get the (corrected) Drupal path of a link.
+ *
+ * @param link
+ * Link object or string to get the Drupal path from.
+ * @param ignorePathFromQueryString
+ * Boolean whether to ignore path from query string if path appears empty.
+ * @return
+ * The drupal path.
+ */
+Drupal.overlay.getPath = function (link, ignorePathFromQueryString) {
+ if (typeof link == 'string') {
+ // Create a native Link object, so we can use its object methods.
+ link = $(link.link(link)).get(0);
+ }
+
+ var path = link.pathname;
+ // Ensure a leading slash on the path, omitted in some browsers.
+ if (path.charAt(0) != '/') {
+ path = '/' + path;
+ }
+ path = path.replace(new RegExp(Drupal.settings.basePath), '');
+ if (path == '' && !ignorePathFromQueryString) {
+ // If the path appears empty, it might mean the path is represented in the
+ // query string (clean URLs are not used).
+ var match = new RegExp("([?&])q=(.+)([&#]|$)").exec(link.search);
+ if (match && match.length == 4) {
+ path = match[2];
+ }
+ }
+
+ return path;
+};
+
+/**
* Theme function to create the overlay iframe element.
*/
Drupal.theme.prototype.overlayElement = function () {
diff --git a/modules/overlay/overlay.module b/modules/overlay/overlay.module
index d64225858..2e86955b3 100644
--- a/modules/overlay/overlay.module
+++ b/modules/overlay/overlay.module
@@ -73,6 +73,9 @@ function overlay_init() {
}
// Indicate that we are viewing an overlay child page.
overlay_set_mode('child');
+
+ // Unset the render parameter to avoid it being included in URLs on the page.
+ unset($_GET['render']);
}
else {
// Otherwise add overlay parent code and our behavior.
@@ -282,7 +285,6 @@ function overlay_preprocess_page(&$variables) {
*/
function overlay_preprocess_toolbar(&$variables) {
$variables['classes_array'][] = "overlay-displace-top";
- $variables['toolbar']['toolbar_toggle']['#attributes']['class'][] = 'overlay-displace-no-click';
}
/**
@@ -303,7 +305,7 @@ function overlay_preprocess_toolbar(&$variables) {
* @ingroup forms
*/
function overlay_form_after_build($form, &$form_state) {
- if (isset($_GET['render']) && $_GET['render'] == 'overlay') {
+ if (overlay_get_mode() == 'child') {
// Form API may have already captured submit handlers from the submitted
// button before after_build callback is invoked. This may have been done
// by _form_builder_handle_input_element(). If so, the list of submit
diff --git a/modules/toolbar/toolbar.js b/modules/toolbar/toolbar.js
index be69b4a71..061ef7ac6 100644
--- a/modules/toolbar/toolbar.js
+++ b/modules/toolbar/toolbar.js
@@ -11,20 +11,13 @@ Drupal.behaviors.admin = {
$('#toolbar', context).once('toolbar', Drupal.admin.toolbar.init);
// Toggling toolbar drawer.
- $('#toolbar a.toggle', context).once('toolbar-toggle').click(function() {
+ $('#toolbar a.toggle', context).once('toolbar-toggle').click(function(e) {
Drupal.admin.toolbar.toggle();
+ // As the toolbar is an overlay displaced region, overlay should be
+ // notified of it's height change to adapt its position.
+ $(window).triggerHandler('resize.overlay-event');
return false;
});
-
- // Set the most recently clicked item as active.
- $('#toolbar a').once().click(function() {
- $('#toolbar a').each(function() {
- $(this).removeClass('active');
- });
- if ($(this).parents('div.toolbar-shortcuts').length) {
- $(this).addClass('active');
- }
- });
}
};