Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(145)

Side by Side Diff: remoting/webapp/app_remoting/js/context_menu_dom.js

Issue 1816653002: Remove app_remoting. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6 * @fileoverview
7 * Provide an alternative location for the application's context menu items
8 * on platforms that don't provide it.
9 *
10 * To mimic the behaviour of an OS-provided context menu, the menu is dismissed
11 * in three situations:
12 *
13 * 1. When the window loses focus (i.e, the user has clicked on another window
14 * or on the desktop).
15 * 2. When the user selects an option from the menu.
16 * 3. When the user clicks on another part of the same window; this is achieved
17 * using an invisible screen element behind the menu, but in front of all
18 * other DOM.
19 *
20 * TODO(jamiewalch): Fold this functionality into remoting.MenuButton.
21 */
22 'use strict';
23
24 /** @suppress {duplicate} */
25 var remoting = remoting || {};
26
27 /**
28 * @constructor
29 * @implements {remoting.WindowShape.ClientUI}
30 * @implements {remoting.ContextMenuAdapter}
31 * @param {HTMLElement} root The root of the context menu DOM.
32 * @param {remoting.WindowShape} windowShape
33 */
34 remoting.ContextMenuDom = function(root, windowShape) {
35 /** @private {HTMLElement} */
36 this.root_ = root;
37 /** @private {HTMLElement} */
38 this.stub_ = /** @type {HTMLElement} */
39 (this.root_.querySelector('.context-menu-stub'));
40 /** @private {HTMLElement} */
41 this.icon_ = /** @type {HTMLElement} */
42 (this.root_.querySelector('.context-menu-icon'));
43 /** @private {HTMLElement} */
44 this.screen_ = /** @type {HTMLElement} */
45 (this.root_.querySelector('.context-menu-screen'));
46 /** @private {HTMLElement} */
47 this.menu_ = /** @type {HTMLElement} */ (this.root_.querySelector('ul'));
48 /** @private {number} */
49 this.bottom_ = 8;
50 /** @private {base.EventSourceImpl} */
51 this.eventSource_ = new base.EventSourceImpl();
52 /** @private {string} */
53 this.eventName_ = '_click';
54 /**
55 * Since the same element is used to lock the icon open and to drag it, we
56 * must keep track of drag events so that the corresponding click event can
57 * be ignored.
58 *
59 * @private {boolean}
60 */
61 this.stubDragged_ = false;
62
63 /** @private */
64 this.windowShape_ = windowShape;
65
66 /**
67 * @private
68 */
69 this.dragAndDrop_ = new remoting.DragAndDrop(
70 this.stub_, this.onDragUpdate_.bind(this));
71
72 this.eventSource_.defineEvents([this.eventName_]);
73 this.root_.addEventListener(
74 'transitionend', this.onTransitionEnd_.bind(this), false);
75 this.stub_.addEventListener('click', this.onStubClick_.bind(this), false);
76 this.icon_.addEventListener('click', this.onIconClick_.bind(this), false);
77 this.screen_.addEventListener('click', this.onIconClick_.bind(this), false);
78
79 this.root_.hidden = false;
80 this.root_.style.bottom = this.bottom_ + 'px';
81 this.windowShape_.registerClientUI(this);
82 };
83
84 remoting.ContextMenuDom.prototype.dispose = function() {
85 this.windowShape_.unregisterClientUI(this);
86 };
87
88 /**
89 * @param {Array<{left: number, top: number, width: number, height: number}>}
90 * rects List of rectangles.
91 */
92 remoting.ContextMenuDom.prototype.addToRegion = function(rects) {
93 var rect = /** @type {ClientRect} */ (this.root_.getBoundingClientRect());
94 // Clip the menu position to the main window in case the screen size has
95 // changed or a recent drag event tried to move it out of bounds.
96 if (rect.top < 0) {
97 this.bottom_ += rect.top;
98 this.root_.style.bottom = this.bottom_ + 'px';
99 rect = this.root_.getBoundingClientRect();
100 }
101
102 rects.push(rect);
103 if (this.root_.classList.contains('menu-opened')) {
104 var menuRect = this.menu_.getBoundingClientRect();
105 rects.push(menuRect);
106 }
107 };
108
109 /**
110 * @param {string} id An identifier for the menu entry.
111 * @param {string} title The text to display in the menu.
112 * @param {boolean} isCheckable True if the state of this menu entry should
113 * have a check-box and manage its toggle state automatically. Note that
114 * checkable menu entries always start off unchecked.
115 * @param {string=} opt_parentId The id of the parent menu item for submenus.
116 */
117 remoting.ContextMenuDom.prototype.create = function(
118 id, title, isCheckable, opt_parentId) {
119 var menuEntry = /** @type {HTMLElement} */ (document.createElement('li'));
120 menuEntry.innerText = title;
121 menuEntry.setAttribute('data-id', id);
122 if (isCheckable) {
123 menuEntry.setAttribute('data-checkable', true);
124 }
125 menuEntry.addEventListener('click', this.onClick_.bind(this), false);
126 /** @type {Node} */
127 var insertBefore = null;
128 if (opt_parentId) {
129 var parent = /** @type {HTMLElement} */
130 (this.menu_.querySelector('[data-id="' + opt_parentId + '"]'));
131 console.assert(
132 parent != null,
133 'No parent match for [data-id="' + /** @type {string} */(opt_parentId) +
134 '"] in create().');
135 console.assert(!parent.classList.contains('menu-group-item'),
136 'Nested sub-menus are not supported.');
137 parent.classList.add('menu-group-header');
138 menuEntry.classList.add('menu-group-item');
139 insertBefore = this.getInsertionPointForParent(
140 /** @type {string} */(opt_parentId));
141 }
142 this.menu_.insertBefore(menuEntry, insertBefore);
143 };
144
145 /**
146 * @param {string} id
147 * @param {string} title
148 */
149 remoting.ContextMenuDom.prototype.updateTitle = function(id, title) {
150 var node = this.menu_.querySelector('[data-id="' + id + '"]');
151 if (node) {
152 node.innerText = title;
153 }
154 };
155
156 /**
157 * @param {string} id
158 * @param {boolean} checked
159 */
160 remoting.ContextMenuDom.prototype.updateCheckState = function(id, checked) {
161 var node = /** @type {HTMLElement} */
162 (this.menu_.querySelector('[data-id="' + id + '"]'));
163 if (node) {
164 if (checked) {
165 node.classList.add('selected');
166 } else {
167 node.classList.remove('selected');
168 }
169 }
170 };
171
172 /**
173 * @param {string} id
174 */
175 remoting.ContextMenuDom.prototype.remove = function(id) {
176 var node = this.menu_.querySelector('[data-id="' + id + '"]');
177 if (node) {
178 this.menu_.removeChild(node);
179 }
180 };
181
182 /**
183 * @param {function(OnClickData=):void} listener
184 */
185 remoting.ContextMenuDom.prototype.addListener = function(listener) {
186 this.eventSource_.addEventListener(this.eventName_, listener);
187 };
188
189 /**
190 * @param {Event} event
191 * @private
192 */
193 remoting.ContextMenuDom.prototype.onClick_ = function(event) {
194 var element = /** @type {HTMLElement} */ (event.target);
195 if (element.getAttribute('data-checkable')) {
196 element.classList.toggle('selected')
197 }
198 var clickData = {
199 menuItemId: element.getAttribute('data-id'),
200 checked: element.classList.contains('selected')
201 };
202 this.eventSource_.raiseEvent(this.eventName_, clickData);
203 this.onIconClick_();
204 };
205
206 /**
207 * Get the insertion point for the specified sub-menu. This is the menu item
208 * immediately following the last child of that menu group, or null if there
209 * are no menu items after that group.
210 *
211 * @param {string} parentId
212 * @return {Node?}
213 */
214 remoting.ContextMenuDom.prototype.getInsertionPointForParent = function(
215 parentId) {
216 var parentNode = this.menu_.querySelector('[data-id="' + parentId + '"]');
217 console.assert(parentNode != null,
218 'No parent match for [data-id="' + parentId +
219 '"] in getInsertionPointForParent().');
220 var childNode = /** @type {HTMLElement} */ (parentNode.nextSibling);
221 while (childNode != null && childNode.classList.contains('menu-group-item')) {
222 childNode = childNode.nextSibling;
223 }
224 return childNode;
225 };
226
227 /**
228 * Called when the CSS show/hide transition completes. Since this changes the
229 * visible dimensions of the context menu, the visible region of the window
230 * needs to be recomputed.
231 *
232 * @private
233 */
234 remoting.ContextMenuDom.prototype.onTransitionEnd_ = function() {
235 this.windowShape_.updateClientWindowShape();
236 };
237
238 /**
239 * Toggle the visibility of the context menu icon.
240 *
241 * @private
242 */
243 remoting.ContextMenuDom.prototype.onStubClick_ = function() {
244 if (this.stubDragged_) {
245 this.stubDragged_ = false;
246 return;
247 }
248 this.root_.classList.toggle('opened');
249 };
250
251 /**
252 * Toggle the visibility of the context menu.
253 *
254 * @private
255 */
256 remoting.ContextMenuDom.prototype.onIconClick_ = function() {
257 this.showMenu_(!this.menu_.classList.contains('opened'));
258 };
259
260 /**
261 * Explicitly show or hide the context menu.
262 *
263 * @param {boolean} show True to show the menu; false to hide it.
264 * @private
265 */
266 remoting.ContextMenuDom.prototype.showMenu_ = function(show) {
267 if (show) {
268 // Ensure that the menu doesn't extend off the top or bottom of the
269 // screen by aligning it to the top or bottom of the icon, depending
270 // on the latter's vertical position.
271 var menuRect =
272 /** @type {ClientRect} */ (this.menu_.getBoundingClientRect());
273 if (menuRect.bottom > window.innerHeight) {
274 this.menu_.classList.add('menu-align-bottom');
275 } else {
276 this.menu_.classList.remove('menu-align-bottom');
277 }
278
279 /** @type {remoting.ContextMenuDom} */
280 var that = this;
281 var onBlur = function() {
282 that.showMenu_(false);
283 window.removeEventListener('blur', onBlur, false);
284 };
285 window.addEventListener('blur', onBlur, false);
286
287 // Show the menu and prevent the icon from auto-hiding on mouse-out.
288 this.menu_.classList.add('opened');
289 this.root_.classList.add('menu-opened');
290
291 } else { // if (!show)
292 this.menu_.classList.remove('opened');
293 this.root_.classList.remove('menu-opened');
294 }
295
296 this.screen_.hidden = !show;
297 this.windowShape_.updateClientWindowShape();
298 };
299
300 /**
301 * @param {number} deltaX
302 * @param {number} deltaY
303 * @private
304 */
305 remoting.ContextMenuDom.prototype.onDragUpdate_ = function(deltaX, deltaY) {
306 this.stubDragged_ = true;
307 this.bottom_ -= deltaY;
308 this.root_.style.bottom = this.bottom_ + 'px';
309 // Deferring the window shape update until the DOM update has completed
310 // helps keep the position of the context menu consistent with the window
311 // shape (though it's still not perfect).
312 window.requestAnimationFrame(
313 this.windowShape_.updateClientWindowShape.bind(this.windowShape_));
314 };
OLDNEW
« no previous file with comments | « remoting/webapp/app_remoting/js/context_menu_chrome.js ('k') | remoting/webapp/app_remoting/js/drag_and_drop.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698