Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit d537d25

Browse files
committed
fix(list): isEventFromControl() works on all browsers now
Fixes #7937
1 parent deb3dfc commit d537d25

File tree

4 files changed

+78
-49
lines changed

4 files changed

+78
-49
lines changed

src/components/list/demoListControls/script.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ angular.module('listDemo2', ['ngMaterial'])
1515
];
1616

1717
$scope.settings = [
18-
{ name: 'Wi-Fi', extraScreen: 'Wi-fi menu', icon: 'device:network-wifi', enabled: true },
18+
{ name: 'Wi-Fi', extraScreen: 'Wi-Fi menu', icon: 'device:network-wifi', enabled: true },
1919
{ name: 'Bluetooth', extraScreen: 'Bluetooth menu', icon: 'device:bluetooth', enabled: false },
2020
];
2121

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
md-divider {
2-
margin-top: 0;
3-
margin-bottom: 0;
2+
margin-top: 0;
3+
margin-bottom: 0;
44
}
5-
65
md-list {
7-
padding-top:0;
6+
padding-top: 0;
87
}
98
md-list-item > p,
109
md-list-item > .md-list-item-inner > p,
1110
md-list-item .md-list-item-inner > p,
1211
md-list-item .md-list-item-inner > .md-list-item-inner > p {
13-
-webkit-user-select: none; /* Chrome all / Safari all */
14-
-moz-user-select: none; /* Firefox all */
15-
-ms-user-select: none; /* IE 10+ */
16-
user-select: none; /* Likely future */
17-
}
12+
-webkit-user-select: none; /* Chrome all / Safari all */
13+
-moz-user-select: none; /* Firefox all */
14+
-ms-user-select: none; /* IE 10+ */
15+
user-select: none; /* Likely future */
16+
}

src/components/list/list.js

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -232,22 +232,24 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
232232
return {
233233
restrict: 'E',
234234
controller: 'MdListController',
235-
compile: function(tEl, tAttrs) {
235+
236+
compile: function(tElement, tAttrs) {
236237

237238
// Check for proxy controls (no ng-click on parent, and a control inside)
238-
var secondaryItems = tEl[0].querySelectorAll('.md-secondary');
239+
var secondaryItems = tElement[0].querySelectorAll('.md-secondary');
239240
var hasProxiedElement;
240241
var proxyElement;
241-
var itemContainer = tEl;
242+
var itemContainer = tElement;
242243

243-
tEl[0].setAttribute('role', 'listitem');
244+
tElement[0].setAttribute('role', 'listitem');
244245

245246
if (tAttrs.ngClick || tAttrs.ngDblclick || tAttrs.ngHref || tAttrs.href || tAttrs.uiSref || tAttrs.ngAttrUiSref) {
246247
wrapIn('button');
247-
} else if (!tEl.hasClass('md-no-proxy')) {
248+
} else if (!tElement.hasClass('md-no-proxy')) {
248249

249-
for (var i = 0, type; type = proxiedTypes[i]; ++i) {
250-
if (proxyElement = tEl[0].querySelector(type)) {
250+
for (var i = 0, type; i < proxiedTypes.length; ++i) {
251+
proxyElement = tElement[0].querySelector(proxiedTypes[i]);
252+
if (proxyElement !== null) {
251253
hasProxiedElement = true;
252254
break;
253255
}
@@ -256,7 +258,7 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
256258
if (hasProxiedElement) {
257259
wrapIn('div');
258260
} else {
259-
tEl.addClass('md-no-proxy');
261+
tElement.addClass('md-no-proxy');
260262
}
261263

262264
}
@@ -272,10 +274,11 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
272274
var toggleTypes = ['md-switch', 'md-checkbox'];
273275
var toggle;
274276

275-
for (var i = 0, toggleType; toggleType = toggleTypes[i]; ++i) {
276-
if (toggle = tEl.find(toggleType)[0]) {
277+
for (var i = 0, toggleType; i < toggleTypes.length; ++i) {
278+
toggle = tElement.find(toggleTypes[i])[0];
279+
if (toggle) {
277280
if (!toggle.hasAttribute('aria-label')) {
278-
var p = tEl.find('p')[0];
281+
var p = tElement.find('p')[0];
279282
if (!p) return;
280283
toggle.setAttribute('aria-label', 'Toggle ' + p.textContent);
281284
}
@@ -312,11 +315,14 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
312315
}
313316
}
314317

318+
/**
319+
* @param {'div'|'button'} type
320+
*/
315321
function wrapIn(type) {
316-
if (type == 'div') {
322+
if (type === 'div') {
317323
itemContainer = angular.element('<div class="md-no-style md-list-item-inner">');
318-
itemContainer.append(tEl.contents());
319-
tEl.addClass('md-proxy-focus');
324+
itemContainer.append(tElement.contents());
325+
tElement.addClass('md-proxy-focus');
320326
} else {
321327
// Element which holds the default list-item content.
322328
itemContainer = angular.element(
@@ -330,30 +336,30 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
330336
'<md-button class="md-no-style"></md-button>'
331337
);
332338

333-
copyAttributes(tEl[0], buttonWrap[0]);
339+
copyAttributes(tElement[0], buttonWrap[0]);
334340

335341
// If there is no aria-label set on the button (previously copied over if present)
336342
// we determine the label from the content and copy it to the button.
337343
if (!buttonWrap.attr('aria-label')) {
338-
buttonWrap.attr('aria-label', $mdAria.getText(tEl));
344+
buttonWrap.attr('aria-label', $mdAria.getText(tElement));
339345
}
340346

341347
// We allow developers to specify the `md-no-focus` class, to disable the focus style
342348
// on the button executor. Once more classes should be forwarded, we should probably make the
343349
// class forward more generic.
344-
if (tEl.hasClass('md-no-focus')) {
350+
if (tElement.hasClass('md-no-focus')) {
345351
buttonWrap.addClass('md-no-focus');
346352
}
347353

348354
// Append the button wrap before our list-item content, because it will overlay in relative.
349355
itemContainer.prepend(buttonWrap);
350-
itemContainer.children().eq(1).append(tEl.contents());
356+
itemContainer.children().eq(1).append(tElement.contents());
351357

352-
tEl.addClass('_md-button-wrap');
358+
tElement.addClass('_md-button-wrap');
353359
}
354360

355-
tEl[0].setAttribute('tabindex', '-1');
356-
tEl.append(itemContainer);
361+
tElement[0].setAttribute('tabindex', '-1');
362+
tElement.append(itemContainer);
357363
}
358364

359365
function wrapSecondaryItems() {
@@ -391,7 +397,7 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
391397
angular.element(secondaryItem).removeClass('md-secondary');
392398
}
393399

394-
tEl.addClass('md-with-secondary');
400+
tElement.addClass('md-with-secondary');
395401
container.append(secondaryItem);
396402
}
397403

@@ -422,13 +428,13 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
422428
}
423429

424430
function isProxiedElement(el) {
425-
return proxiedTypes.indexOf(el.nodeName.toLowerCase()) != -1;
431+
return proxiedTypes.indexOf(el.nodeName.toLowerCase()) !== -1;
426432
}
427433

428434
function isButton(el) {
429435
var nodeName = el.nodeName.toUpperCase();
430436

431-
return nodeName == "MD-BUTTON" || nodeName == "BUTTON";
437+
return nodeName === "MD-BUTTON" || nodeName === "BUTTON";
432438
}
433439

434440
function hasClickEvent (element) {
@@ -493,7 +499,7 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
493499
}
494500

495501
function computeClickable() {
496-
if (proxies.length == 1 || hasClick) {
502+
if (proxies.length === 1 || hasClick) {
497503
$element.addClass('md-clickable');
498504

499505
if (!hasClick) {
@@ -502,29 +508,35 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
502508
}
503509
}
504510

511+
/**
512+
* @param {MouseEvent} event
513+
* @return {boolean}
514+
*/
505515
function isEventFromControl(event) {
506516
var forbiddenControls = ['md-slider'];
517+
var eventBubblePath = $mdUtil.getEventPath(event);
507518

508-
// If there is no path property in the event, then we can assume that the event was not bubbled.
509-
if (!event.path) {
519+
// If there is no bubble path, then the event was not bubbled.
520+
if (!eventBubblePath || eventBubblePath.length === 0) {
510521
return forbiddenControls.indexOf(event.target.tagName.toLowerCase()) !== -1;
511522
}
512523

513-
// We iterate the event path up and check for a possible component.
524+
// We iterate the event bubble path up and check for a possible component.
514525
// Our maximum index to search, is the list item root.
515-
var maxPath = event.path.indexOf($element.children()[0]);
526+
var maxPath = eventBubblePath.indexOf($element.children()[0]);
516527

517528
for (var i = 0; i < maxPath; i++) {
518-
if (forbiddenControls.indexOf(event.path[i].tagName.toLowerCase()) !== -1) {
529+
if (forbiddenControls.indexOf(eventBubblePath[i].tagName.toLowerCase()) !== -1) {
519530
return true;
520531
}
521532
}
533+
return false;
522534
}
523535

524536
var clickChildKeypressListener = function(e) {
525-
if (e.target.nodeName != 'INPUT' && e.target.nodeName != 'TEXTAREA' && !e.target.isContentEditable) {
537+
if (e.target.nodeName !== 'INPUT' && e.target.nodeName !== 'TEXTAREA' && !e.target.isContentEditable) {
526538
var keyCode = e.which || e.keyCode;
527-
if (keyCode == $mdConstant.KEY_CODE.SPACE) {
539+
if (keyCode === $mdConstant.KEY_CODE.SPACE) {
528540
if (clickChild) {
529541
clickChild.click();
530542
e.preventDefault();
@@ -541,16 +553,16 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
541553
$element.off('click');
542554
$element.off('keypress');
543555

544-
if (proxies.length == 1 && clickChild) {
545-
$element.children().eq(0).on('click', function(e) {
546-
// When the event is coming from an control and it should not trigger the proxied element
556+
if (proxies.length === 1 && clickChild) {
557+
$element.children().eq(0).on('click', function(clickEvent) {
558+
// When the event is coming from a control and it should not trigger the proxied element
547559
// then we are skipping.
548-
if (isEventFromControl(e)) return;
560+
if (isEventFromControl(clickEvent)) return;
549561

550-
var parentButton = $mdUtil.getClosest(e.target, 'BUTTON');
551-
if (!parentButton && clickChild.contains(e.target)) {
562+
var parentButton = $mdUtil.getClosest(clickEvent.target, 'BUTTON');
563+
if (!parentButton && clickChild.contains(clickEvent.target)) {
552564
angular.forEach(proxies, function(proxy) {
553-
if (e.target !== proxy && !proxy.contains(e.target)) {
565+
if (clickEvent.target !== proxy && !proxy.contains(clickEvent.target)) {
554566
if (proxy.nodeName === 'MD-MENU') {
555567
proxy = proxy.children[0];
556568
}

src/core/util/util.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,24 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in
978978
return property;
979979
}
980980
}
981+
},
982+
983+
/**
984+
* @param {Event} event the event to calculate the bubble path for
985+
* @return {EventTarget[]} the set of nodes that this event could bubble up to
986+
*/
987+
getEventPath: function(event) {
988+
var path = [];
989+
var currentTarget = event.target;
990+
while (currentTarget) {
991+
path.push(currentTarget);
992+
currentTarget = currentTarget.parentElement;
993+
}
994+
if (path.indexOf(window) === -1 && path.indexOf(document) === -1)
995+
path.push(document);
996+
if (path.indexOf(window) === -1)
997+
path.push(window);
998+
return path;
981999
}
9821000
};
9831001

0 commit comments

Comments
 (0)