From 758ea6c8fa323791b31e537724c1344b82bdc402 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Wed, 18 May 2016 22:43:23 +0200 Subject: [PATCH] fix(input): add support for dynamically loaded ngMessage directives. * Currently ngMessage directives inside of an input-container, which are loaded / transcluded dynamically, didn't get initialized properly. The animation was not working. As well as the auto-hide didn't work properly. Fixes #7477. Fixes #7596. Fixes #6810. Fixes #7823 --- src/components/input/input.js | 41 +++++++++++++++++++---- src/components/input/input.spec.js | 53 ++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 7 deletions(-) diff --git a/src/components/input/input.js b/src/components/input/input.js index cee6929c94f..f8c84a4be4e 100644 --- a/src/components/input/input.js +++ b/src/components/input/input.js @@ -717,16 +717,43 @@ function ngMessageDirective($mdUtil) { priority: 100 }; - function compile(element) { - var inputContainer = $mdUtil.getClosest(element, "md-input-container"); + function compile(tElement) { + if (!isInsideInputContainer(tElement)) { + + // When the current element is inside of a document fragment, then we need to check for an input-container + // in the postLink, because the element will be later added to the DOM and is currently just in a temporary + // fragment, which causes the input-container check to fail. + if (isInsideFragment()) { + return function (scope, element) { + if (isInsideInputContainer(element)) { + // Inside of the postLink function, a ngMessage directive will be a comment element, because it's + // currently hidden. To access the shown element, we need to use the element from the compile function. + initMessageElement(tElement); + } + }; + } + } else { + initMessageElement(tElement); + } - // If we are not a child of an input container, don't do anything - if (!inputContainer) return; + function isInsideFragment() { + var nextNode = tElement[0]; + while (nextNode = nextNode.parentNode) { + if (nextNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + return true; + } + } + return false; + } - // Add our animation class - element.toggleClass('md-input-message-animation', true); + function isInsideInputContainer(element) { + return !!$mdUtil.getClosest(element, "md-input-container"); + } - return {}; + function initMessageElement(element) { + // Add our animation class + element.toggleClass('md-input-message-animation', true); + } } } diff --git a/src/components/input/input.spec.js b/src/components/input/input.spec.js index 0605cc6e26f..ee5dff14d70 100644 --- a/src/components/input/input.spec.js +++ b/src/components/input/input.spec.js @@ -436,6 +436,59 @@ describe('md-input-container directive', function() { expect(el[0].querySelector("[ng-messages]").classList.contains('md-auto-hide')).toBe(false); })); + it('should set the animation class on the ngMessage properly', inject(function() { + var element = compile( + '' + + '' + + '
' + + 'Field required' + + '
' + + '
' + ); + + var ngMessage = element.find('ng-message'); + expect(ngMessage).toHaveClass('md-input-message-animation'); + })); + + it('should set the animation class on a transcluded ngMessage', function() { + // We can emulate the transclusion, by wrapping the ngMessage inside of a document fragment. + // It is not necessary to add a *extra* component / directive for that, since we just + // want to the test the DocumentFragment detection. + var fragment = document.createDocumentFragment(); + + var inputContainer = compile( + '' + + '' + + '
' + + '
' + + '
' + ); + + // We build our element, without compiling and linking it. + // Because we invoke those steps manually during the tests. + var messageElement = angular.element( + 'Field Required' + ); + + fragment.appendChild(messageElement[0]); + + // Only compile the element at this time, and link it to its scope later. + // Normally the directive will add the animation class upon compile. + var linkFn = $compile(messageElement); + + expect(messageElement).not.toHaveClass('md-input-message-animation'); + + // Now we emulate the finish of the transclusion. + // We move the element from the fragment into the correct input + // container. + inputContainer[0].appendChild(messageElement[0]); + + // Manually invoke the postLink function of the directive. + linkFn($rootScope.$new()); + + expect(messageElement).toHaveClass('md-input-message-animation'); + }); + it('should select the input value on focus', inject(function($timeout) { var container = setup('md-select-on-focus'); var input = container.find('input');