blob: fca8603604c04bab5a2866bf04ca8ff9a8718c25 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001// Copyright 2016 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5/**
6 * It is common to make a DIV temporarily visible to simulate
7 * a popup window. Often, this is done by adding an onClick
8 * handler to the element that can be clicked on to show the
9 * popup.
10 *
11 * Unfortunately, closing the popup is not as simple.
12 * The popup creator often wants to let the user close
13 * the popup by clicking elsewhere on the window; however,
14 * the popup only receives mouse events that occur
15 * on the popup itself. Thus, popups need a mechanism
16 * that notifies them that the user has clicked elsewhere
17 * to try to get rid of them.
18 *
19 * PopupController is such a mechanism --
20 * it monitors all mousedown events that
21 * occur in the window so that it can notify registered
22 * popups of the mousedown, and the popups can choose
23 * to deactivate themselves.
24 *
25 * For an object to qualify as a popup, it must have a
26 * function called "deactivate" that takes a mousedown event
27 * and returns a boolean indicating that it has deactivated
28 * itself as a result of that event.
29 *
30 * EXAMPLE:
31 *
32 * // popup that attaches itself to the supplied div
33 * function MyPopup(div) {
34 * this._div = div;
35 * this._isVisible = false;
36 * this._innerHTML = ...
37 * }
38 *
39 * MyPopup.prototype.show = function() {
40 * this._div.display = '';
41 * this._isVisible = true;
42 * PC_addPopup(this);
43 * }
44 *
45 * MyPopup.prototype.hide = function() {
46 * this._div.display = 'none';
47 * this._isVisible = false;
48 * }
49 *
50 * MyPopup.prototype.deactivate = function(e) {
51 * if (this._isVisible) {
52 * var p = GetMousePosition(e);
53 * if (nodeBounds(this._div).contains(p)) {
54 * return false; // use clicked on popup, remain visible
55 * } else {
56 * this.hide();
57 * return true; // clicked outside popup, make invisible
58 * }
59 * } else {
60 * return true; // already deactivated, not visible
61 * }
62 * }
63 *
64 * DEPENDENCIES (from this directory):
65 * bind.js
66 * listen.js
67 * common.js
68 * shapes.js
69 * geom.js
70 *
71 * USAGE:
72 * _PC_Install() must be called after the body is loaded
73 */
74
75/**
76 * PopupController constructor.
77 * @constructor
78 */
79function PopupController() {
80 this.activePopups_ = [];
81}
82
83/**
84 * @param {Document} opt_doc document to add PopupController to
85 * DEFAULT: "document" variable that is currently in scope
86 * @return {boolean} indicating if PopupController installed for the document;
87 * returns false if document already had PopupController
88 */
89function _PC_Install(opt_doc) {
90 if (gPopupControllerInstalled) return false;
91 gPopupControllerInstalled = true;
92 let doc = (opt_doc) ? opt_doc : document;
93
94 // insert _notifyPopups in BODY's onmousedown chain
95 listen(doc.body, 'mousedown', PC_notifyPopups);
96 return true;
97}
98
99/**
100 * Notifies each popup of a mousedown event, giving
101 * each popup the chance to deactivate itself.
102 *
103 * @throws Error if a popup does not have a deactivate function
104 *
105 * @private
106 */
107function PC_notifyPopups(e) {
108 if (gPopupController.activePopups_.length == 0) return false;
109 e = e || window.event;
110 for (let i = gPopupController.activePopups_.length - 1; i >= 0; --i) {
111 let popup = gPopupController.activePopups_[i];
112 PC_assertIsPopup(popup);
113 if (popup.deactivate(e)) {
114 gPopupController.activePopups_.splice(i, 1);
115 }
116 }
117 return true;
118}
119
120/**
121 * Adds the popup to the list of popups to be
122 * notified of a mousedown event.
123 *
124 * @return boolean indicating if added popup; false if already contained
125 * @throws Error if popup does not have a deactivate function
126 */
127function PC_addPopup(popup) {
128 PC_assertIsPopup(popup);
129 for (let i = 0; i < gPopupController.activePopups_.length; ++i) {
130 if (popup === gPopupController.activePopups_[i]) return false;
131 }
132 gPopupController.activePopups_.push(popup);
133 return true;
134}
135
136/** asserts that popup has a deactivate function */
137function PC_assertIsPopup(popup) {
138 AssertType(popup.deactivate, Function, 'popup missing deactivate function');
139}
140
141var gPopupController = new PopupController();
142var gPopupControllerInstalled = false;