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

Commit 6275424

Browse files
Derek LouieThomasBurleson
authored andcommitted
feat(select): Adding md-select-header directive to md-select.
Closes #7782
1 parent 30e6657 commit 6275424

File tree

5 files changed

+195
-10
lines changed

5 files changed

+195
-10
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<div ng-controller="SelectHeaderController" class="md-padding" ng-cloak>
2+
<div>
3+
<h1 class="md-title">Pick a vegetable below</h1>
4+
<div layout="row">
5+
<md-input-container>
6+
<label>Vegetables</label>
7+
<md-select ng-model="selectedVegetables"
8+
md-on-close="clearSearchTerm()"
9+
data-md-container-class="selectdemoSelectHeader"
10+
multiple>
11+
<md-select-header class="demo-select-header">
12+
<input ng-model="searchTerm"
13+
type="search"
14+
placeholder="Search for a vegetable.."
15+
class="demo-header-searchbox _md-text">
16+
</md-select-header>
17+
<md-optgroup label="vegetables">
18+
<md-option ng-value="vegetable" ng-repeat="vegetable in vegetables |
19+
filter:searchTerm">{{vegetable}}</md-option>
20+
</md-optgroup>
21+
</md-select>
22+
</md-input-container>
23+
</div>
24+
</div>
25+
</div>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
angular
2+
.module('selectDemoSelectHeader', ['ngMaterial'])
3+
.controller('SelectHeaderController', function($scope, $element) {
4+
$scope.vegetables = ['Corn' ,'Onions' ,'Kale' ,'Arugula' ,'Peas', 'Zucchini'];
5+
$scope.searchTerm;
6+
$scope.clearSearchTerm = function() {
7+
$scope.searchTerm = '';
8+
};
9+
// The md-select directive eats keydown events for some quick select
10+
// logic. Since we have a search input here, we don't need that logic.
11+
$element.find('input').on('keydown', function(ev) {
12+
ev.stopPropagation();
13+
});
14+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/* Please note: All these selectors are only applied to children of elements with the 'selectdemoSelectHeader' class */
2+
3+
.demo-header-searchbox {
4+
border: none;
5+
outline: none;
6+
height: 100%;
7+
width: 100%;
8+
padding: 0;
9+
}
10+
11+
.demo-select-header {
12+
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.1), 0 0 0 0 rgba(0, 0, 0,
13+
0.14), 0 0 0 0 rgba(0, 0, 0, 0.12);
14+
padding-left: 10.667px;
15+
height: 48px;
16+
cursor: pointer;
17+
position: relative;
18+
display: flex;
19+
align-items: center;
20+
width: auto;
21+
}
22+
23+
md-content._md {
24+
max-height: 240px;
25+
}

src/components/select/select.js

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ angular.module('material.components.select', [
2323
.directive('mdSelectMenu', SelectMenuDirective)
2424
.directive('mdOption', OptionDirective)
2525
.directive('mdOptgroup', OptgroupDirective)
26+
.directive('mdSelectHeader', SelectHeaderDirective)
2627
.provider('$mdSelect', SelectProvider);
2728

2829
/**
@@ -69,6 +70,27 @@ angular.module('material.components.select', [
6970
* </md-input-container>
7071
* </hljs>
7172
*
73+
* With a select-header
74+
*
75+
* When a developer needs to put more than just a text label in the
76+
* md-select-menu, they should use the md-select-header.
77+
* The user can put custom HTML inside of the header and style it to their liking.
78+
* One common use case of this would be a sticky search bar.
79+
*
80+
* When using the md-select-header the labels that would previously be added to the
81+
* OptGroupDirective are ignored.
82+
*
83+
* <hljs lang="html">
84+
* <md-input-container>
85+
* <md-select ng-model="someModel">
86+
* <md-select-header>
87+
* <span> Neighborhoods - </span>
88+
* </md-select-header>
89+
* <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
90+
* </md-select>
91+
* </md-input-container>
92+
* </hljs>
93+
*
7294
* ## Selects and object equality
7395
* When using a `md-select` to pick from a list of objects, it is important to realize how javascript handles
7496
* equality. Consider the following example:
@@ -212,7 +234,11 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par
212234
};
213235

214236
if (containerCtrl.input) {
215-
throw new Error("<md-input-container> can only have *one* child <input>, <textarea> or <select> element!");
237+
// We ignore inputs that are in the md-select-header (one
238+
// case where this might be useful would be adding as searchbox)
239+
if (element.find('md-select-header').find('input')[0] !== containerCtrl.input[0]) {
240+
throw new Error("<md-input-container> can only have *one* child <input>, <textarea> or <select> element!");
241+
}
216242
}
217243

218244
containerCtrl.input = element;
@@ -507,11 +533,13 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par
507533
}
508534

509535
function SelectMenuDirective($parse, $mdUtil, $mdTheming) {
510-
536+
// We want the scope to be set to 'false' so an isolated scope is not created
537+
// which would interfere with the md-select-header's access to the
538+
// parent scope.
511539
return {
512540
restrict: 'E',
513541
require: ['mdSelectMenu'],
514-
scope: true,
542+
scope: false,
515543
controller: SelectMenuController,
516544
link: {pre: preLink}
517545
};
@@ -897,16 +925,34 @@ function OptgroupDirective() {
897925
compile: compile
898926
};
899927
function compile(el, attrs) {
900-
var labelElement = el.find('label');
901-
if (!labelElement.length) {
902-
labelElement = angular.element('<label>');
903-
el.prepend(labelElement);
928+
// If we have a select header element, we don't want to add the normal label
929+
// header.
930+
if (!hasSelectHeader()) {
931+
setupLabelElement();
932+
}
933+
934+
function hasSelectHeader() {
935+
return el.parent().find('md-select-header').length;
936+
}
937+
938+
function setupLabelElement() {
939+
var labelElement = el.find('label');
940+
if (!labelElement.length) {
941+
labelElement = angular.element('<label>');
942+
el.prepend(labelElement);
943+
}
944+
labelElement.addClass('_md-container-ignore');
945+
if (attrs.label) labelElement.text(attrs.label);
904946
}
905-
labelElement.addClass('_md-container-ignore');
906-
if (attrs.label) labelElement.text(attrs.label);
907947
}
908948
}
909949

950+
function SelectHeaderDirective() {
951+
return {
952+
restrict: 'E',
953+
};
954+
}
955+
910956
function SelectProvider($$interimElementProvider) {
911957
return $$interimElementProvider('$mdSelect')
912958
.setDefaults({
@@ -1245,6 +1291,7 @@ function SelectProvider($$interimElementProvider) {
12451291
newOption = optionsArray[index];
12461292
if (newOption.hasAttribute('disabled')) newOption = undefined;
12471293
} while (!newOption && index < optionsArray.length - 1 && index > 0);
1294+
12481295
newOption && newOption.focus();
12491296
opts.focusedNode = newOption;
12501297
}

src/components/select/select.spec.js

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,81 @@ describe('<md-select>', function() {
339339

340340
}));
341341

342+
describe('md-select-header behavior', function() {
343+
it('supports rendering a md-select-header', inject(function($rootScope, $compile) {
344+
$rootScope.val = [1];
345+
var select = $compile(
346+
'<md-input-container>' +
347+
' <label>Label</label>' +
348+
' <md-select multiple ng-model="val" placeholder="Hello World">' +
349+
' <md-select-header class="demo-select-header">' +
350+
' <span>Hello World</span>' +
351+
' </md-select-header>' +
352+
' <md-optgroup label="stuff">' +
353+
' <md-option value="1">One</md-option>' +
354+
' <md-option value="2">Two</md-option>' +
355+
' <md-option value="3">Three</md-option>' +
356+
' </md-optgroup>' +
357+
' </md-select>' +
358+
'</md-input-container>')($rootScope);
359+
360+
var header = select.find('md-select-header');
361+
var headerContent = header.find('span');
362+
363+
expect(headerContent.text()).toBe('Hello World');
364+
}));
365+
366+
it('does not render the label in md-optgroup if md-select-header is present', inject(function($rootScope, $compile) {
367+
$rootScope.val = [1];
368+
var select = $compile(
369+
'<md-input-container>' +
370+
' <label>Label</label>' +
371+
' <md-select multiple ng-model="val" placeholder="Hello World">' +
372+
' <md-select-header class="demo-select-header">' +
373+
' <span>Hello World</span>' +
374+
' </md-select-header>' +
375+
' <md-optgroup label="stuff">' +
376+
' <md-option value="1">One</md-option>' +
377+
' <md-option value="2">Two</md-option>' +
378+
' <md-option value="3">Three</md-option>' +
379+
' </md-optgroup>' +
380+
' </md-select>' +
381+
'</md-input-container>')($rootScope);
382+
383+
var optgroupLabel = select[0].querySelector('._md-container-ignore');
384+
385+
expect(optgroupLabel).toBe(null);
386+
}));
387+
});
388+
389+
it('does not allow keydown events to propagate from inside the md-select-menu', inject(function($rootScope, $compile) {
390+
$rootScope.val = [1];
391+
var select = $compile(
392+
'<md-input-container>' +
393+
' <label>Label</label>' +
394+
' <md-select multiple ng-model="val" placeholder="Hello World">' +
395+
' <md-option value="1">One</md-option>' +
396+
' <md-option value="2">Two</md-option>' +
397+
' <md-option value="3">Three</md-option>' +
398+
' </md-select>' +
399+
'</md-input-container>')($rootScope);
400+
401+
var mdOption = select.find('md-option');
402+
var selectMenu = select.find('md-select-menu');
403+
var keydownEvent = {
404+
type: 'keydown',
405+
target: mdOption[0],
406+
preventDefault: jasmine.createSpy(),
407+
stopPropagation: jasmine.createSpy()
408+
};
409+
410+
openSelect(select);
411+
angular.element(selectMenu).triggerHandler(keydownEvent);
412+
413+
expect(keydownEvent.preventDefault).toHaveBeenCalled();
414+
expect(keydownEvent.stopPropagation).toHaveBeenCalled();
415+
}));
416+
342417
it('supports raw html', inject(function($rootScope, $compile, $sce) {
343418
$rootScope.val = 0;
344419
$rootScope.opts = [
@@ -580,7 +655,6 @@ describe('<md-select>', function() {
580655

581656
describe('multiple', function() {
582657

583-
584658
describe('model->view', function() {
585659

586660
it('renders initial model value', inject(function($rootScope) {

0 commit comments

Comments
 (0)