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

Commit c6d08bb

Browse files
Derek Louiejelbourn
authored andcommitted
feat(select): Adding md-select-header directive to md-select.
Closes #7782
1 parent d9682e2 commit c6d08bb

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:
@@ -210,7 +232,11 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par
210232
};
211233

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

216242
containerCtrl.input = element;
@@ -503,11 +529,13 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $compile, $par
503529
}
504530

505531
function SelectMenuDirective($parse, $mdUtil, $mdTheming) {
506-
532+
// We want the scope to be set to 'false' so an isolated scope is not created
533+
// which would interfere with the md-select-header's access to the
534+
// parent scope.
507535
return {
508536
restrict: 'E',
509537
require: ['mdSelectMenu'],
510-
scope: true,
538+
scope: false,
511539
controller: SelectMenuController,
512540
link: {pre: preLink}
513541
};
@@ -891,16 +919,34 @@ function OptgroupDirective() {
891919
compile: compile
892920
};
893921
function compile(el, attrs) {
894-
var labelElement = el.find('label');
895-
if (!labelElement.length) {
896-
labelElement = angular.element('<label>');
897-
el.prepend(labelElement);
922+
// If we have a select header element, we don't want to add the normal label
923+
// header.
924+
if (!hasSelectHeader()) {
925+
setupLabelElement();
926+
}
927+
928+
function hasSelectHeader() {
929+
return el.parent().find('md-select-header').length;
930+
}
931+
932+
function setupLabelElement() {
933+
var labelElement = el.find('label');
934+
if (!labelElement.length) {
935+
labelElement = angular.element('<label>');
936+
el.prepend(labelElement);
937+
}
938+
labelElement.addClass('md-container-ignore');
939+
if (attrs.label) labelElement.text(attrs.label);
898940
}
899-
labelElement.addClass('md-container-ignore');
900-
if (attrs.label) labelElement.text(attrs.label);
901941
}
902942
}
903943

944+
function SelectHeaderDirective() {
945+
return {
946+
restrict: 'E',
947+
};
948+
}
949+
904950
function SelectProvider($$interimElementProvider) {
905951
return $$interimElementProvider('$mdSelect')
906952
.setDefaults({
@@ -1241,6 +1287,7 @@ function SelectProvider($$interimElementProvider) {
12411287
newOption = optionsArray[index];
12421288
if (newOption.hasAttribute('disabled')) newOption = undefined;
12431289
} while (!newOption && index < optionsArray.length - 1 && index > 0);
1290+
12441291
newOption && newOption.focus();
12451292
opts.focusedNode = newOption;
12461293
}

src/components/select/select.spec.js

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

334334
}));
335335

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

575650
describe('multiple', function() {
576651

577-
578652
describe('model->view', function() {
579653

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

0 commit comments

Comments
 (0)