From a6b9d5390d27ef8d277374055b35ff268794dc28 Mon Sep 17 00:00:00 2001 From: sashless Date: Mon, 8 Dec 2014 14:41:37 +0100 Subject: [PATCH 01/62] make it useable in xhtml documents --- src/modal/modal.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modal/modal.js b/src/modal/modal.js index 662765d357..c52d53d0d2 100644 --- a/src/modal/modal.js +++ b/src/modal/modal.js @@ -247,13 +247,13 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) if (currBackdropIndex >= 0 && !backdropDomEl) { backdropScope = $rootScope.$new(true); backdropScope.index = currBackdropIndex; - var angularBackgroundDomEl = angular.element('
'); + var angularBackgroundDomEl = angular.element('
'); angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass); backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope); body.append(backdropDomEl); } - var angularDomEl = angular.element('
'); + var angularDomEl = angular.element('
'); angularDomEl.attr({ 'template-url': modal.windowTemplateUrl, 'window-class': modal.windowClass, From 20a01cefcc42c69202fa7175e6df5393dfeec3ca Mon Sep 17 00:00:00 2001 From: Matt Evanoff Date: Tue, 27 Jan 2015 21:30:51 -0500 Subject: [PATCH 02/62] docs(accordion): add is-open Closes #3230 --- src/accordion/docs/readme.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/accordion/docs/readme.md b/src/accordion/docs/readme.md index 5b3ae93b39..1a67460cbe 100644 --- a/src/accordion/docs/readme.md +++ b/src/accordion/docs/readme.md @@ -2,4 +2,9 @@ The **accordion directive** builds on top of the collapse directive to provide a We can control whether expanding an item will cause the other items to close, using the `close-others` attribute on accordion. -The body of each accordion group is transcluded in to the body of the collapsible element. \ No newline at end of file +The body of each accordion group is transcluded in to the body of the collapsible element. + +### Accordion Settings ### + + * `is-open` (Defaults: false) : + Whether accordion group is open or closed. From f9a9b979c54fd9bef0f24334e972b9d2efffc5a5 Mon Sep 17 00:00:00 2001 From: Chris Chua Date: Sat, 25 Jan 2014 12:41:22 -0800 Subject: [PATCH 03/62] refactor(accordion): use ngAnimate for animations - Deprecate collapse module Consider removing it after transition module is deprecated as well. Fixes #2871 Fixes #3141 Closes #1675 --- src/accordion/accordion.js | 63 ++++++++++++++++++++++++- src/accordion/test/accordion.spec.js | 4 +- src/collapse/collapse.js | 3 ++ template/accordion/accordion-group.html | 2 +- 4 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/accordion/accordion.js b/src/accordion/accordion.js index 75da595d91..28dae4cb87 100644 --- a/src/accordion/accordion.js +++ b/src/accordion/accordion.js @@ -1,4 +1,4 @@ -angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) +angular.module('ui.bootstrap.accordion', []) .constant('accordionConfig', { closeOthers: true @@ -127,4 +127,63 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) }); } }; -}); +}) + +/** + * Animations based on addition and removal of `in` class + * This requires the bootstrap classes to be present in order to take advantage + * of the animation classes. + */ +.animation('.panel-collapse', function () { + return { + beforeAddClass: function (element, className, done) { + if (className == 'in') { + element + .removeClass('collapse') + .addClass('collapsing') + ; + } + done(); + }, + addClass: function (element, className, done) { + if (className == 'in') { + element + .css({height: element[0].scrollHeight + 'px'}) + .one('$animate:close', function closeFn() { + element + .removeClass('collapsing') + .css({height: 'auto'}); + }); + } + done(); + }, + beforeRemoveClass: function (element, className, done) { + if (className == 'in') { + element + // IMPORTANT: The height must be set before adding "collapsing" class. + // Otherwise, the browser attempts to animate from height 0 (in + // collapsing class) to the given height here. + .css({height: element[0].scrollHeight + 'px'}) + // initially all panel collapse have the collapse class, this removal + // prevents the animation from jumping to collapsed state + .removeClass('collapse') + .addClass('collapsing'); + } + done(); + }, + removeClass: function (element, className, done) { + if (className == 'in') { + element + .css({height: '0'}) + .one('$animate:close', function closeFn() { + element + .removeClass('collapsing') + .addClass('collapse'); + }); + } + done(); + } + }; +}) + +; diff --git a/src/accordion/test/accordion.spec.js b/src/accordion/test/accordion.spec.js index 08de2c172c..721d0ff2a0 100644 --- a/src/accordion/test/accordion.spec.js +++ b/src/accordion/test/accordion.spec.js @@ -265,8 +265,8 @@ describe('accordion', function () { }); it('should have visible panel body when the group with isOpen set to true', function () { - expect(findGroupBody(0)[0].clientHeight).not.toBe(0); - expect(findGroupBody(1)[0].clientHeight).toBe(0); + expect(findGroupBody(0)).toHaveClass('in'); + expect(findGroupBody(1)).not.toHaveClass('in'); }); }); diff --git a/src/collapse/collapse.js b/src/collapse/collapse.js index 5d6787a7fd..8bbc87c652 100644 --- a/src/collapse/collapse.js +++ b/src/collapse/collapse.js @@ -1,3 +1,6 @@ +/** + * @deprecated Switching over to using ngAnimate for animations + */ angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) .directive('collapse', ['$transition', function ($transition) { diff --git a/template/accordion/accordion-group.html b/template/accordion/accordion-group.html index af0fd3e223..f3c02d90bc 100644 --- a/template/accordion/accordion-group.html +++ b/template/accordion/accordion-group.html @@ -4,7 +4,7 @@

{{heading}}

-
+
From 0ecf7faad3b340e171e5c8ca17a17597fa6e6596 Mon Sep 17 00:00:00 2001 From: Dennis Stevense Date: Tue, 3 Mar 2015 20:58:40 -0800 Subject: [PATCH 04/62] fix(datepicker): Parse date from $viewValue instead of $modelValue Allow use in conjunction with an ngModelController that has custom formatters and parsers to translate between $modelValue and $viewValue --- src/datepicker/datepicker.js | 10 +++--- src/datepicker/test/datepicker.spec.js | 46 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/datepicker/datepicker.js b/src/datepicker/datepicker.js index 5b60fdd932..75a13218fa 100644 --- a/src/datepicker/datepicker.js +++ b/src/datepicker/datepicker.js @@ -63,8 +63,8 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst }; this.render = function() { - if ( ngModelCtrl.$modelValue ) { - var date = new Date( ngModelCtrl.$modelValue ), + if ( ngModelCtrl.$viewValue ) { + var date = new Date( ngModelCtrl.$viewValue ), isValid = !isNaN(date); if ( isValid ) { @@ -81,13 +81,13 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst if ( this.element ) { this._refreshView(); - var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date))); } }; this.createDateObject = function(date, format) { - var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; return { date: date, label: dateFilter(date, format), @@ -112,7 +112,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst $scope.select = function( date ) { if ( $scope.datepickerMode === self.minMode ) { - var dt = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); + var dt = ngModelCtrl.$viewValue ? new Date( ngModelCtrl.$viewValue ) : new Date(0, 0, 0, 0, 0, 0, 0); dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); ngModelCtrl.$setViewValue( dt ); ngModelCtrl.$render(); diff --git a/src/datepicker/test/datepicker.spec.js b/src/datepicker/test/datepicker.spec.js index bab11e772f..e6caa8cc76 100644 --- a/src/datepicker/test/datepicker.spec.js +++ b/src/datepicker/test/datepicker.spec.js @@ -6,6 +6,26 @@ describe('datepicker directive', function () { beforeEach(module('template/datepicker/month.html')); beforeEach(module('template/datepicker/year.html')); beforeEach(module('template/datepicker/popup.html')); + beforeEach(module(function($compileProvider) { + $compileProvider.directive('dateModel', function() { + return { + restrict: 'A', + require: 'ngModel', + link: function(scope, element, attrs, modelController) { + modelController.$formatters.push(function(object) { + return new Date(object.date); + }); + + modelController.$parsers.push(function(date) { + return { + type: 'date', + date: date.toUTCString() + }; + }); + } + }; + }); + })); beforeEach(inject(function(_$compile_, _$rootScope_) { $compile = _$compile_; $rootScope = _$rootScope_; @@ -1732,4 +1752,30 @@ describe('datepicker directive', function () { expect(getTitle()).toBe('2013'); }); }); + + describe('with an ngModelController having formatters and parsers', function() { + beforeEach(inject(function() { + // Custom date object. + $rootScope.date = { type: 'date', date: 'April 1, 2015 00:00:00' }; + + // Use dateModel directive to add formatters and parsers to the + // ngModelController that translate the custom date object. + element = $compile('')($rootScope); + $rootScope.$digest(); + })); + + it('updates the view', function() { + $rootScope.date = { type: 'date', date: 'April 15, 2015 00:00:00' }; + $rootScope.$digest(); + + expectSelectedElement(17); + }); + + it('updates the model', function() { + clickOption(17); + + expect($rootScope.date.type).toEqual('date'); + expect(new Date($rootScope.date.date)).toEqual(new Date('April 15, 2015 00:00:00')); + }); + }); }); From d784354a53fa40597cb8c124405ea9a90b6f0b8e Mon Sep 17 00:00:00 2001 From: Stepan Riha Date: Tue, 24 Feb 2015 16:58:51 -0600 Subject: [PATCH 05/62] feat(tooltip): Support for tooltip-class configuration Closes #3126. --- src/tooltip/docs/demo.html | 28 +++++++++++++++---- src/tooltip/docs/readme.md | 1 + src/tooltip/test/tooltip2.spec.js | 17 ++++++++++- src/tooltip/tooltip.js | 9 ++++-- .../tooltip/tooltip-html-unsafe-popup.html | 2 +- template/tooltip/tooltip-popup.html | 2 +- 6 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/tooltip/docs/demo.html b/src/tooltip/docs/demo.html index 0e7a7463e9..09aa0ba496 100644 --- a/src/tooltip/docs/demo.html +++ b/src/tooltip/docs/demo.html @@ -10,20 +10,36 @@

Pellentesque {{dynamicTooltipText}}, sit amet venenatis urna cursus eget nunc scelerisque viverra mauris, in - aliquam. Tincidunt lobortis feugiat vivamus at + aliquam. Tincidunt lobortis feugiat vivamus at left eget arcu dictum varius duis at consectetur lorem. Vitae elementum curabitur - right - nunc sed velit dignissim sodales ut eu sem integer vitae. Turpis egestas - bottom - pharetra convallis posuere morbi leo urna, + right + nunc sed velit dignissim sodales ut eu sem integer vitae. Turpis egestas + bottom + pharetra convallis posuere morbi leo urna, fading at elementum eu, facilisis sed odio morbi quis commodo odio. In cursus delayed turpis massa tincidunt dui ut.

- I can even contain HTML. Check me out! + I can even contain HTML. Check me out! +

+ +

+ + I can have a custom class. Check me out!

diff --git a/src/tooltip/docs/readme.md b/src/tooltip/docs/readme.md index ab96a86d73..960a7d154f 100644 --- a/src/tooltip/docs/readme.md +++ b/src/tooltip/docs/readme.md @@ -21,6 +21,7 @@ will display: `tooltip-trigger`. - `tooltip-append-to-body`: Should the tooltip be appended to `$body` instead of the parent element? +- `tooltip-class`: Custom class to be applied to the tooltip. The tooltip directives require the `$position` service. diff --git a/src/tooltip/test/tooltip2.spec.js b/src/tooltip/test/tooltip2.spec.js index c161ba01ac..2127a493c8 100644 --- a/src/tooltip/test/tooltip2.spec.js +++ b/src/tooltip/test/tooltip2.spec.js @@ -101,6 +101,22 @@ describe('tooltip directive', function () { }); + describe('class', function () { + + it('can specify a custom class', function () { + var fragment = compileTooltip('Trigger here'); + fragment.find('span').trigger( 'mouseenter' ); + + var ttipElement = fragment.find('div.tooltip'); + expect(fragment).toHaveOpenTooltips(); + expect(ttipElement).toHaveClass('custom'); + + closeTooltip(fragment.find('span')); + expect(fragment).not.toHaveOpenTooltips(); + }); + + }); + }); it('should show even after close trigger is called multiple times - issue #1847', function () { @@ -144,5 +160,4 @@ describe('tooltip directive', function () { // One needs to flush deferred functions before checking there is no tooltip. expect(fragment).not.toHaveOpenTooltips(); }); - }); diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index 70932db2b0..ec7ed327f3 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -100,6 +100,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap 'title="'+startSym+'title'+endSym+'" '+ 'content="'+startSym+'content'+endSym+'" '+ 'placement="'+startSym+'placement'+endSym+'" '+ + 'class="'+startSym+'class'+endSym+'" '+ 'animation="animation" '+ 'is-open="isOpen"'+ '>'+ @@ -276,6 +277,10 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap ttScope.title = val; }); + attrs.$observe( prefix+'Class', function ( val ) { + ttScope.class = val; + }); + function prepPlacement() { var val = attrs[ prefix + 'Placement' ]; ttScope.placement = angular.isDefined( val ) ? val : options.placement; @@ -343,7 +348,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap return { restrict: 'EA', replace: true, - scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + scope: { content: '@', placement: '@', class: '@', animation: '&', isOpen: '&' }, templateUrl: 'template/tooltip/tooltip-popup.html' }; }) @@ -356,7 +361,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap return { restrict: 'EA', replace: true, - scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + scope: { content: '@', placement: '@', class: '@', animation: '&', isOpen: '&' }, templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' }; }) diff --git a/template/tooltip/tooltip-html-unsafe-popup.html b/template/tooltip/tooltip-html-unsafe-popup.html index 129016d9c1..b5b3784acf 100644 --- a/template/tooltip/tooltip-html-unsafe-popup.html +++ b/template/tooltip/tooltip-html-unsafe-popup.html @@ -1,4 +1,4 @@ -
+
diff --git a/template/tooltip/tooltip-popup.html b/template/tooltip/tooltip-popup.html index fd51120774..14c3e3a27c 100644 --- a/template/tooltip/tooltip-popup.html +++ b/template/tooltip/tooltip-popup.html @@ -1,4 +1,4 @@ -
+
From d253208bd7ebbd6209c6a25f70e010123198fcd7 Mon Sep 17 00:00:00 2001 From: Khashayar Hajian Date: Sat, 21 Feb 2015 21:09:09 +0100 Subject: [PATCH 06/62] fix(datepicker): `ng-model` value can be a timestamp Accept a number of milliseconds since 01.01.1970 as a valid value for `ng-model`: - change parseDate() to handle timestamp values - add the test to `datepicker.spec.js` Closes #2345 --- src/datepicker/datepicker.js | 5 +++++ src/datepicker/test/datepicker.spec.js | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/datepicker/datepicker.js b/src/datepicker/datepicker.js index 75a13218fa..021ce60ef0 100644 --- a/src/datepicker/datepicker.js +++ b/src/datepicker/datepicker.js @@ -502,6 +502,11 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi } function parseDate(viewValue) { + if (angular.isNumber(viewValue)) { + // presumably timestamp to date object + viewValue = new Date(viewValue); + } + if (!viewValue) { ngModel.$setValidity('date', true); return null; diff --git a/src/datepicker/test/datepicker.spec.js b/src/datepicker/test/datepicker.spec.js index e6caa8cc76..1a9f85d65c 100644 --- a/src/datepicker/test/datepicker.spec.js +++ b/src/datepicker/test/datepicker.spec.js @@ -1547,11 +1547,18 @@ describe('datepicker directive', function () { it('should be invalid initially', function() { expect(inputEl.hasClass('ng-invalid')).toBeTruthy(); }); + it('should be valid if model has been specified', function() { $rootScope.date = new Date(); $rootScope.$digest(); expect(inputEl.hasClass('ng-valid')).toBeTruthy(); }); + + it('should be valid if model value is a valid timestamp', function() { + $rootScope.date = Date.now(); + $rootScope.$digest(); + expect(inputEl.hasClass('ng-valid')).toBeTruthy(); + }); }); describe('use with `ng-change` directive', function() { From b5f220fa8483f5743ba6ab3610f5064bf5c71be7 Mon Sep 17 00:00:00 2001 From: Andrey Yakovlev Date: Sun, 22 Mar 2015 04:35:41 +0600 Subject: [PATCH 07/62] fix(carousel): respect the order of the slides index parameter is added to the slide directive. It is used to order the slides. Fixes #488 --- src/carousel/carousel.js | 45 ++++++++++++++----- src/carousel/test/carousel.spec.js | 70 ++++++++++++++++++++++++++++++ template/carousel/carousel.html | 2 +- 3 files changed, 106 insertions(+), 11 deletions(-) diff --git a/src/carousel/carousel.js b/src/carousel/carousel.js index b5ba28561a..21c7abb29e 100644 --- a/src/carousel/carousel.js +++ b/src/carousel/carousel.js @@ -17,10 +17,10 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) var destroyed = false; /* direction: "prev" or "next" */ self.select = $scope.select = function(nextSlide, direction) { - var nextIndex = slides.indexOf(nextSlide); + var nextIndex = self.indexOfSlide(nextSlide); //Decide direction if it's not given if (direction === undefined) { - direction = nextIndex > currentIndex ? 'next' : 'prev'; + direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev'; } if (nextSlide && nextSlide !== self.currentSlide) { goNext(); @@ -31,7 +31,6 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) angular.extend(nextSlide, {direction: direction, active: true}); angular.extend(self.currentSlide || {}, {direction: direction, active: false}); - if ($animate.enabled() && !$scope.noTransition && nextSlide.$element) { $scope.$currentTransition = true; // TODO: Switch to use .one when upgrading beyond 1.2.21 @@ -52,26 +51,45 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) destroyed = true; }); + function getSlideByIndex(index) { + if (angular.isUndefined(slides[index].index)) { + return slides[index]; + } + var i, len = slides.length; + for (i = 0; i < slides.length; ++i) { + if (slides[i].index == index) { + return slides[i]; + } + } + } + + self.getCurrentIndex = function() { + if (self.currentSlide && angular.isDefined(self.currentSlide.index)) { + return +self.currentSlide.index; + } + return currentIndex; + }; + /* Allow outside people to call indexOf on slides array */ self.indexOfSlide = function(slide) { - return slides.indexOf(slide); + return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide); }; $scope.next = function() { - var newIndex = (currentIndex + 1) % slides.length; + var newIndex = (self.getCurrentIndex() + 1) % slides.length; //Prevent this user-triggered transition from occurring if there is already one in progress if (!$scope.$currentTransition) { - return self.select(slides[newIndex], 'next'); + return self.select(getSlideByIndex(newIndex), 'next'); } }; $scope.prev = function() { - var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; + var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1; //Prevent this user-triggered transition from occurring if there is already one in progress if (!$scope.$currentTransition) { - return self.select(slides[newIndex], 'prev'); + return self.select(getSlideByIndex(newIndex), 'prev'); } }; @@ -134,6 +152,11 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) }; self.removeSlide = function(slide) { + if (angular.isDefined(slide.index)) { + slides.sort(function(a, b) { + return +a.index > +b.index; + }); + } //get the index of the slide inside the carousel var index = slides.indexOf(slide); slides.splice(index, 1); @@ -213,13 +236,14 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. * * @param {boolean=} active Model binding, whether or not this slide is currently active. + * @param {number=} index The index of the slide. The slides will be sorted by this parameter. * * @example
- +