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

Commit

Permalink
fix(subheader): Fix thrown error when using md-subheader with ng-if a…
Browse files Browse the repository at this point in the history
…nd ng-repeat.

When used with `ng-if` or `ng-repeat`, the `md-subheader` was not able to properly
clone itself when creating a sticky because it only had a comment tag. Delay
initialization until after the `ng-if`/`ng-repeat` has finished before attempting
to create the clone.

fixes #2650. fixes #2980. closes #4171.
  • Loading branch information
topherfangio authored and ThomasBurleson committed Aug 18, 2015
1 parent 3598e6d commit dbca2a4
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 59 deletions.
5 changes: 5 additions & 0 deletions src/components/subheader/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

<div ng-controller="SubheaderAppCtrl" layout="column" flex layout-fill>
<md-content style="height: 600px;" md-theme="altTheme">

<section>
<md-subheader class="md-primary">Unread Messages</md-subheader>
<md-list layout-padding>
Expand All @@ -16,6 +17,7 @@ <h4>{{message.who}}</h4>
</md-list-item>
</md-list>
</section>

<section>
<md-subheader class="md-warn">Late Messages</md-subheader>
<md-list layout="column" layout-padding>
Expand All @@ -31,6 +33,7 @@ <h4>{{message.who}}</h4>
</md-list-item>
</md-list>
</section>

<section>
<md-subheader>Read Messages</md-subheader>
<md-list layout="column" layout-padding>
Expand All @@ -46,6 +49,7 @@ <h4>{{message.who}}</h4>
</md-list-item>
</md-list>
</section>

<section>
<md-subheader class="md-accent">Archived messages</md-subheader>
<md-list layout="column" layout-padding>
Expand All @@ -61,5 +65,6 @@ <h4>{{message.who}}</h4>
</md-list-item>
</md-list>
</section>

</md-content>
</div>
62 changes: 35 additions & 27 deletions src/components/subheader/subheader.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,42 +40,50 @@ angular.module('material.components.subheader', [
* </hljs>
*/

function MdSubheaderDirective($mdSticky, $compile, $mdTheming) {
function MdSubheaderDirective($mdSticky, $compile, $mdTheming, $mdUtil) {
return {
restrict: 'E',
replace: true,
transclude: true,
template:
'<h2 class="md-subheader">' +
'<div class="md-subheader-inner">' +
'<span class="md-subheader-content"></span>' +
'</div>' +
'</h2>',
compile: function(element, attr, transclude) {
return function postLink(scope, element, attr) {
$mdTheming(element);
var outerHTML = element[0].outerHTML;
template: (
'<h2 class="md-subheader">' +
' <div class="md-subheader-inner">' +
' <span class="md-subheader-content"></span>' +
' </div>' +
'</h2>'
),
link: function postLink(scope, element, attr, controllers, transclude) {
$mdTheming(element);
var outerHTML = element[0].outerHTML;

function getContent(el) {
return angular.element(el[0].querySelector('.md-subheader-content'));
}
function getContent(el) {
return angular.element(el[0].querySelector('.md-subheader-content'));
}

// Transclude the user-given contents of the subheader
// the conventional way.
// Transclude the user-given contents of the subheader
// the conventional way.
transclude(scope, function(clone) {
getContent(element).append(clone);
});

// Create another clone, that uses the outer and inner contents
// of the element, that will be 'stickied' as the user scrolls.
if (!element.hasClass('md-no-sticky')) {
transclude(scope, function(clone) {
getContent(element).append(clone);
});
// Wrap to avoid multiple transclusion errors on same element, then append the sticky
var wrapperHtml = '<div class="md-subheader-wrapper">' + outerHTML + '</div>';
var stickyClone = $compile(wrapperHtml)(scope);

$mdSticky(scope, element, stickyClone);

// Create another clone, that uses the outer and inner contents
// of the element, that will be 'stickied' as the user scrolls.
if (!element.hasClass('md-no-sticky')) {
transclude(scope, function(clone) {
var stickyClone = $compile(angular.element(outerHTML))(scope);
// Delay initialization until after any `ng-if`/`ng-repeat`/etc has finished before
// attempting to create the clone

$mdUtil.nextTick(function() {
getContent(stickyClone).append(clone);
$mdSticky(scope, element, stickyClone);
});
}
};
});
}
}
};
}
}
37 changes: 24 additions & 13 deletions src/components/subheader/subheader.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,14 @@ $subheader-sticky-shadow: 0px 2px 4px 0 rgba(0,0,0,0.16) !default;
}
}

.md-subheader {
display: block;
font-size: $subheader-font-size;
font-weight: $subheader-font-weight;
line-height: $subheader-line-height;
margin: $subheader-margin;
margin-right: $subheader-margin-right;
position: relative;

.md-subheader-inner {
padding: $subheader-padding;
}
.md-subheader-wrapper {

&:not(.md-sticky-no-effect) {
&:after {
.md-subheader {
margin: 0;
}

.md-subheader:after {
position: absolute;
left: 0;
bottom: 0;
Expand All @@ -47,17 +40,35 @@ $subheader-sticky-shadow: 0px 2px 4px 0 rgba(0,0,0,0.16) !default;
}

transition: 0.2s ease-out margin;

&.md-sticky-clone {
z-index: 2;
}

&[sticky-state="active"] {
margin-top: -2px;
}

&:not(.md-sticky-clone)[sticky-prev-state="active"] .md-subheader-inner:after {
animation: subheaderStickyHoverOut 0.3s ease-out both;
}
}

}

.md-subheader {
display: block;
font-size: $subheader-font-size;
font-weight: $subheader-font-weight;
line-height: $subheader-line-height;
margin: $subheader-margin;
margin-right: $subheader-margin-right;
position: relative;

.md-subheader-inner {
padding: $subheader-padding;
}

.md-subheader-content {
z-index: 1;
position: relative;
Expand Down
90 changes: 71 additions & 19 deletions src/components/subheader/subheader.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
describe('mdSubheader', function() {
var $mdStickyMock,
basicHtml = '<md-subheader>Hello world!</md-header>';
basicHtml = '<md-subheader>Hello world!</md-header>',
pageScope, element, controller;

var $rootScope, $timeout, $exceptionHandler;

beforeEach(module('material.components.subheader', function($provide) {
$mdStickyMock = function() {
Expand All @@ -9,28 +12,77 @@ describe('mdSubheader', function() {
$provide.value('$mdSticky', $mdStickyMock);
}));


it('should preserve content', inject(function($compile, $rootScope) {
var $scope = $rootScope.$new();
$scope.to = 'world';
var $el = $compile('<div><md-subheader>Hello {{ to }}!</md-subheader></div>')($scope);
$scope.$digest();
var $subHeader = $el.children();
expect($subHeader.text()).toEqual('Hello world!');
beforeEach(inject(function(_$rootScope_, _$timeout_, _$exceptionHandler_) {
$rootScope = _$rootScope_;
$timeout = _$timeout_;
$exceptionHandler = _$exceptionHandler_;
}));

it('should implement $mdSticky', inject(function($compile, $rootScope) {
var scope = $rootScope.$new();
var $el = $compile(basicHtml)(scope);
expect($mdStickyMock.args[0]).toBe(scope);
}));
it('preserves content', function() {
build('<div><md-subheader>Hello {{ to }}!</md-subheader></div>');
pageScope.to = 'world';
pageScope.$digest();

var subHeader = element.children();

expect(subHeader.text().trim()).toEqual('Hello world!');
});

it('implements $mdSticky', function() {
build(basicHtml);

it('should apply the theme to the header and clone', inject(function($compile, $rootScope) {
var scope = $rootScope.$new();
$compile('<div md-theme="somethingElse">' + basicHtml + '</div>')(scope);
expect($mdStickyMock.args[0]).toBe(pageScope);
});

it('applies the theme to the header and clone', function() {
build('<div md-theme="somethingElse">' + basicHtml + '</div>');

// Grab the real element
var element = $mdStickyMock.args[1];
var clone = $mdStickyMock.args[2];

// The subheader now wraps the clone in a DIV in case of ng-if usage, so we have to search for
// the proper element.
var clone = angular.element($mdStickyMock.args[2][0].querySelector('.md-subheader'));

expect(element.hasClass('md-somethingElse-theme')).toBe(true);
expect(clone.hasClass('md-somethingElse-theme')).toBe(true);
}));
});

it('applies the proper scope to the clone', function() {
build('<div><md-subheader>Hello {{ to }}!</md-subheader></div>');

pageScope.to = 'world';
pageScope.$apply();

var element = $mdStickyMock.args[1];
var clone = $mdStickyMock.args[2];

expect(element.text().trim()).toEqual('Hello world!');
expect(clone.text().trim()).toEqual('Hello world!');
});

it('supports ng-if', function() {
build('<div><md-subheader ng-if="true">test</md-subheader></div>');

expect($exceptionHandler.errors).toEqual([]);
expect(element[0].querySelectorAll('.md-subheader').length).toEqual(1);
});

it('supports ng-repeat', function() {
build('<div><md-subheader ng-repeat="i in [1,2,3]">Test {{i}}</md-subheader></div>');

expect($exceptionHandler.errors).toEqual([]);
expect(element[0].querySelectorAll('.md-subheader').length).toEqual(3);
});

function build(template) {
inject(function($compile) {
pageScope = $rootScope.$new();
element = $compile(template)(pageScope);
controller = element.controller('mdSubheader');

pageScope.$apply();
$timeout.flush();
});
}
});

0 comments on commit dbca2a4

Please sign in to comment.