Permalink
Browse files

feat(ng): add directive for visible and invisible elements

when you need to preserve a hidden elements dom space ngVisible and ngInvisible will allow you to do so
  • Loading branch information...
jacobscarter committed Jun 16, 2015
1 parent 46b7cf7 commit 4e341a49190b7f3502eb39745fdeb7cdbb14ed6f
View
@@ -73,6 +73,7 @@ var angularFiles = {
'src/ng/directive/ngStyle.js',
'src/ng/directive/ngSwitch.js',
'src/ng/directive/ngTransclude.js',
+ 'src/ng/directive/ngVisibleInvisible.js',
'src/ng/directive/script.js',
'src/ng/directive/select.js',
'src/ng/directive/style.js',
View
@@ -17,3 +17,7 @@ ng\:form {
.ng-anchor {
position:absolute;
}
+
+.ng-invisible:not(.ng-invisible-animate) {
+ visibility: hidden !important;
+}
View
@@ -29,6 +29,7 @@
ngIncludeDirective,
ngIncludeFillContentDirective,
ngInitDirective,
+ ngInvisibleDirective
ngNonBindableDirective,
ngPluralizeDirective,
ngRepeatDirective,
@@ -39,6 +40,7 @@
ngSwitchDefaultDirective,
ngOptionsDirective,
ngTranscludeDirective,
+ ngVisibleDirective,
ngModelDirective,
ngListDirective,
ngChangeDirective,
@@ -187,6 +189,7 @@ function publishExternalAPI(angular) {
ngIf: ngIfDirective,
ngInclude: ngIncludeDirective,
ngInit: ngInitDirective,
+ ngInvisible: ngInvisibleDirective,
ngNonBindable: ngNonBindableDirective,
ngPluralize: ngPluralizeDirective,
ngRepeat: ngRepeatDirective,
@@ -197,6 +200,7 @@ function publishExternalAPI(angular) {
ngSwitchDefault: ngSwitchDefaultDirective,
ngOptions: ngOptionsDirective,
ngTransclude: ngTranscludeDirective,
+ ngVisible: ngVisibleDirective,
ngModel: ngModelDirective,
ngList: ngListDirective,
ngChange: ngChangeDirective,
@@ -0,0 +1,326 @@
+'use strict';
+
+var NG_INVISIBLE_CLASS = 'ng-invisible';
+var NG_INVISIBLE_IN_PROGRESS_CLASS = 'ng-invisible-animate';
+/**
+ * @ngdoc directive
+ * @name ngVisible
+ *
+ * @description
+ * The `ngVisible` directive shows or hides the given HTML element based on the expression
+ * provided to the `ngVisible` attribute. The element is shown or hidden by removing or adding
+ * the `.ng-invisible` CSS class onto the element. The `.ng-invisible` CSS class is predefined
+ * in AngularJS and sets the visiblity style to hidden (using an !important flag). The visibility property
+ * hides an element while leaving the space where it would have been.
+ * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
+ *
+ * ```html
+ * <!-- when $scope.myValue is truthy (element is visible) -->
+ * <div ng-visible="myValue"></div>
+ *
+ * <!-- when $scope.myValue is falsy (element is hidden) -->
+ * <div ng-visible="myValue" class="ng-invisible"></div>
+ * ```
+ *
+ * When the `ngVisible` expression evaluates to a falsy value then the `.ng-invisible` CSS class is added to the class
+ * attribute on the element causing it to become hidden. When truthy, the `.ng-invisible` CSS class is removed
+ * from the element causing the element not to appear hidden.
+ *
+ * ## Why is !important used?
+ *
+ * You may be wondering why !important is used for the `.ng-invisible` CSS class. This is because the `.ng-invisible` selector
+ * can be easily overridden by heavier selectors. For example, something as simple
+ * as changing the display style on a HTML list item would make hidden elements appear visible.
+ * This also becomes a bigger issue when dealing with CSS frameworks.
+ *
+ * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
+ * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
+ * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
+ *
+ * ### Overriding `.ng-invisible`
+ *
+ * By default, the `.ng-invisible` class will style the element with `visibility: hidden!important`. If you wish to change
+ * the hide behavior with ngVisible/ngInvisible then this can be achieved by restating the styles for the `.ng-invisible`
+ * class CSS. Note that the selector that needs to be used is actually `.ng-invisible:not(.ng-invisible-animate)` to cope
+ * with extra animation classes that can be added.
+ *
+ * ```css
+ * .ng-invisible:not(.ng-invisible-animate) {
+ * /&#42; this is just another form of hiding an element &#42;/
+ * visibility: visible!important;
+ * position: absolute;
+ * top: -9999px;
+ * left: -9999px;
+ * }
+ * ```
+ *
+ * By default you don't need to override in CSS anything and the animations will work around the display style.
+ *
+ * ## A note about animations with `ngVisible`
+ *
+ * Animations in ngVisible/ngInvisible work with the visible and invisible events that are triggered when the directive expression
+ * is true and false. This system works like the animation system present with ngClass except that
+ * you must also include the !important flag to override the display property
+ * so that you can perform an animation when the element is hidden during the time of the animation.
+ *
+ * ```css
+ * //
+ * //a working example can be found at the bottom of this page
+ * //
+ * .my-element.ng-invisible-add, .my-element.ng-invisible-remove {
+ * /&#42; this is required as of 1.3x to properly
+ * apply all styling in a show/hide animation &#42;/
+ * transition: 0s linear all;
+ * }
+ *
+ * .my-element.ng-invisible-add-active,
+ * .my-element.ng-invisible-remove-active {
+ * /&#42; the transition is defined in the active class &#42;/
+ * transition: 1s linear all;
+ * }
+ *
+ * .my-element.ng-invisible-add { ... }
+ * .my-element.ng-invisible-add.ng-invisible-add-active { ... }
+ * .my-element.ng-invisible-remove { ... }
+ * .my-element.ng-invisible-remove.ng-invisible-remove-active { ... }
+ * ```
+ *
+ * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
+ * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
+ *
+ * @animations
+ * addClass: `.ng-invisible` - happens after the `ngVisible` expression evaluates to a truthy value and the just before contents are set to visible
+ * removeClass: `.ng-invisible` - happens after the `ngVisible` expression evaluates to a non truthy value and just before the contents are set to hidden
+ *
+ * @element ANY
+ * @param {expression} ngVisible If the {@link guide/expression expression} is truthy
+ * then the element is shown or hidden respectively.
+ *
+ * @example
+ <example module="ngAnimate" deps="angular-animate.js" animations="true">
+ <file name="index.html">
+ Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/>
+ <div>
+ Show:
+ <div class="check-element animate-visible" ng-visible="checked">
+ <span class="glyphicon glyphicon-thumbs-up"></span> I am visible when your checkbox is checked.
+ </div>
+ </div>
+ <div>
+ Hide:
+ <div class="check-element animate-visible" ng-invisible="checked">
+ <span class="glyphicon glyphicon-thumbs-down"></span> I am invisible when your checkbox is checked.
+ </div>
+ </div>
+ </file>
+ <file name="glyphicons.css">
+ @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
+ </file>
+ <file name="animations.css">
+ .animate-visible {
+ line-height: 20px;
+ opacity: 1;
+ padding: 10px;
+ border: 1px solid black;
+ background: white;
+ }
+ .animate-visible.ng-invisible-add.ng-invisible-add-active,
+ .animate-visible.ng-invisible-remove.ng-invisible-remove-active {
+ -webkit-transition: all linear 0.5s;
+ transition: all linear 0.5s;
+ }
+ .animate-visible.ng-invisible {
+ line-height: 0;
+ opacity: 0;
+ padding: 10px;
+ }
+ .check-element {
+ padding: 10px;
+ border: 1px solid black;
+ background: white;
+ }
+ </file>
+ <file name="protractor.js" type="protractor">
+ var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
+ var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
+ it('should check ng-visible / ng-invisible', function() {
+ expect(thumbsUp.isDisplayed()).toBeFalsy();
+ expect(thumbsDown.isDisplayed()).toBeTruthy();
+ element(by.model('checked')).click();
+ expect(thumbsUp.isDisplayed()).toBeTruthy();
+ expect(thumbsDown.isDisplayed()).toBeFalsy();
+ });
+ </file>
+ </example>
+ */
+var ngVisibleDirective = ['$animate', function($animate) {
+ return {
+ restrict: 'A',
+ multiElement: true,
+ link: function(scope, element, attr) {
+ scope.$watch(attr.ngVisible, function ngVisibleWatchAction(value) {
+ // we're adding a temporary, animation-specific class for ng-invisible since this way
+ // we can control when the element is actually displayed on screen without having
+ // to have a global/greedy CSS selector that breaks when other animations are run.
+ $animate[value ? 'removeClass' : 'addClass'](element, NG_INVISIBLE_CLASS, {
+ tempClasses: NG_INVISIBLE_IN_PROGRESS_CLASS
+ });
+ });
+ }
+ };
+}];
+
+
+/**
+ * @ngdoc directive
+ * @name ngInvisible
+ *
+ * @description
+ * The `ngInvisible` directive shows or hides the given HTML element based on the expression
+ * provided to the `ngInvisible` attribute. The element is shown or hidden by removing or adding
+ * the `.ng-invisible` CSS class onto the element. The `.ng-invisible` CSS class is predefined
+ * in AngularJS and sets the visiblity to hidden (using an !important flag). The visibility property
+ * hides an element while leaving the space where it would have been.
+ * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}).
+ *
+ * ```html
+ * <!-- when $scope.myValue is truthy (element is hidden) -->
+ * <div ng-invisible="myValue" class="ng-invisible"></div>
+ *
+ * <!-- when $scope.myValue is falsy (element is visible) -->
+ * <div ng-invisible="myValue"></div>
+ * ```
+ *
+ * When the `ngInvisible` expression evaluates to a truthy value then the `.ng-invisible` CSS class is added to the class
+ * attribute on the element causing it to become hidden. When falsy, the `.ng-invisible` CSS class is removed
+ * from the element causing the element not to appear hidden.
+ *
+ * ## Why is !important used?
+ *
+ * You may be wondering why !important is used for the `.ng-invisible` CSS class. This is because the `.ng-invisible` selector
+ * can be easily overridden by heavier selectors. For example, something as simple
+ * as changing the display style on a HTML list item would make hidden elements appear visible.
+ * This also becomes a bigger issue when dealing with CSS frameworks.
+ *
+ * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector
+ * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the
+ * styling to change how to hide an element then it is just a matter of using !important in their own CSS code.
+ *
+ * ### Overriding `.ng-invisible`
+ *
+ * By default, the `.ng-invisible` class will style the element with `visibility: hidden!important`. If you wish to change
+ * the hide behavior with ngVisible/ngInvisible then this can be achieved by restating the styles for the `.ng-invisible`
+ * class in CSS:
+ *
+ * ```css
+ * .ng-invisible {
+ * /&#42; this is just another form of hiding an element &#42;/
+ * visibility: visible!important;
+ * position: absolute;
+ * top: -9999px;
+ * left: -9999px;
+ * }
+ * ```
+ *
+ * By default you don't need to override in CSS anything and the animations will work around the display style.
+ *
+ * ## A note about animations with `ngInvisible`
+ *
+ * Animations in ngVisible/ngInvisible work with the show and hide events that are triggered when the directive expression
+ * is true and false. This system works like the animation system present with ngClass, except that the `.ng-invisible`
+ * CSS class is added and removed for you instead of your own CSS class.
+ *
+ * ```css
+ * //
+ * //a working example can be found at the bottom of this page
+ * //
+ * .my-element.ng-invisible-add, .my-element.ng-invisible-remove {
+ * transition: 0.5s linear all;
+ * }
+ *
+ * .my-element.ng-invisible-add { ... }
+ * .my-element.ng-invisible-add.ng-invisible-add-active { ... }
+ * .my-element.ng-invisible-remove { ... }
+ * .my-element.ng-invisible-remove.ng-invisible-remove-active { ... }
+ * ```
+ *
+ * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display
+ * property to block during animation states--ngAnimate will handle the style toggling automatically for you.
+ *
+ * @animations
+ * removeClass: `.ng-invisible` - happens after the `ngInvisible` expression evaluates to a truthy value and just before the contents are set to hidden
+ * addClass: `.ng-invisible` - happens after the `ngInvisible` expression evaluates to a non truthy value and just before the contents are set to visible
+ *
+ * @element ANY
+ * @param {expression} ngInvisible If the {@link guide/expression expression} is truthy then
+ * the element is shown or hidden respectively.
+ *
+ * @example
+ <example module="ngAnimate" deps="angular-animate.js" animations="true">
+ <file name="index.html">
+ Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngValidate"><br/>
+ <div>
+ Show:
+ <div class="check-element animate-hide" ng-visible="checked">
+ <span class="glyphicon glyphicon-thumbs-up"></span> I am visible when your checkbox is checked.
+ </div>
+ </div>
+ <div>
+ Hide:
+ <div class="check-element animate-hide" ng-invisible="checked">
+ <span class="glyphicon glyphicon-thumbs-down"></span> I am invisible when your checkbox is checked.
+ </div>
+ </div>
+ </file>
+ <file name="glyphicons.css">
+ @import url(../../components/bootstrap-3.1.1/css/bootstrap.css);
+ </file>
+ <file name="animations.css">
+ .animate-hide {
+ -webkit-transition: all linear 0.5s;
+ transition: all linear 0.5s;
+ line-height: 20px;
+ opacity: 1;
+ padding: 10px;
+ border: 1px solid black;
+ background: white;
+ }
+ .animate-hide.ng-invisible {
+ line-height: 0;
+ opacity: 0;
+ padding: 10px;
+ }
+ .check-element {
+ padding: 10px;
+ border: 1px solid black;
+ background: white;
+ }
+ </file>
+ <file name="protractor.js" type="protractor">
+ var thumbsUp = element(by.css('span.glyphicon-thumbs-up'));
+ var thumbsDown = element(by.css('span.glyphicon-thumbs-down'));
+ it('should check ng-visible / ng-invisible', function() {
+ expect(thumbsUp.isDisplayed()).toBeFalsy();
+ expect(thumbsDown.isDisplayed()).toBeTruthy();
+ element(by.model('checked')).click();
+ expect(thumbsUp.isDisplayed()).toBeTruthy();
+ expect(thumbsDown.isDisplayed()).toBeFalsy();
+ });
+ </file>
+ </example>
+ */
+var ngInvisibleDirective = ['$animate', function($animate) {
+ return {
+ restrict: 'A',
+ multiElement: true,
+ link: function(scope, element, attr) {
+ scope.$watch(attr.ngInvisible, function ngInvisibleWatchAction(value) {
+ // The comment inside of the ngVisibleDirective explains why we add and
+ // remove a temporary class for the visible/invisible animation
+ $animate[value ? 'addClass' : 'removeClass'](element,NG_INVISIBLE_CLASS, {
+ tempClasses: NG_INVISIBLE_IN_PROGRESS_CLASS
+ });
+ });
+ }
+ };
+}];
Oops, something went wrong.

0 comments on commit 4e341a4

Please sign in to comment.