From 544381d1cc916f40dbb8e987ce1d654815e3c12f Mon Sep 17 00:00:00 2001 From: Topher Fangio Date: Tue, 23 Jun 2015 20:30:48 -0700 Subject: [PATCH] fix(fabSpeedDial): ng-hide, ng-repeat, animation bug * fixes ability to use ng-hide: the animations were not properly calling the `done()` callback, so the `ng-animate` class was never being removed * you can now use ng-repeat for the speed dial's action items also updated docs to have fewer but more complex examples * the speed dial was not properly initializing it's transforms, so the first time it was opened, it would not animate properly closes #3313, closes #3224, closes #3349 --- src/components/fabActions/fabActions.js | 17 +++- src/components/fabActions/fabActions.spec.js | 50 ++++++++++ .../fabSpeedDial/demoBasicUsage/script.js | 2 +- .../fabSpeedDial/demoMoreOptions/index.html | 93 +++++++++++++++++++ .../fabSpeedDial/demoMoreOptions/script.js | 38 ++++++++ .../style.scss | 12 --- .../fabSpeedDial/demoTooltips/index.html | 59 ------------ .../fabSpeedDial/demoTooltips/script.js | 23 ----- src/components/fabSpeedDial/fabSpeedDial.js | 22 ++++- .../fabSpeedDial/fabSpeedDial.spec.js | 36 +++++++ 10 files changed, 251 insertions(+), 101 deletions(-) create mode 100644 src/components/fabActions/fabActions.spec.js create mode 100644 src/components/fabSpeedDial/demoMoreOptions/index.html create mode 100644 src/components/fabSpeedDial/demoMoreOptions/script.js rename src/components/fabSpeedDial/{demoTooltips => demoMoreOptions}/style.scss (65%) delete mode 100644 src/components/fabSpeedDial/demoTooltips/index.html delete mode 100644 src/components/fabSpeedDial/demoTooltips/script.js diff --git a/src/components/fabActions/fabActions.js b/src/components/fabActions/fabActions.js index 7f186673da9..a30e2552ee6 100644 --- a/src/components/fabActions/fabActions.js +++ b/src/components/fabActions/fabActions.js @@ -26,6 +26,19 @@ require: ['^?mdFabSpeedDial', '^?mdFabToolbar'], + compile: function(element, attributes) { + var children = element.children(); + + // Support both ng-repat and static content + if (children.attr('ng-repeat')) { + children.addClass('md-fab-action-item'); + } else { + // After setting up the listeners, wrap every child in a new div and add a class that we can + // scale/fling independently + children.wrap('
'); + } + }, + link: function(scope, element, attributes, controllers) { // Grab whichever parent controller is used var controller = controllers[0] || controllers[1]; @@ -37,10 +50,6 @@ angular.element(child).on('blur', controller.close); }); } - - // After setting up the listeners, wrap every child in a new div and add a class that we can - // scale/fling independently - element.children().wrap('
'); } } } diff --git a/src/components/fabActions/fabActions.spec.js b/src/components/fabActions/fabActions.spec.js new file mode 100644 index 00000000000..4586750012f --- /dev/null +++ b/src/components/fabActions/fabActions.spec.js @@ -0,0 +1,50 @@ +ddescribe(' directive', function() { + + beforeEach(module('material.components.fabActions')); + + var pageScope, element, controller; + + function compileAndLink(template) { + inject(function($compile, $rootScope) { + pageScope = $rootScope.$new(); + element = $compile(template)(pageScope); + controller = element.controller('mdFabActions'); + + pageScope.$apply(); + }); + } + + it('supports static children', inject(function() { + compileAndLink( + '' + + ' ' + + ' 1' + + ' 2' + + ' 3' + + ' ' + + '' + ); + + expect(element.find("md-fab-actions").children().length).toBe(3); + expect(element.find("md-fab-actions").children()).toHaveClass('md-fab-action-item'); + })); + + it('supports actions created by ng-repeat', inject(function() { + compileAndLink( + '' + + ' ' + + '
{{i}}
' + + '
' + + '
' + ); + + expect(element.find("md-fab-actions").children().length).toBe(3); + expect(element.find("md-fab-actions").children()).toHaveClass('md-fab-action-item'); + + pageScope.$apply('nums=[1,2,3,4]'); + + expect(element.find("md-fab-actions").children().length).toBe(4); + expect(element.find("md-fab-actions").children()).toHaveClass('md-fab-action-item'); + })); + +}); \ No newline at end of file diff --git a/src/components/fabSpeedDial/demoBasicUsage/script.js b/src/components/fabSpeedDial/demoBasicUsage/script.js index 516392c1788..824460332d9 100644 --- a/src/components/fabSpeedDial/demoBasicUsage/script.js +++ b/src/components/fabSpeedDial/demoBasicUsage/script.js @@ -1,7 +1,7 @@ (function() { 'use strict'; - angular.module('fabSpeedDialBasicUsageDemo', ['ngMaterial']) + angular.module('fabSpeedDialDemoBasicUsage', ['ngMaterial']) .controller('DemoCtrl', function() { this.topDirections = ['left', 'up']; this.bottomDirections = ['down', 'right']; diff --git a/src/components/fabSpeedDial/demoMoreOptions/index.html b/src/components/fabSpeedDial/demoMoreOptions/index.html new file mode 100644 index 00000000000..93a7207d88c --- /dev/null +++ b/src/components/fabSpeedDial/demoMoreOptions/index.html @@ -0,0 +1,93 @@ +
+ +

+ The speed dial supports many advanced usage scenarios. This demo shows many of them mixed + together. +

+ +
+ + + + Menu + + + + + +
+ + {{item.name}} + + +
+
+
+
+
+ + +
+

Tooltips

+ +

+ Each action item supports a tooltip using the standard approach as can be seen above. +

+
+ +
+

ngHide

+ +

+ The speed dial also supports hiding using the standard ng-hide attribute. + + + Hide the speed dial. + +

+
+
+ + +
+

ngRepeat

+ +

+ You can easily use ng-repeat with the speed dial, but it requires a slightly + different HTML structure in order to support the necessary DOM changes/styling. +

+ +

+ Simply ensure that your ng-repeat is on a div (or any other tag) + that wraps your items. +

+
+ +
+

$mdDialog

+ +

+ You can also use the buttons to open a dialog. When clicked, the buttons above will open a + dialog showing a message which item was clicked. +

+
+
+ + + +
diff --git a/src/components/fabSpeedDial/demoMoreOptions/script.js b/src/components/fabSpeedDial/demoMoreOptions/script.js new file mode 100644 index 00000000000..8e6eac11510 --- /dev/null +++ b/src/components/fabSpeedDial/demoMoreOptions/script.js @@ -0,0 +1,38 @@ +(function() { + 'use strict'; + + angular.module('fabSpeedDialDemoMoreOptions', ['ngMaterial']) + .controller('DemoCtrl', function($mdDialog) { + var self = this; + + self.hidden = false; + + self.items = [ + {name: "Twitter", icon: "img/icons/twitter.svg", direction: "left" }, + {name: "Facebook", icon: "img/icons/facebook.svg", direction: "right" }, + {name: "Google Hangout", icon: "img/icons/hangout.svg", direction: "left" } + ]; + + self.openDialog = function($event, item) { + // Show the dialog + $mdDialog.show({ + clickOutsideToClose: true, + controller: function($mdDialog) { + // Save the clicked item + this.item = item; + + // Setup some handlers + this.close = function() { + $mdDialog.cancel(); + }; + this.submit = function() { + $mdDialog.hide(); + }; + }, + controllerAs: 'dialog', + templateUrl: 'dialog.html', + targetEvent: $event + }); + } + }); +})(); diff --git a/src/components/fabSpeedDial/demoTooltips/style.scss b/src/components/fabSpeedDial/demoMoreOptions/style.scss similarity index 65% rename from src/components/fabSpeedDial/demoTooltips/style.scss rename to src/components/fabSpeedDial/demoMoreOptions/style.scss index eb48a8ccdc4..5b682cf8ccc 100644 --- a/src/components/fabSpeedDial/demoTooltips/style.scss +++ b/src/components/fabSpeedDial/demoMoreOptions/style.scss @@ -1,15 +1,3 @@ -.text-capitalize { - text-transform: capitalize; -} - -.md-fab:hover, .md-fab.md-focused { - background-color: #000 !important; -} - -p.note { - font-size: 1.2rem; -} - .lock-size { min-width: 300px; min-height: 300px; diff --git a/src/components/fabSpeedDial/demoTooltips/index.html b/src/components/fabSpeedDial/demoTooltips/index.html deleted file mode 100644 index c6dd1e974ee..00000000000 --- a/src/components/fabSpeedDial/demoTooltips/index.html +++ /dev/null @@ -1,59 +0,0 @@ -
- -

- You may add tooltips to both the trigger and actions...
- Note: you can easily open a dialog with a Fab action. -

- -
- - - - Menu - - - - - - - Twitter - - - - - Facebook - - - - - Google Hangout - - - - - Open dialog - - - - -
-
- - - -
diff --git a/src/components/fabSpeedDial/demoTooltips/script.js b/src/components/fabSpeedDial/demoTooltips/script.js deleted file mode 100644 index b3da36b86ff..00000000000 --- a/src/components/fabSpeedDial/demoTooltips/script.js +++ /dev/null @@ -1,23 +0,0 @@ -(function() { - 'use strict'; - - angular.module('fabSpeedDialModalDemo', ['ngMaterial']) - .controller('DemoCtrl', function($mdDialog) { - this.openDialog = function($event) { - $mdDialog.show({ - clickOutsideToClose: true, - controller: function($mdDialog) { - this.close = function() { - $mdDialog.cancel(); - }; - this.submit = function() { - $mdDialog.hide(); - }; - }, - controllerAs: 'dialog', - templateUrl: 'dialog.html', - targetEvent: $event - }); - } - }); -})(); diff --git a/src/components/fabSpeedDial/fabSpeedDial.js b/src/components/fabSpeedDial/fabSpeedDial.js index 61c4751e152..0f7f1b8305e 100644 --- a/src/components/fabSpeedDial/fabSpeedDial.js +++ b/src/components/fabSpeedDial/fabSpeedDial.js @@ -2,14 +2,23 @@ 'use strict'; angular + // Declare our module .module('material.components.fabSpeedDial', [ 'material.core', 'material.components.fabTrigger', 'material.components.fabActions' ]) + + // Register our directive .directive('mdFabSpeedDial', MdFabSpeedDialDirective) + + // Register our custom animations .animation('.md-fling', MdFabSpeedDialFlingAnimation) - .animation('.md-scale', MdFabSpeedDialScaleAnimation); + .animation('.md-scale', MdFabSpeedDialScaleAnimation) + + // Register a service for each animation so that we can easily inject them into unit tests + .service('mdFabSpeedDialFlingAnimation', MdFabSpeedDialFlingAnimation) + .service('mdFabSpeedDialScaleAnimation', MdFabSpeedDialScaleAnimation); /** * @ngdoc directive @@ -73,7 +82,7 @@ element.prepend('
'); } - function FabSpeedDialController($scope, $element, $animate) { + function FabSpeedDialController($scope, $element, $animate, $timeout) { var vm = this; // Define our open/close functions @@ -103,6 +112,11 @@ setupListeners(); setupWatchers(); + // Fire the animations once in a separate digest loop to initialize them + $timeout(function() { + $animate.addClass($element, 'md-noop'); + }, 0); + // Set our default variables function setupDefaults() { // Set the default direction to 'down' if none is specified @@ -199,10 +213,12 @@ addClass: function(element, className, done) { if (element.hasClass('md-fling')) { runAnimation(element); + done(); } }, removeClass: function(element, className, done) { runAnimation(element); + done(); } } } @@ -229,10 +245,12 @@ return { addClass: function(element, className, done) { runAnimation(element); + done(); }, removeClass: function(element, className, done) { runAnimation(element); + done(); } } } diff --git a/src/components/fabSpeedDial/fabSpeedDial.spec.js b/src/components/fabSpeedDial/fabSpeedDial.spec.js index 3545a5cd9ff..c6b2f40d425 100644 --- a/src/components/fabSpeedDial/fabSpeedDial.spec.js +++ b/src/components/fabSpeedDial/fabSpeedDial.spec.js @@ -83,4 +83,40 @@ describe(' directive', function() { expect(controller.isOpen).toBe(false); })); + it('properly finishes the fling animation', inject(function(mdFabSpeedDialFlingAnimation) { + compileAndLink( + '' + + ' ' + + ' ' + + '' + ); + + var addDone = jasmine.createSpy('addDone'); + var removeDone = jasmine.createSpy('removeDone'); + + mdFabSpeedDialFlingAnimation.addClass(element, 'md-is-open', addDone); + expect(addDone).toHaveBeenCalled(); + + mdFabSpeedDialFlingAnimation.removeClass(element, 'md-is-open', removeDone); + expect(removeDone).toHaveBeenCalled(); + })); + + it('properly finishes the scale animation', inject(function(mdFabSpeedDialScaleAnimation) { + compileAndLink( + '' + + ' ' + + ' ' + + '' + ); + + var addDone = jasmine.createSpy('addDone'); + var removeDone = jasmine.createSpy('removeDone'); + + mdFabSpeedDialScaleAnimation.addClass(element, 'md-is-open', addDone); + expect(addDone).toHaveBeenCalled(); + + mdFabSpeedDialScaleAnimation.removeClass(element, 'md-is-open', removeDone); + expect(removeDone).toHaveBeenCalled(); + })); + });