summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorarv@chromium.org <arv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-05-13 21:56:54 +0000
committerarv@chromium.org <arv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-05-13 21:56:54 +0000
commit4dc955eb16d38e575aa787ff5b699f1ed6102bf8 (patch)
tree68d8ee1dc158c63eebb836f104ccb6ca5bdc5e88 /chrome
parent271eb663e75b2ee3f119b0a719a6ca156175ed57 (diff)
downloadchromium_src-4dc955eb16d38e575aa787ff5b699f1ed6102bf8.zip
chromium_src-4dc955eb16d38e575aa787ff5b699f1ed6102bf8.tar.gz
chromium_src-4dc955eb16d38e575aa787ff5b699f1ed6102bf8.tar.bz2
Add smarter popup position code to DOM UI Shared Resources.
BUG=None TEST=js unit tests as well as manually ensuring the menu button in the bookmarks manager flips side when the window gets small. Review URL: http://codereview.chromium.org/2042017 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@47198 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/resources/bookmark_manager/main.html1
-rw-r--r--chrome/browser/resources/shared/js/cr/ui/context_menu_handler.js34
-rw-r--r--chrome/browser/resources/shared/js/cr/ui/menu_button.js30
-rw-r--r--chrome/browser/resources/shared/js/cr/ui/position_util.js203
-rw-r--r--chrome/browser/resources/shared/js/cr/ui/position_util_test.html282
-rw-r--r--chrome/chrome_browser.gypi1
6 files changed, 506 insertions, 45 deletions
diff --git a/chrome/browser/resources/bookmark_manager/main.html b/chrome/browser/resources/bookmark_manager/main.html
index 6630aa9..04d4cc2 100644
--- a/chrome/browser/resources/bookmark_manager/main.html
+++ b/chrome/browser/resources/bookmark_manager/main.html
@@ -31,6 +31,7 @@ found in the LICENSE file.
<script src="chrome://resources/js/cr/ui/command.js"></script>
<script src="chrome://resources/js/cr/ui/menu_item.js"></script>
<script src="chrome://resources/js/cr/ui/menu.js"></script>
+<script src="chrome://resources/js/cr/ui/position_util.js"></script>
<script src="chrome://resources/js/cr/ui/menu_button.js"></script>
<script src="chrome://resources/js/cr/ui/context_menu_handler.js"></script>
diff --git a/chrome/browser/resources/shared/js/cr/ui/context_menu_handler.js b/chrome/browser/resources/shared/js/cr/ui/context_menu_handler.js
index 2d96a4c..6b86808 100644
--- a/chrome/browser/resources/shared/js/cr/ui/context_menu_handler.js
+++ b/chrome/browser/resources/shared/js/cr/ui/context_menu_handler.js
@@ -4,6 +4,8 @@
cr.define('cr.ui', function() {
+ const positionPopupAtPoint = cr.ui.positionPopupAtPoint;
+
/**
* Handles context menus.
* @constructor
@@ -86,37 +88,7 @@ cr.define('cr.ui', function() {
y = e.clientY;
}
- var menuRect = menu.getBoundingClientRect();
- var bodyRect = menu.ownerDocument.body.getBoundingClientRect();
-
- // Does menu fit below?
- if (y + menuRect.height > bodyRect.height) {
- // Does menu fit above?
- if (y - menuRect.height >= 0) {
- y -= menuRect.height;
- } else {
- // Menu did not fit above nor below.
- y = 0;
- // We could resize the menu here but lets not worry about that at this
- // point.
- }
- }
-
- // Does menu fit to the right?
- if (x + menuRect.width > bodyRect.width) {
- // Does menu fit to the left?
- if (x - menuRect.width >= 0) {
- x -= menuRect.width;
- } else {
- // Menu did not fit to the right nor to the left.
- x = 0;
- // We could resize the menu here but lets not worry about that at this
- // point.
- }
- }
-
- menu.style.left = x + 'px';
- menu.style.top = y + 'px';
+ positionPopupAtPoint(x, y, menu);
},
/**
diff --git a/chrome/browser/resources/shared/js/cr/ui/menu_button.js b/chrome/browser/resources/shared/js/cr/ui/menu_button.js
index cd8defb..b44ed6c 100644
--- a/chrome/browser/resources/shared/js/cr/ui/menu_button.js
+++ b/chrome/browser/resources/shared/js/cr/ui/menu_button.js
@@ -4,6 +4,7 @@
cr.define('cr.ui', function() {
const Menu = cr.ui.Menu;
+ const positionPopupAroundElement = cr.ui.positionPopupAroundElement;
/**
* Creates a new menu button element.
@@ -86,6 +87,7 @@ cr.define('cr.ui', function() {
case 'activate':
case 'blur':
+ case 'resize':
this.hideMenu();
break;
}
@@ -96,10 +98,14 @@ cr.define('cr.ui', function() {
*/
showMenu: function() {
this.menu.style.display = 'block';
+
// when the menu is shown we steal all keyboard events.
- this.ownerDocument.addEventListener('keydown', this, true);
- this.ownerDocument.addEventListener('mousedown', this, true);
- this.ownerDocument.addEventListener('blur', this, true);
+ var doc = this.ownerDocument;
+ var win = doc.defaultView;
+ doc.addEventListener('keydown', this, true);
+ doc.addEventListener('mousedown', this, true);
+ doc.addEventListener('blur', this, true);
+ win.addEventListener('resize', this);
this.menu.addEventListener('activate', this);
this.positionMenu_();
},
@@ -109,9 +115,12 @@ cr.define('cr.ui', function() {
*/
hideMenu: function() {
this.menu.style.display = 'none';
- this.ownerDocument.removeEventListener('keydown', this, true);
- this.ownerDocument.removeEventListener('mousedown', this, true);
- this.ownerDocument.removeEventListener('blur', this, true);
+ var doc = this.ownerDocument;
+ var win = doc.defaultView;
+ doc.removeEventListener('keydown', this, true);
+ doc.removeEventListener('mousedown', this, true);
+ doc.removeEventListener('blur', this, true);
+ win.removeEventListener('resize', this);
this.menu.removeEventListener('activate', this);
this.menu.selectedIndex = -1;
},
@@ -129,14 +138,7 @@ cr.define('cr.ui', function() {
* @private
*/
positionMenu_: function() {
- var buttonRect = this.getBoundingClientRect();
- this.menu.style.top = buttonRect.bottom + 'px';
- if (getComputedStyle(this).direction == 'rtl') {
- var menuRect = this.menu.getBoundingClientRect();
- this.menu.style.left = buttonRect.right - menuRect.width + 'px';
- } else {
- this.menu.style.left = buttonRect.left + 'px';
- }
+ positionPopupAroundElement(this, this.menu, cr.ui.AnchorType.BELOW);
},
/**
diff --git a/chrome/browser/resources/shared/js/cr/ui/position_util.js b/chrome/browser/resources/shared/js/cr/ui/position_util.js
new file mode 100644
index 0000000..bc96f49
--- /dev/null
+++ b/chrome/browser/resources/shared/js/cr/ui/position_util.js
@@ -0,0 +1,203 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This file provides utility functions for position popups.
+ */
+
+cr.define('cr.ui', function() {
+
+ /**
+ * Type def for rects as returned by getBoundingClientRect.
+ * @typedef { {left: number, top: number, width: number, height: number,
+ * right: number, bottom: number}}
+ */
+ var Rect;
+
+ /**
+ * Enum for defining how to anchor a popup to an anchor element.
+ * @enum {number}
+ */
+ const AnchorType = {
+ /**
+ * The popop's right edge is aligned with the left edge of the anchor.
+ * The popup's top edge is aligned with the top edge of the anchor's top
+ * edge.
+ */
+ BEFORE: 1, // p: right, a: left, p: top, a: top
+
+ /**
+ * The popop's left edge is aligned with the right edge of the anchor.
+ * The popup's top edge is aligned with the top edge of the anchor's top
+ * edge.
+ */
+ AFTER: 2, // p: left a: right, p: top, a: top
+
+ /**
+ * The popop's bottom edge is aligned with the top edge of the anchor.
+ * The popup's left edge is aligned with the left edge of the anchor's top
+ * edge.
+ */
+ ABOVE: 3, // p: bottom, a: top, p: left, a: left
+
+ /**
+ * The popop's top edge is aligned with the bottom edge of the anchor.
+ * The popup's left edge is aligned with the left edge of the anchor's top
+ * edge.
+ */
+ BELOW: 4 // p: top, a: bottom, p: left, a: left
+ };
+
+ /**
+ * Helper function for positionPopupAroundElement and positionPopupAroundRect.
+ * @param {!Rect} anchorRect The rect for the anchor.
+ * @param {!HTMLElement} popupElement The element used for the popup.
+ * @param {AnchorType} type The type of anchoring to do.
+ */
+ function positionPopupAroundRect(anchorRect, popupElement, type) {
+ var popupRect = popupElement.getBoundingClientRect();
+ var popupContainer = popupElement.offsetParent;
+ var availRect = popupContainer.getBoundingClientRect();
+ var rtl = popupElement.ownerDocument.defaultView.
+ getComputedStyle(popupElement).direction == 'rtl';
+
+ // Flip BEFORE, AFTER based on RTL.
+ if (rtl) {
+ if (type == AnchorType.BEFORE)
+ type = AnchorType.AFTER;
+ else if (type == AnchorType.AFTER)
+ type = AnchorType.BEFORE;
+ }
+
+ // Flip type based on available size
+ switch (type) {
+ case AnchorType.BELOW:
+ if (anchorRect.bottom + popupRect.height > availRect.height)
+ type = AnchorType.ABOVE;
+ break;
+ case AnchorType.ABOVE:
+ if (popupRect.height > anchorRect.top)
+ type = AnchorType.BELOW;
+ break;
+ case AnchorType.AFTER:
+ if (anchorRect.right + popupRect.width > availRect.width)
+ type = AnchorType.BEFORE;
+ break;
+ case AnchorType.BEFORE:
+ if (popupRect.width > anchorRect.left)
+ type = AnchorType.AFTER;
+ break;
+ }
+ // flipping done
+
+ var style = popupElement.style;
+ // Reset all directions.
+ style.left = style.right = style.top = style.bottom = 'auto'
+
+ // Primary direction
+ switch (type) {
+ case AnchorType.BELOW:
+ style.top = anchorRect.bottom + 'px';
+ break;
+ case AnchorType.ABOVE:
+ style.bottom = availRect.height - anchorRect.top + 'px';
+ break;
+ case AnchorType.AFTER:
+ style.left = anchorRect.right + 'px';
+ break;
+ case AnchorType.BEFORE:
+ style.right = availRect.width - anchorRect.left + 'px';
+ break;
+ }
+
+ // Secondary direction
+ switch (type) {
+ case AnchorType.BELOW:
+ case AnchorType.ABOVE:
+ if (rtl) {
+ // align right edges
+ if (anchorRect.right - popupRect.width >= 0) {
+ style.right = availRect.width - anchorRect.right + 'px';
+
+ // align left edges
+ } else if (anchorRect.left + popupRect.width <= availRect.width) {
+ style.left = anchorRect.left + 'px';
+
+ // not enough room on either side
+ } else {
+ style.right = '0';
+ }
+ } else {
+ // align left edges
+ if (anchorRect.left + popupRect.width <= availRect.width) {
+ style.left = anchorRect.left + 'px';
+
+ // align right edges
+ } else if (anchorRect.right - popupRect.width >= 0) {
+ style.right = availRect.width - anchorRect.right + 'px';
+
+ // not enough room on either side
+ } else {
+ style.left = '0';
+ }
+ }
+ break;
+
+ case AnchorType.AFTER:
+ case AnchorType.BEFORE:
+ // align top edges
+ if (anchorRect.top + popupRect.height <= availRect.height) {
+ style.top = anchorRect.top + 'px';
+
+ // align bottom edges
+ } else if (anchorRect.bottom - popupRect.height >= 0) {
+ style.bottom = availRect.height - anchorRect.bottom + 'px';
+
+ // not enough room on either side
+ } else {
+ style.top = '0';
+ }
+ break;
+ }
+ }
+
+ /**
+ * Positions a popup element relative to an anchor element. The popup element
+ * should have position set to absolute and it should be a child of the body
+ * element.
+ * @param {!HTMLElement} anchorElement The element that the popup is anchored
+ * to.
+ * @param {!HTMLElement} popupElement The popup element we are positioning.
+ * @param {AnchorType} type The type of anchoring we want.
+ */
+ function positionPopupAroundElement(anchorElement, popupElement, type) {
+ var anchorRect = anchorElement.getBoundingClientRect();
+ positionPopupAroundRect(anchorRect, popupElement, type);
+ }
+
+ /**
+ * Positions a popup around a point.
+ * @param {number} x The client x position.
+ * @param {number} y The client y position.
+ * @param {!HTMLElement} popupElement The popup element we are positioning.
+ */
+ function positionPopupAtPoint(x, y, popupElement) {
+ var rect = {
+ left: x,
+ top: y,
+ width: 0,
+ height: 0,
+ right: x,
+ bottom: y
+ };
+ positionPopupAroundRect(rect, popupElement, AnchorType.BELOW);
+ }
+
+ // Export
+ return {
+ AnchorType: AnchorType,
+ positionPopupAroundElement: positionPopupAroundElement,
+ positionPopupAtPoint: positionPopupAtPoint
+ };
+});
diff --git a/chrome/browser/resources/shared/js/cr/ui/position_util_test.html b/chrome/browser/resources/shared/js/cr/ui/position_util_test.html
new file mode 100644
index 0000000..5cf28cf
--- /dev/null
+++ b/chrome/browser/resources/shared/js/cr/ui/position_util_test.html
@@ -0,0 +1,282 @@
+<!DOCTYPE html>
+<html>
+<head>
+<!-- TODO(arv): Check in Closue unit tests and make this run as part of the
+ tests -->
+<script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script>
+<script src="../../cr.js"></script>
+<script src="position_util.js"></script>
+<script>
+
+goog.require('goog.testing.PropertyReplacer');
+goog.require('goog.testing.jsunit');
+
+</script>
+<style>
+
+html, body {
+ margin: 0;
+ width: 100%;
+ height: 100%;
+}
+
+#anchor {
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ background: green;
+}
+
+#popup {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100px;
+ height: 100px;
+ background: red;
+}
+
+</style>
+</head>
+<body>
+
+<div id=anchor></div>
+<div id=popup></div>
+
+<script>
+
+var anchor = document.getElementById('anchor');
+var popup = document.getElementById('popup');
+var anchorParent = anchor.offsetParent;
+var pr = new goog.testing.PropertyReplacer;
+var availRect;
+
+function MockRect(w, h) {
+ this.width = w;
+ this.height = h;
+ this.right = this.left + w;
+ this.bottom = this.top + h;
+}
+MockRect.prototype = {
+ left: 0,
+ top: 0
+};
+
+function setUp() {
+ anchor.style.top = '100px';
+ anchor.style.left = '100px';
+ availRect = new MockRect(200, 200);
+ pr.set(anchorParent, 'getBoundingClientRect', function() {
+ return availRect;
+ });
+}
+
+function tearDown() {
+ document.documentElement.dir = 'ltr';
+ pr.reset();
+}
+
+function testAbovePrimary() {
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.ABOVE);
+
+ assertEquals('auto', popup.style.top);
+ assertEquals('100px', popup.style.bottom);
+
+ anchor.style.top = '90px';
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.ABOVE);
+ assertEquals('100px', popup.style.top);
+ assertEquals('auto', popup.style.bottom);
+}
+
+function testBelowPrimary() {
+ // ensure enough below
+ anchor.style.top = '90px';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.BELOW);
+
+ assertEquals('100px', popup.style.top);
+ assertEquals('auto', popup.style.bottom);
+
+ // ensure not enough below
+ anchor.style.top = '100px';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.BELOW);
+ assertEquals('auto', popup.style.top);
+ assertEquals('100px', popup.style.bottom);
+}
+
+function testBeforePrimary() {
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.BEFORE);
+
+ assertEquals('auto', popup.style.left);
+ assertEquals('100px', popup.style.right);
+
+ anchor.style.left = '90px';
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.BEFORE);
+ assertEquals('100px', popup.style.left);
+ assertEquals('auto', popup.style.right);
+}
+
+function testBeforePrimaryRtl() {
+ document.documentElement.dir = 'rtl';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.AFTER);
+
+ assertEquals('auto', popup.style.left);
+ assertEquals('100px', popup.style.right);
+
+ anchor.style.left = '90px';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.AFTER);
+ assertEquals('100px', popup.style.left);
+ assertEquals('auto', popup.style.right);
+}
+
+function testAfterPrimary() {
+ // ensure enough to the right
+ anchor.style.left = '90px';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.AFTER);
+
+ assertEquals('100px', popup.style.left);
+ assertEquals('auto', popup.style.right);
+
+ // ensure not enough below
+ anchor.style.left = '100px';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.AFTER);
+ assertEquals('auto', popup.style.left);
+ assertEquals('100px', popup.style.right);
+}
+
+function testAfterPrimaryRtl() {
+ document.documentElement.dir = 'rtl';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.AFTER);
+
+ assertEquals('auto', popup.style.left);
+ assertEquals('100px', popup.style.right);
+
+ // ensure not enough below
+ anchor.style.left = '90px';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.AFTER);
+ assertEquals('100px', popup.style.left);
+ assertEquals('auto', popup.style.right);
+}
+
+function testAboveSecondary() {
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.ABOVE);
+
+ assertEquals('100px', popup.style.left);
+ assertEquals('auto', popup.style.right);
+
+ anchor.style.left = '110px';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.ABOVE);
+
+ assertEquals('auto', popup.style.left);
+ assertEquals('80px', popup.style.right);
+}
+
+function testAboveSecondaryRtl() {
+ document.documentElement.dir = 'rtl';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.ABOVE);
+
+ assertEquals('auto', popup.style.left);
+ assertEquals('90px', popup.style.right);
+
+ anchor.style.left = '80px';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.ABOVE);
+
+ assertEquals('80px', popup.style.left);
+ assertEquals('auto', popup.style.right);
+}
+
+function testBelowSecondary() {
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.BELOW);
+
+ assertEquals('100px', popup.style.left);
+ assertEquals('auto', popup.style.right);
+
+ anchor.style.left = '110px';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.BELOW);
+
+ assertEquals('auto', popup.style.left);
+ assertEquals('80px', popup.style.right);
+}
+
+function testBelowSecondaryRtl() {
+ document.documentElement.dir = 'rtl';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.BELOW);
+
+ assertEquals('auto', popup.style.left);
+ assertEquals('90px', popup.style.right);
+
+ anchor.style.left = '80px';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.BELOW);
+
+ assertEquals('80px', popup.style.left);
+ assertEquals('auto', popup.style.right);
+}
+
+function testBeforeSecondary() {
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.BEFORE);
+
+ assertEquals('100px', popup.style.top);
+ assertEquals('auto', popup.style.bottom);
+
+ anchor.style.top = '110px';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.BEFORE);
+
+ assertEquals('auto', popup.style.top);
+ assertEquals('80px', popup.style.bottom);
+}
+
+function testAfterSecondary() {
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.AFTER);
+
+ assertEquals('100px', popup.style.top);
+ assertEquals('auto', popup.style.bottom);
+
+ anchor.style.top = '110px';
+
+ cr.ui.positionPopupAroundElement(anchor, popup, cr.ui.AnchorType.AFTER);
+
+ assertEquals('auto', popup.style.top);
+ assertEquals('80px', popup.style.bottom);
+}
+
+function testPositionAtPoint() {
+ cr.ui.positionPopupAtPoint(100, 100, popup);
+
+ assertEquals('100px', popup.style.left);
+ assertEquals('100px', popup.style.top);
+ assertEquals('auto', popup.style.right);
+ assertEquals('auto', popup.style.bottom);
+
+ cr.ui.positionPopupAtPoint(100, 150, popup);
+
+ assertEquals('100px', popup.style.left);
+ assertEquals('auto', popup.style.top);
+ assertEquals('auto', popup.style.right);
+ assertEquals('50px', popup.style.bottom);
+
+ cr.ui.positionPopupAtPoint(150, 150, popup);
+
+ assertEquals('auto', popup.style.left);
+ assertEquals('auto', popup.style.top);
+ assertEquals('50px', popup.style.right);
+ assertEquals('50px', popup.style.bottom);
+}
+
+</script>
+
+</body>
+</html>
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 9fe4472..77087148 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -3273,6 +3273,7 @@
'browser/resources/shared/js/cr/ui/menu.js',
'browser/resources/shared/js/cr/ui/menu_button.js',
'browser/resources/shared/js/cr/ui/menu_item.js',
+ 'browser/resources/shared/js/cr/ui/position_util.js',
'browser/resources/shared/js/cr/ui/splitter.js',
'browser/resources/shared/js/cr/ui/tree.js',
]