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

Commit b3e401a

Browse files
devversionThomasBurleson
authored andcommitted
feat(prefixer): add service to prefix attributes
The service is containing the following features: * Check for an element, to have the specified attribute, including the prefixes * Generate an attribute selector for the specified attribute * Build a list of all prefixed attributes Fixes #3258. Fixes #8080. Closes #8121 Closes #8163
1 parent dce2fee commit b3e401a

File tree

10 files changed

+183
-24
lines changed

10 files changed

+183
-24
lines changed

src/components/bottomSheet/bottom-sheet.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ function MdBottomSheetProvider($$interimElementProvider) {
195195
var focusable = $mdUtil.findFocusTarget(element) || angular.element(
196196
element[0].querySelector('button') ||
197197
element[0].querySelector('a') ||
198-
element[0].querySelector('[ng-click]')
198+
element[0].querySelector($mdUtil.prefixer('ng-click', true))
199199
) || backdrop;
200200

201201
if (options.escapeToClose) {

src/components/chips/js/chipsDirective.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,17 @@
249249

250250
var chipTemplate = getTemplateByQuery('md-chips>md-chip-template');
251251

252+
var chipRemoveSelector = $mdUtil
253+
.prefixer()
254+
.buildList('md-chip-remove')
255+
.map(function(attr) {
256+
return 'md-chips>*[' + attr + ']';
257+
})
258+
.join(',');
259+
252260
// Set the chip remove, chip contents and chip input templates. The link function will put
253261
// them on the scope for transclusion later.
254-
var chipRemoveTemplate = getTemplateByQuery('md-chips>*[md-chip-remove]') || templates.remove,
262+
var chipRemoveTemplate = getTemplateByQuery(chipRemoveSelector) || templates.remove,
255263
chipContentsTemplate = chipTemplate || templates.default,
256264
chipInputTemplate = getTemplateByQuery('md-chips>md-autocomplete')
257265
|| getTemplateByQuery('md-chips>input')

src/components/fabActions/fabActions.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
* @usage
2525
* See the `<md-fab-speed-dial>` or `<md-fab-toolbar>` directives for example usage.
2626
*/
27-
function MdFabActionsDirective() {
27+
function MdFabActionsDirective($mdUtil) {
2828
return {
2929
restrict: 'E',
3030

@@ -33,11 +33,7 @@
3333
compile: function(element, attributes) {
3434
var children = element.children();
3535

36-
var hasNgRepeat = false;
37-
38-
angular.forEach(['', 'data-', 'x-'], function(prefix) {
39-
hasNgRepeat = hasNgRepeat || (children.attr(prefix + 'ng-repeat') ? true : false);
40-
});
36+
var hasNgRepeat = $mdUtil.prefixer().hasAttribute(children, 'ng-repeat');
4137

4238
// Support both ng-repeat and static content
4339
if (hasNgRepeat) {

src/components/list/list.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,10 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
194194
}
195195

196196
function copyAttributes(item, wrapper) {
197-
var copiedAttrs = ['ng-if', 'ng-click', 'ng-dblclick', 'aria-label', 'ng-disabled',
198-
'ui-sref', 'href', 'ng-href', 'target', 'ng-attr-ui-sref', 'ui-sref-opts'];
197+
var copiedAttrs = $mdUtil.prefixer([
198+
'ng-if', 'ng-click', 'ng-dblclick', 'aria-label', 'ng-disabled', 'ui-sref',
199+
'href', 'ng-href', 'target', 'ng-attr-ui-sref', 'ui-sref-opts'
200+
]);
199201

200202
angular.forEach(copiedAttrs, function(attr) {
201203
if (item.hasAttribute(attr)) {

src/components/menu/js/menuController.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ angular
99
*/
1010
function MenuController($mdMenu, $attrs, $element, $scope, $mdUtil, $timeout, $rootScope, $q) {
1111

12+
var prefixer = $mdUtil.prefixer();
1213
var menuContainer;
1314
var self = this;
1415
var triggerElement;
@@ -22,8 +23,9 @@ function MenuController($mdMenu, $attrs, $element, $scope, $mdUtil, $timeout, $r
2223
this.init = function init(setMenuContainer, opts) {
2324
opts = opts || {};
2425
menuContainer = setMenuContainer;
26+
2527
// Default element for ARIA attributes has the ngClick or ngMouseenter expression
26-
triggerElement = $element[0].querySelector('[ng-click],[ng-mouseenter]');
28+
triggerElement = $element[0].querySelector(prefixer.buildSelector(['ng-click', 'ng-mouseenter']));
2729
triggerElement.setAttribute('aria-expanded', 'false');
2830

2931
this.isInMenuBar = opts.isInMenuBar;
@@ -153,7 +155,9 @@ function MenuController($mdMenu, $attrs, $element, $scope, $mdUtil, $timeout, $r
153155
};
154156

155157
this.focusMenuContainer = function focusMenuContainer() {
156-
var focusTarget = menuContainer[0].querySelector('[md-menu-focus-target], [md-autofocus]');
158+
var focusTarget = menuContainer[0]
159+
.querySelector(prefixer.buildSelector(['md-menu-focus-target', 'md-autofocus']));
160+
157161
if (!focusTarget) focusTarget = menuContainer[0].querySelector('.md-button');
158162
focusTarget.focus();
159163
};

src/components/menu/js/menuDirective.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,11 @@ function MenuDirective($mdUtil) {
175175
function compile(templateElement) {
176176
templateElement.addClass('md-menu');
177177
var triggerElement = templateElement.children()[0];
178-
if (!triggerElement.hasAttribute('ng-click')) {
179-
triggerElement = triggerElement.querySelector('[ng-click],[ng-mouseenter]') || triggerElement;
178+
var prefixer = $mdUtil.prefixer();
179+
180+
if (!prefixer.hasAttribute(triggerElement, 'ng-click')) {
181+
triggerElement = triggerElement
182+
.querySelector(prefixer.buildSelector(['ng-click', 'ng-mouseenter'])) || triggerElement;
180183
}
181184
if (triggerElement && (
182185
triggerElement.nodeName == 'MD-BUTTON' ||

src/components/menu/js/menuServiceProvider.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ function MenuProvider($$interimElementProvider) {
2323

2424
/* @ngInject */
2525
function menuDefaultOptions($mdUtil, $mdTheming, $mdConstant, $document, $window, $q, $$rAF, $animateCss, $animate) {
26+
var prefixer = $mdUtil.prefixer();
2627
var animator = $mdUtil.dom.animator;
2728

2829
return {
@@ -210,7 +211,9 @@ function MenuProvider($$interimElementProvider) {
210211
opts.menuContentEl[0].addEventListener('click', captureClickListener, true);
211212

212213
// kick off initial focus in the menu on the first element
213-
var focusTarget = opts.menuContentEl[0].querySelector('[md-menu-focus-target], [md-autofocus]');
214+
var focusTarget = opts.menuContentEl[0]
215+
.querySelector(prefixer.buildSelector(['md-menu-focus-target', 'md-autofocus']));
216+
214217
if ( !focusTarget ) {
215218
var firstChild = opts.menuContentEl[0].firstElementChild;
216219

@@ -306,14 +309,13 @@ function MenuProvider($$interimElementProvider) {
306309

307310
function hasAnyAttribute(target, attrs) {
308311
if (!target) return false;
312+
309313
for (var i = 0, attr; attr = attrs[i]; ++i) {
310-
var altForms = [attr, 'data-' + attr, 'x-' + attr];
311-
for (var j = 0, rawAttr; rawAttr = altForms[j]; ++j) {
312-
if (target.hasAttribute(rawAttr)) {
313-
return true;
314-
}
314+
if (prefixer.hasAttribute(target, attr)) {
315+
return true;
315316
}
316317
}
318+
317319
return false;
318320
}
319321
}
@@ -389,7 +391,7 @@ function MenuProvider($$interimElementProvider) {
389391

390392
var menuStyle = $window.getComputedStyle(openMenuNode);
391393

392-
var originNode = opts.target[0].querySelector('[md-menu-origin]') || opts.target[0],
394+
var originNode = opts.target[0].querySelector(prefixer.buildSelector('md-menu-origin')) || opts.target[0],
393395
originNodeRect = originNode.getBoundingClientRect();
394396

395397
var bounds = {
@@ -407,7 +409,7 @@ function MenuProvider($$interimElementProvider) {
407409
if ( alignTarget ) {
408410
// TODO: Allow centering on an arbitrary node, for now center on first menu-item's child
409411
alignTarget = alignTarget.firstElementChild || alignTarget;
410-
alignTarget = alignTarget.querySelector('[md-menu-align-target]') || alignTarget;
412+
alignTarget = alignTarget.querySelector(prefixer.buildSelector('md-menu-align-target')) || alignTarget;
411413
alignTargetRect = alignTarget.getBoundingClientRect();
412414

413415
existingOffsets = {

src/core/util/prefixer.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
angular
2+
.module('material.core')
3+
.config( function($provide) {
4+
$provide.decorator('$mdUtil', ['$delegate', function ($delegate) {
5+
6+
// Inject the prefixer into our original $mdUtil service.
7+
$delegate.prefixer = MdPrefixer;
8+
9+
return $delegate;
10+
}]);
11+
});
12+
13+
function MdPrefixer(initialAttributes, buildSelector) {
14+
var PREFIXES = ['data', 'x'];
15+
16+
if (initialAttributes) {
17+
// The prefixer also accepts attributes as a parameter, and immediately builds a list or selector for
18+
// the specified attributes.
19+
return buildSelector ? _buildSelector(initialAttributes) : _buildList(initialAttributes);
20+
}
21+
22+
return {
23+
buildList: _buildList,
24+
buildSelector: _buildSelector,
25+
hasAttribute: _hasAttribute
26+
};
27+
28+
function _buildList(attributes) {
29+
attributes = angular.isArray(attributes) ? attributes : [attributes];
30+
31+
attributes.forEach(function(item) {
32+
PREFIXES.forEach(function(prefix) {
33+
attributes.push(prefix + '-' + item);
34+
});
35+
});
36+
37+
return attributes;
38+
}
39+
40+
function _buildSelector(attributes) {
41+
attributes = angular.isArray(attributes) ? attributes : [attributes];
42+
43+
return _buildList(attributes)
44+
.map(function (item) {
45+
return '[' + item + ']'
46+
})
47+
.join(',');
48+
}
49+
50+
function _hasAttribute(element, attribute) {
51+
element = element[0] || element;
52+
53+
var prefixedAttrs = _buildList(attribute);
54+
55+
for (var i = 0; i < prefixedAttrs.length; i++) {
56+
if (element.hasAttribute(prefixedAttrs[i])) {
57+
return true;
58+
}
59+
}
60+
61+
return false;
62+
}
63+
}

src/core/util/prefixer.spec.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
describe('prefixer', function() {
2+
3+
beforeEach(module('material.core'));
4+
5+
beforeEach(inject(function($injector) {
6+
$mdUtil = $injector.get('$mdUtil');
7+
}));
8+
9+
describe('when using an initial parameter', function() {
10+
11+
it('should correctly prefix a single attribute', function() {
12+
expect($mdUtil.prefixer('ng-click')).toEqual(['ng-click', 'data-ng-click', 'x-ng-click']);
13+
});
14+
15+
it('should correctly prefix multiple attributes', function() {
16+
expect($mdUtil.prefixer(['ng-click', 'ng-href']))
17+
.toEqual(['ng-click', 'ng-href', 'data-ng-click', 'x-ng-click', 'data-ng-href', 'x-ng-href']);
18+
});
19+
20+
it('should correctly build a selector for a single attribute', function() {
21+
expect($mdUtil.prefixer('ng-click', true)).toBe('[ng-click],[data-ng-click],[x-ng-click]');
22+
});
23+
24+
it('should correctly build a selector for multiple attributes', function() {
25+
expect($mdUtil.prefixer(['ng-click', 'ng-href'], true))
26+
.toBe('[ng-click],[ng-href],[data-ng-click],[x-ng-click],[data-ng-href],[x-ng-href]');
27+
});
28+
29+
});
30+
31+
describe('when using the returned object', function() {
32+
var prefixer;
33+
34+
beforeEach(function() {
35+
prefixer = $mdUtil.prefixer();
36+
});
37+
38+
describe('and building a list', function() {
39+
40+
it('should correctly prefix a single attribute', function() {
41+
expect(prefixer.buildList('ng-click')).toEqual(['ng-click', 'data-ng-click', 'x-ng-click']);
42+
});
43+
44+
it('should correctly prefix multiple attributes', function() {
45+
expect(prefixer.buildList(['ng-click', 'ng-href']))
46+
.toEqual(['ng-click', 'ng-href', 'data-ng-click', 'x-ng-click', 'data-ng-href', 'x-ng-href']);
47+
});
48+
49+
});
50+
51+
describe('and building a selector', function() {
52+
53+
it('should correctly build for a single attribute', function() {
54+
expect(prefixer.buildSelector('ng-click')).toBe('[ng-click],[data-ng-click],[x-ng-click]');
55+
});
56+
57+
it('should correctly build for multiple attributes', function() {
58+
expect(prefixer.buildSelector(['ng-click', 'ng-href']))
59+
.toBe('[ng-click],[ng-href],[data-ng-click],[x-ng-click],[data-ng-href],[x-ng-href]');
60+
});
61+
});
62+
63+
describe('and checking for an attribute', function() {
64+
65+
it('should correctly detect a prefixed attribute', function() {
66+
var element = angular.element('<div data-ng-click="null">');
67+
68+
expect(prefixer.hasAttribute(element, 'ng-click')).toBe(true);
69+
});
70+
71+
it('should correctly detect an un-prefixed attribute', function() {
72+
var element = angular.element('<div ng-click="null">');
73+
74+
expect(prefixer.hasAttribute(element, 'ng-click')).toBe(true);
75+
});
76+
77+
});
78+
79+
});
80+
81+
});

src/core/util/util.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,14 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in
134134
* @returns {*}
135135
*/
136136
findFocusTarget: function(containerEl, attributeVal) {
137-
var AUTO_FOCUS = '[md-autofocus]';
137+
var AUTO_FOCUS = this.prefixer('md-autofocus', true);
138138
var elToFocus;
139139

140140
elToFocus = scanForFocusable(containerEl, attributeVal || AUTO_FOCUS);
141141

142142
if ( !elToFocus && attributeVal != AUTO_FOCUS) {
143143
// Scan for deprecated attribute
144-
elToFocus = scanForFocusable(containerEl, '[md-auto-focus]');
144+
elToFocus = scanForFocusable(containerEl, this.prefixer('md-auto-focus', true));
145145

146146
if ( !elToFocus ) {
147147
// Scan for fallback to 'universal' API

0 commit comments

Comments
 (0)