angular-ui-bootstrap
Advanced tools
Comparing version 1.1.2 to 1.2.0
{ | ||
"author": "https://github.com/angular-ui/bootstrap/graphs/contributors", | ||
"name": "angular-ui-bootstrap", | ||
"version": "1.1.2", | ||
"version": "1.2.0", | ||
"homepage": "http://angular-ui.github.io/bootstrap/", | ||
@@ -11,6 +11,9 @@ "dependencies": {}, | ||
"files": [ | ||
"dist/", "src/", "template/" | ||
"index.js", | ||
"dist/", | ||
"src/", | ||
"template/" | ||
], | ||
"main": "index.js", | ||
"scripts":{ | ||
"scripts": { | ||
"test": "grunt" | ||
@@ -23,5 +26,5 @@ }, | ||
"devDependencies": { | ||
"angular": "^1.4.4", | ||
"angular-mocks": "^1.4.4", | ||
"angular-sanitize": "^1.4.4", | ||
"angular": "1.5.0", | ||
"angular-mocks": "1.5.0", | ||
"angular-sanitize": "1.5.0", | ||
"grunt": "^0.4.5", | ||
@@ -44,2 +47,3 @@ "grunt-contrib-concat": "^0.5.1", | ||
"load-grunt-tasks": "^3.3.0", | ||
"lodash": "^4.1.0", | ||
"marked": "^0.3.5", | ||
@@ -46,0 +50,0 @@ "semver": "^5.0.1", |
@@ -16,2 +16,3 @@ ### UI Bootstrap - [AngularJS](http://angularjs.org/) directives specific to [Bootstrap](http://getbootstrap.com) | ||
- [Manual](#manual-download) | ||
- [Webpack](#webpack) | ||
- [Support](#support) | ||
@@ -39,3 +40,5 @@ - [FAQ](#faq) | ||
Installation is easy as UI Bootstrap has minimal dependencies - only the AngularJS and Twitter Bootstrap's CSS are required. | ||
Note: Since version 0.13.0, UI Bootstrap depends on [ngAnimate](https://docs.angularjs.org/api/ngAnimate) for transitions and animations, such as the accordion, carousel, etc. Include `ngAnimate` in the module dependencies for your app in order to enable animation. | ||
*Notes:* | ||
* Since version 0.13.0, UI Bootstrap depends on [ngAnimate](https://docs.angularjs.org/api/ngAnimate) for transitions and animations, such as the accordion, carousel, etc. Include `ngAnimate` in the module dependencies for your app in order to enable animation. | ||
* UI Bootstrap depends on [ngTouch](https://docs.angularjs.org/api/ngTouch) for swipe actions. Include `ngTouch` in the module dependencies for your app in order to enable swiping. | ||
@@ -91,10 +94,25 @@ ## Angular Requirements | ||
If you're a Browserify or Webpack user, you can do: | ||
# Webpack | ||
To use this project with webpack, follow the [NPM](#install-with-npm) instructions. Now, if you want to use only the accordion, you can do: | ||
```js | ||
var uibs = require('angular-ui-bootstrap'); | ||
import accordion from 'angular-ui-bootstrap/src/accordion'; | ||
angular.module('myModule', [uibs]); | ||
angular.module('myModule', [accordion]); | ||
``` | ||
You can import all the pieces you need in the same way: | ||
```js | ||
import accordion from 'angular-ui-bootstrap/src/accordion'; | ||
import datepicker from 'angular-ui-bootstrap/src/datepicker'; | ||
angular.module('myModule', [accordion, datepicker]); | ||
``` | ||
This will load all the dependencies (if any) and also the templates (if any). | ||
Be sure to have a loader able to process `css` files like `css-loader`. | ||
# Support | ||
@@ -101,0 +119,0 @@ |
@@ -126,4 +126,5 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) | ||
if (heading) { | ||
element.find('span').html(''); | ||
element.find('span').append(heading); | ||
var elem = angular.element(element[0].querySelector('[uib-accordion-header]')); | ||
elem.html(''); | ||
elem.append(heading); | ||
} | ||
@@ -130,0 +131,0 @@ }); |
@@ -47,2 +47,4 @@ The **accordion directive** builds on top of the collapse directive to provide a list of items, with collapsible bodies that are collapsed or expanded by clicking on the item's header. | ||
If you're using a custom template for the `uib-accordion-group`, you'll need to have an element for the heading to be transcluded into using `uib-accordion-header` (e.g. `<div uib-accordion-header></div>`). | ||
### Known issues | ||
@@ -49,0 +51,0 @@ |
@@ -47,3 +47,3 @@ angular.module('ui.bootstrap.buttons', []) | ||
scope.$watch(uncheckableExpr, function(uncheckable) { | ||
attrs.$set('uncheckable', uncheckable ? '' : null); | ||
attrs.$set('uncheckable', uncheckable ? '' : undefined); | ||
}); | ||
@@ -50,0 +50,0 @@ } |
@@ -331,4 +331,18 @@ describe('buttons', function() { | ||
expect(btns.eq(0).attr('uncheckable')).toBeUndefined(); | ||
expect(btns.eq(0).attr('uncheckable')).toBeUndefined(); | ||
expect(btns.eq(1).attr('uncheckable')).toBeUndefined(); | ||
expect($scope.model).toBeUndefined(); | ||
btns.eq(0).click(); | ||
expect($scope.model).toEqual(1); | ||
btns.eq(0).click(); | ||
expect($scope.model).toEqual(1); | ||
btns.eq(1).click(); | ||
expect($scope.model).toEqual(2); | ||
btns.eq(1).click(); | ||
expect($scope.model).toEqual(2); | ||
$scope.uncheckable = true; | ||
@@ -338,2 +352,14 @@ $scope.$digest(); | ||
expect(btns.eq(1).attr('uncheckable')).toBeDefined(); | ||
btns.eq(0).click(); | ||
expect($scope.model).toEqual(1); | ||
btns.eq(0).click(); | ||
expect($scope.model).toEqual(1); | ||
btns.eq(1).click(); | ||
expect($scope.model).toEqual(2); | ||
btns.eq(1).click(); | ||
expect($scope.model).toBeNull(); | ||
}); | ||
@@ -340,0 +366,0 @@ }); |
@@ -7,5 +7,4 @@ angular.module('ui.bootstrap.carousel', []) | ||
SLIDE_DIRECTION = 'uib-slideDirection', | ||
currentIndex = -1, | ||
currentIndex = $scope.active, | ||
currentInterval, isPlaying, bufferedTransitions = []; | ||
self.currentSlide = null; | ||
@@ -15,6 +14,11 @@ var destroyed = false; | ||
self.addSlide = function(slide, element) { | ||
slide.$element = element; | ||
slides.push(slide); | ||
slides.push({ | ||
slide: slide, | ||
element: element | ||
}); | ||
slides.sort(function(a, b) { | ||
return +a.slide.index > +b.slide.index; | ||
}); | ||
//if this is the first slide or the slide is set to active, select it | ||
if (slides.length === 1 || slide.active) { | ||
if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) { | ||
if ($scope.$currentTransition) { | ||
@@ -24,8 +28,9 @@ $scope.$currentTransition = null; | ||
self.select(slides[slides.length - 1]); | ||
currentIndex = slide.index; | ||
$scope.active = slide.index; | ||
setActive(currentIndex); | ||
self.select(slides[findSlideIndex(slide)]); | ||
if (slides.length === 1) { | ||
$scope.play(); | ||
} | ||
} else { | ||
slide.active = false; | ||
} | ||
@@ -35,6 +40,7 @@ }; | ||
self.getCurrentIndex = function() { | ||
if (self.currentSlide && angular.isDefined(self.currentSlide.index)) { | ||
return +self.currentSlide.index; | ||
for (var i = 0; i < slides.length; i++) { | ||
if (slides[i].slide.index === currentIndex) { | ||
return i; | ||
} | ||
} | ||
return currentIndex; | ||
}; | ||
@@ -50,3 +56,3 @@ | ||
return self.select(getSlideByIndex(newIndex), 'next'); | ||
return self.select(slides[newIndex], 'next'); | ||
}; | ||
@@ -62,34 +68,36 @@ | ||
return self.select(getSlideByIndex(newIndex), 'prev'); | ||
return self.select(slides[newIndex], 'prev'); | ||
}; | ||
self.removeSlide = function(slide) { | ||
if (angular.isDefined(slide.index)) { | ||
slides.sort(function(a, b) { | ||
return +a.index > +b.index; | ||
}); | ||
} | ||
var index = findSlideIndex(slide); | ||
var bufferedIndex = bufferedTransitions.indexOf(slide); | ||
var bufferedIndex = bufferedTransitions.indexOf(slides[index]); | ||
if (bufferedIndex !== -1) { | ||
bufferedTransitions.splice(bufferedIndex, 1); | ||
} | ||
//get the index of the slide inside the carousel | ||
var index = slides.indexOf(slide); | ||
slides.splice(index, 1); | ||
$timeout(function() { | ||
if (slides.length > 0 && slide.active) { | ||
if (index >= slides.length) { | ||
self.select(slides[index - 1]); | ||
} else { | ||
self.select(slides[index]); | ||
} | ||
} else if (currentIndex > index) { | ||
currentIndex--; | ||
if (slides.length > 0 && currentIndex === index) { | ||
if (index >= slides.length) { | ||
currentIndex = slides.length - 1; | ||
$scope.active = currentIndex; | ||
setActive(currentIndex); | ||
self.select(slides[slides.length - 1]); | ||
} else { | ||
currentIndex = index; | ||
$scope.active = currentIndex; | ||
setActive(currentIndex); | ||
self.select(slides[index]); | ||
} | ||
}); | ||
} else if (currentIndex > index) { | ||
currentIndex--; | ||
$scope.active = currentIndex; | ||
} | ||
//clean the currentSlide when no more slide | ||
//clean the active value when no more slide | ||
if (slides.length === 0) { | ||
self.currentSlide = null; | ||
currentIndex = null; | ||
$scope.active = null; | ||
clearBufferedTransitions(); | ||
@@ -101,3 +109,3 @@ } | ||
self.select = $scope.select = function(nextSlide, direction) { | ||
var nextIndex = $scope.indexOfSlide(nextSlide); | ||
var nextIndex = findSlideIndex(nextSlide.slide); | ||
//Decide direction if it's not given | ||
@@ -108,7 +116,7 @@ if (direction === undefined) { | ||
//Prevent this user-triggered transition from occurring if there is already one in progress | ||
if (nextSlide && nextSlide !== self.currentSlide && !$scope.$currentTransition) { | ||
goNext(nextSlide, nextIndex, direction); | ||
} else if (nextSlide && nextSlide !== self.currentSlide && $scope.$currentTransition) { | ||
bufferedTransitions.push(nextSlide); | ||
nextSlide.active = false; | ||
if (nextSlide.slide.index !== currentIndex && | ||
!$scope.$currentTransition) { | ||
goNext(nextSlide.slide, nextIndex, direction); | ||
} else if (nextSlide && nextSlide.slide.index !== currentIndex && $scope.$currentTransition) { | ||
bufferedTransitions.push(slides[nextIndex]); | ||
} | ||
@@ -119,7 +127,7 @@ }; | ||
$scope.indexOfSlide = function(slide) { | ||
return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide); | ||
return +slide.slide.index; | ||
}; | ||
$scope.isActive = function(slide) { | ||
return self.currentSlide === slide; | ||
return $scope.active === slide.slide.index; | ||
}; | ||
@@ -154,2 +162,20 @@ | ||
$scope.$watch('active', function(index) { | ||
if (angular.isNumber(index) && currentIndex !== index) { | ||
for (var i = 0; i < slides.length; i++) { | ||
if (slides[i].slide.index === index) { | ||
index = i; | ||
break; | ||
} | ||
} | ||
var slide = slides[index]; | ||
if (slide) { | ||
currentIndex = index; | ||
setActive(index); | ||
self.select(slides[index]); | ||
} | ||
} | ||
}); | ||
function clearBufferedTransitions() { | ||
@@ -162,5 +188,2 @@ while (bufferedTransitions.length) { | ||
function getSlideByIndex(index) { | ||
if (angular.isUndefined(slides[index].index)) { | ||
return slides[index]; | ||
} | ||
for (var i = 0, l = slides.length; i < l; ++i) { | ||
@@ -173,16 +196,26 @@ if (slides[i].index === index) { | ||
function setActive(index) { | ||
for (var i = 0; i < slides.length; i++) { | ||
slides[i].slide.active = i === index; | ||
} | ||
} | ||
function goNext(slide, index, direction) { | ||
if (destroyed) { return; } | ||
if (destroyed) { | ||
return; | ||
} | ||
angular.extend(slide, {direction: direction, active: true}); | ||
angular.extend(self.currentSlide || {}, {direction: direction, active: false}); | ||
angular.extend(slide, {direction: direction}); | ||
angular.extend(slides[currentIndex].slide || {}, {direction: direction}); | ||
if ($animate.enabled($element) && !$scope.$currentTransition && | ||
slide.$element && self.slides.length > 1) { | ||
slide.$element.data(SLIDE_DIRECTION, slide.direction); | ||
if (self.currentSlide && self.currentSlide.$element) { | ||
self.currentSlide.$element.data(SLIDE_DIRECTION, slide.direction); | ||
slides[index].element && self.slides.length > 1) { | ||
slides[index].element.data(SLIDE_DIRECTION, slide.direction); | ||
var currentIdx = self.getCurrentIndex(); | ||
if (angular.isNumber(currentIdx) && slides[currentIdx].element) { | ||
slides[currentIdx].element.data(SLIDE_DIRECTION, slide.direction); | ||
} | ||
$scope.$currentTransition = true; | ||
$animate.on('addClass', slide.$element, function(element, phase) { | ||
$animate.on('addClass', slides[index].element, function(element, phase) { | ||
if (phase === 'close') { | ||
@@ -192,4 +225,4 @@ $scope.$currentTransition = null; | ||
if (bufferedTransitions.length) { | ||
var nextSlide = bufferedTransitions.pop(); | ||
var nextIndex = $scope.indexOfSlide(nextSlide); | ||
var nextSlide = bufferedTransitions.pop().slide; | ||
var nextIndex = nextSlide.index; | ||
var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev'; | ||
@@ -204,4 +237,5 @@ clearBufferedTransitions(); | ||
self.currentSlide = slide; | ||
currentIndex = index; | ||
$scope.active = slide.index; | ||
currentIndex = slide.index; | ||
setActive(index); | ||
@@ -212,2 +246,10 @@ //every time you change slides, reset the timer | ||
function findSlideIndex(slide) { | ||
for (var i = 0; i < slides.length; i++) { | ||
if (slides[i].slide === slide) { | ||
return i; | ||
} | ||
} | ||
} | ||
function resetTimer() { | ||
@@ -255,2 +297,3 @@ if (currentInterval) { | ||
scope: { | ||
active: '=', | ||
interval: '=', | ||
@@ -273,3 +316,2 @@ noTransition: '=', | ||
scope: { | ||
active: '=?', | ||
actual: '=?', | ||
@@ -284,8 +326,2 @@ index: '=?' | ||
}); | ||
scope.$watch('active', function(active) { | ||
if (active) { | ||
carouselCtrl.select(scope); | ||
} | ||
}); | ||
} | ||
@@ -292,0 +328,0 @@ }; |
angular.module('ui.bootstrap.demo').controller('CarouselDemoCtrl', function ($scope) { | ||
$scope.myInterval = 5000; | ||
$scope.noWrapSlides = false; | ||
$scope.active = 0; | ||
var slides = $scope.slides = []; | ||
@@ -5,0 +6,0 @@ var currIndex = 0; |
@@ -9,2 +9,7 @@ Carousel creates a carousel similar to bootstrap's image carousel. | ||
* `active` | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `Index of first slide`)_ - | ||
Index of current active slide. | ||
* `interval` | ||
@@ -15,3 +20,3 @@ <small class="badge">$</small> | ||
Sets an interval to cycle through the slides. You need a number bigger than 0 to make the interval work. | ||
* `no-pause` | ||
@@ -22,3 +27,3 @@ <small class="badge">$</small> | ||
The interval pauses on mouseover. Setting this to truthy, disables this pause. | ||
* `no-transition` | ||
@@ -29,3 +34,3 @@ <small class="badge">$</small> | ||
Whether to disable the transition animation between slides. Setting this to truthy, disables this transition. | ||
* `no-wrap` | ||
@@ -35,15 +40,9 @@ <small class="badge">$</small> | ||
Disables the looping of slides. Setting `no-wrap` to an expression which evaluates to a truthy value will prevent looping. | ||
* `template-url` | ||
_(Default: `uib/template/carousel/carousel.html`)_ - | ||
Add the ability to override the template used on the component. | ||
### uib-slide settings | ||
* `active` | ||
<small class="badge">$</small> | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `false`)_ - | ||
Sets the slide as the active one. | ||
* `actual` | ||
@@ -54,3 +53,3 @@ <small class="badge">$</small> | ||
Use this attribute to bind the slide model (or any object of interest) onto the slide scope, which makes it available for customization in the carousel template. | ||
* `index` | ||
@@ -60,6 +59,6 @@ <small class="badge">$</small> | ||
_(Default: `none`)_ - | ||
Use this attribute to change how the slides are ordered. | ||
The index of the slide. Must be unique. | ||
* `template-url` | ||
_(Default: `uib/template/carousel/slide.html`)_ - | ||
Add the ability to override the template used on the component. |
@@ -34,9 +34,9 @@ describe('carousel', function() { | ||
scope.slides = [ | ||
{active:false,content:'one'}, | ||
{active:false,content:'two'}, | ||
{active:false,content:'three'} | ||
{content: 'one', index: 0}, | ||
{content: 'two', index: 1}, | ||
{content: 'three', index: 2} | ||
]; | ||
elm = $compile( | ||
'<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' + | ||
'<uib-slide ng-repeat="slide in slides" active="slide.active">' + | ||
'<uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' + | ||
'<uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' + | ||
'{{slide.content}}' + | ||
@@ -54,5 +54,5 @@ '</uib-slide>' + | ||
if (i === slideIndex) { | ||
expect(scope.slides[i].active).toBe(true); | ||
expect(scope.active).toBe(scope.slides[i].index); | ||
} else { | ||
expect(scope.slides[i].active).not.toBe(true); | ||
expect(scope.active).not.toBe(scope.slides[i].index); | ||
} | ||
@@ -87,3 +87,3 @@ } | ||
testSlideActive(0); | ||
scope.$apply('slides[1].active=true'); | ||
scope.$apply('active=1'); | ||
testSlideActive(1); | ||
@@ -107,8 +107,8 @@ }); | ||
elm = $compile( | ||
'<uib-carousel interval="interval" no-wrap="noWrap">' + | ||
'<uib-slide ng-repeat="slide in slides" active="slide.active">' + | ||
'{{slide.content}}' + | ||
'</uib-slide>' + | ||
'</uib-carousel>' | ||
)(scope); | ||
'<uib-carousel active="active" interval="interval" no-wrap="noWrap">' + | ||
'<uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' + | ||
'{{slide.content}}' + | ||
'</uib-slide>' + | ||
'</uib-carousel>' | ||
)(scope); | ||
@@ -118,12 +118,11 @@ scope.noWrap = true; | ||
scope = elm.isolateScope(); | ||
spyOn(scope, 'pause'); | ||
var $scope = elm.isolateScope(); | ||
spyOn($scope, 'pause'); | ||
for (var i = 0; i < scope.slides.length - 1; ++i) { | ||
scope.next(); | ||
} | ||
testSlideActive(scope.slides.length - 1); | ||
scope.next(); | ||
testSlideActive(scope.slides.length - 1); | ||
expect(scope.pause).toHaveBeenCalled(); | ||
scope.active = $scope.slides.length - 1; | ||
scope.$apply(); | ||
testSlideActive($scope.slides.length - 1); | ||
$scope.next(); | ||
testSlideActive($scope.slides.length - 1); | ||
expect($scope.pause).toHaveBeenCalled(); | ||
}); | ||
@@ -133,8 +132,8 @@ | ||
elm = $compile( | ||
'<uib-carousel interval="interval" no-wrap="noWrap">' + | ||
'<uib-slide ng-repeat="slide in slides" active="slide.active">' + | ||
'{{slide.content}}' + | ||
'</uib-slide>' + | ||
'</uib-carousel>' | ||
)(scope); | ||
'<uib-carousel active="active" interval="interval" no-wrap="noWrap">' + | ||
'<uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' + | ||
'{{slide.content}}' + | ||
'</uib-slide>' + | ||
'</uib-carousel>' | ||
)(scope); | ||
@@ -144,9 +143,9 @@ scope.noWrap = true; | ||
scope = elm.isolateScope(); | ||
spyOn(scope, 'pause'); | ||
var $scope = elm.isolateScope(); | ||
spyOn($scope, 'pause'); | ||
testSlideActive(0); | ||
scope.prev(); | ||
$scope.prev(); | ||
testSlideActive(0); | ||
expect(scope.pause).toHaveBeenCalled(); | ||
expect($scope.pause).toHaveBeenCalled(); | ||
}); | ||
@@ -158,8 +157,8 @@ | ||
elm = $compile( | ||
'<uib-carousel interval="interval" no-transition="true">' + | ||
'<uib-slide ng-repeat="slide in slides" active="slide.active">' + | ||
'{{slide.content}}' + | ||
'</uib-slide>' + | ||
'</uib-carousel>' | ||
)(scope); | ||
'<uib-carousel active="active" interval="interval" no-transition="true">' + | ||
'<uib-slide ng-repeat="slide in slides" index="$index">' + | ||
'{{slide.content}}' + | ||
'</uib-slide>' + | ||
'</uib-carousel>' | ||
)(scope); | ||
var indicators = elm.find('ol.carousel-indicators > li'); | ||
@@ -302,5 +301,5 @@ expect(indicators.length).toBe(0); | ||
it('should remove slide from dom and change active slide', function() { | ||
scope.$apply('slides[2].active = true'); | ||
scope.$apply('active = 2'); | ||
testSlideActive(2); | ||
scope.$apply('slides.splice(0,1)'); | ||
scope.$apply('slides.splice(2,1)'); | ||
$timeout.flush(0); | ||
@@ -318,3 +317,7 @@ expect(elm.find('div.item').length).toBe(2); | ||
it('should change dom when you reassign ng-repeat slides array', function() { | ||
scope.slides = [{content:'new1'},{content:'new2'},{content:'new3'}]; | ||
scope.slides = [ | ||
{content:'new1', index: 4}, | ||
{content:'new2', index: 5}, | ||
{content:'new3', index: 6} | ||
]; | ||
scope.$apply(); | ||
@@ -392,3 +395,7 @@ var contents = elm.find('div.item'); | ||
var next = elm.find('a.right'); | ||
scope.slides = [{content:'new1'},{content:'new2'},{content:'new3'}]; | ||
scope.slides = [ | ||
{content:'new1', index: 1}, | ||
{content:'new2', index: 2}, | ||
{content:'new3', index: 3} | ||
]; | ||
scope.$apply(); | ||
@@ -404,71 +411,81 @@ | ||
}); | ||
}); | ||
describe('slide order', function() { | ||
beforeEach(function() { | ||
scope.slides = [ | ||
{active:false,content:'one', id:3}, | ||
{active:false,content:'two', id:1}, | ||
{active:false,content:'three', id:2} | ||
]; | ||
elm = $compile( | ||
'<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' + | ||
'<uib-slide ng-repeat="slide in slides | orderBy: \'id\' " active="slide.active" index="$index">' + | ||
'{{slide.content}}' + | ||
'</uib-slide>' + | ||
'</uib-carousel>' | ||
)(scope); | ||
scope.$apply(); | ||
}); | ||
describe('slide order', function() { | ||
var elm, scope; | ||
beforeEach(function() { | ||
scope = $rootScope.$new(); | ||
scope.slides = [ | ||
{content: 'one', id: 3}, | ||
{content: 'two', id: 1}, | ||
{content: 'three', id: 2} | ||
]; | ||
elm = $compile( | ||
'<uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' + | ||
'<uib-slide ng-repeat="slide in slides | orderBy: \'id\' track by slide.id" index="slide.id">' + | ||
'{{slide.content}}' + | ||
'</uib-slide>' + | ||
'</uib-carousel>' | ||
)(scope); | ||
scope.$apply(); | ||
}); | ||
it('should change dom when the order of the slides changes', function() { | ||
scope.slides[0].id = 3; | ||
scope.slides[1].id = 2; | ||
scope.slides[2].id = 1; | ||
scope.$apply(); | ||
var contents = elm.find('div.item'); | ||
expect(contents.length).toBe(3); | ||
expect(contents.eq(0).text()).toBe('three'); | ||
expect(contents.eq(1).text()).toBe('two'); | ||
expect(contents.eq(2).text()).toBe('one'); | ||
}); | ||
function testSlideActive(slideIndex) { | ||
for (var i = 0; i < scope.slides.length; i++) { | ||
if (i === slideIndex) { | ||
expect(scope.active).toBe(scope.slides[i].id); | ||
} else { | ||
expect(scope.active).not.toBe(scope.slides[i].id); | ||
} | ||
} | ||
} | ||
it('should select next after order change', function() { | ||
testSlideActive(0); | ||
var next = elm.find('a.right'); | ||
next.click(); | ||
testSlideActive(1); | ||
}); | ||
it('should change dom when the order of the slides changes', function() { | ||
scope.slides[0].id = 3; | ||
scope.slides[1].id = 2; | ||
scope.slides[2].id = 1; | ||
scope.$apply(); | ||
var contents = elm.find('div.item'); | ||
expect(contents.length).toBe(3); | ||
expect(contents.eq(0).text()).toBe('three'); | ||
expect(contents.eq(1).text()).toBe('two'); | ||
expect(contents.eq(2).text()).toBe('one'); | ||
}); | ||
it('should select prev after order change', function() { | ||
testSlideActive(0); | ||
var prev = elm.find('a.left'); | ||
prev.click(); | ||
testSlideActive(2); | ||
}); | ||
it('should select next after order change', function() { | ||
testSlideActive(1); | ||
var next = elm.find('a.right'); | ||
next.click(); | ||
testSlideActive(2); | ||
}); | ||
it('should add slide in the specified position', function() { | ||
testSlideActive(0); | ||
scope.slides[2].id = 4; | ||
scope.slides.push({active:false,content:'four', id:2}); | ||
scope.$apply(); | ||
var contents = elm.find('div.item'); | ||
expect(contents.length).toBe(4); | ||
expect(contents.eq(0).text()).toBe('two'); | ||
expect(contents.eq(1).text()).toBe('four'); | ||
expect(contents.eq(2).text()).toBe('one'); | ||
expect(contents.eq(3).text()).toBe('three'); | ||
}); | ||
it('should select prev after order change', function() { | ||
testSlideActive(1); | ||
var prev = elm.find('a.left'); | ||
prev.click(); | ||
testSlideActive(0); | ||
}); | ||
it('should remove slide after order change', function() { | ||
testSlideActive(0); | ||
scope.slides.splice(1, 1); | ||
scope.$apply(); | ||
var contents = elm.find('div.item'); | ||
expect(contents.length).toBe(2); | ||
expect(contents.eq(0).text()).toBe('three'); | ||
expect(contents.eq(1).text()).toBe('one'); | ||
}); | ||
it('should add slide in the specified position', function() { | ||
testSlideActive(1); | ||
scope.slides[2].id = 4; | ||
scope.slides.push({content:'four', id: 5}); | ||
scope.$apply(); | ||
var contents = elm.find('div.item'); | ||
expect(contents.length).toBe(4); | ||
expect(contents.eq(0).text()).toBe('two'); | ||
expect(contents.eq(1).text()).toBe('one'); | ||
expect(contents.eq(2).text()).toBe('three'); | ||
expect(contents.eq(3).text()).toBe('four'); | ||
}); | ||
it('should remove slide after order change', function() { | ||
testSlideActive(1); | ||
scope.slides.splice(1, 1); | ||
scope.$apply(); | ||
var contents = elm.find('div.item'); | ||
expect(contents.length).toBe(2); | ||
expect(contents.eq(0).text()).toBe('three'); | ||
expect(contents.eq(1).text()).toBe('one'); | ||
}); | ||
}); | ||
@@ -479,8 +496,14 @@ | ||
//create an array of slides and add to the scope | ||
var slides = [{'content':1},{'content':2},{'content':3},{'content':4}]; | ||
var slides = [ | ||
{'content': 1, index: 0}, | ||
{'content': 2, index: 1}, | ||
{'content': 3, index: 2}, | ||
{'content': 4, index: 3} | ||
]; | ||
beforeEach(function() { | ||
scope = $rootScope.$new(); | ||
scope.noWrap = angular.noop; | ||
ctrl = $controller('UibCarouselController', {$scope: scope, $element: angular.element('<div></div>')}); | ||
for(var i = 0;i < slides.length;i++){ | ||
for (var i = 0; i < slides.length; i++) { | ||
ctrl.addSlide(slides[i]); | ||
@@ -493,5 +516,5 @@ } | ||
if (i !== 0) { | ||
expect(slide.active).not.toBe(true); | ||
expect(slide.slide.active).not.toBe(true); | ||
} else { | ||
expect(slide.active).toBe(true); | ||
expect(slide.slide.active).toBe(true); | ||
} | ||
@@ -501,18 +524,9 @@ }); | ||
it('should add new slide and change active to true if active is true on the added slide', function() { | ||
var newSlide = {active: true}; | ||
expect(ctrl.slides.length).toBe(4); | ||
ctrl.addSlide(newSlide); | ||
expect(ctrl.slides.length).toBe(5); | ||
expect(ctrl.slides[4].active).toBe(true); | ||
expect(ctrl.slides[0].active).toBe(false); | ||
}); | ||
it('should add a new slide and not change the active slide', function() { | ||
var newSlide = {active: false}; | ||
var newSlide = {active: false, index: 4}; | ||
expect(ctrl.slides.length).toBe(4); | ||
ctrl.addSlide(newSlide); | ||
expect(ctrl.slides.length).toBe(5); | ||
expect(ctrl.slides[4].active).toBe(false); | ||
expect(ctrl.slides[0].active).toBe(true); | ||
expect(ctrl.slides[4].slide.active).toBe(false); | ||
expect(ctrl.slides[0].slide.active).toBe(true); | ||
}); | ||
@@ -522,19 +536,19 @@ | ||
expect(ctrl.slides.length).toBe(4); | ||
ctrl.removeSlide(ctrl.slides[0]); | ||
ctrl.removeSlide(ctrl.slides[0].slide); | ||
$timeout.flush(0); | ||
expect(ctrl.slides.length).toBe(3); | ||
expect(ctrl.currentSlide).toBe(ctrl.slides[0]); | ||
expect(scope.active).toBe(1); | ||
ctrl.select(ctrl.slides[2]); | ||
ctrl.removeSlide(ctrl.slides[2]); | ||
ctrl.removeSlide(ctrl.slides[2].slide); | ||
$timeout.flush(0); | ||
expect(ctrl.slides.length).toBe(2); | ||
expect(ctrl.currentSlide).toBe(ctrl.slides[1]); | ||
ctrl.removeSlide(ctrl.slides[0]); | ||
expect(scope.active).toBe(2); | ||
ctrl.removeSlide(ctrl.slides[0].slide); | ||
$timeout.flush(0); | ||
expect(ctrl.slides.length).toBe(1); | ||
expect(ctrl.currentSlide).toBe(ctrl.slides[0]); | ||
expect(scope.active).toBe(1); | ||
}); | ||
it('issue 1414 - should not continue running timers after scope is destroyed', function() { | ||
spyOn(scope, 'next').and.callThrough(); | ||
spyOn(scope, 'next'); | ||
scope.interval = 2000; | ||
@@ -578,4 +592,4 @@ scope.$digest(); | ||
var elm = $compile( | ||
'<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' + | ||
'<uib-slide ng-repeat="slide in slides" active="slide.active" actual="slide">' + | ||
'<uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' + | ||
'<uib-slide ng-repeat="slide in slides" index="$index" actual="slide">' + | ||
'{{slide.content}}' + | ||
@@ -590,5 +604,5 @@ '</uib-slide>' + | ||
expect(angular.equals(ctrl.slides.map(function(slide) { | ||
return slide.actual; | ||
return slide.slide.actual; | ||
}), scope.slides)).toBe(true); | ||
}); | ||
}); |
@@ -325,7 +325,9 @@ angular.module('ui.bootstrap.dateparser', []) | ||
var endStr = i === map.length - 1 ? _format : ''; | ||
if (mapper.apply) { | ||
return str + mapper.apply.call(null, date); | ||
return str + mapper.apply.call(null, date) + endStr; | ||
} | ||
return str; | ||
return str + endStr; | ||
}, ''); | ||
@@ -332,0 +334,0 @@ }; |
@@ -264,2 +264,6 @@ describe('date parser', function() { | ||
}); | ||
it('should work correctly for literal text', function() { | ||
expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.yyyy foo', '17.11.2013 foo'); | ||
}); | ||
}); | ||
@@ -266,0 +270,0 @@ |
angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass', 'ui.bootstrap.position']) | ||
.value('$datepickerSuppressError', false) | ||
.value('uibDatepickerAttributeWarning', true) | ||
@@ -24,8 +25,9 @@ .constant('uibDatepickerConfig', { | ||
.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', 'uibDateParser', | ||
function($scope, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerSuppressError, dateParser) { | ||
.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', 'uibDatepickerAttributeWarning', 'uibDateParser', | ||
function($scope, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerSuppressError, datepickerAttributeWarning, dateParser) { | ||
var self = this, | ||
ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl; | ||
ngModelOptions = {}, | ||
watchListeners = []; | ||
watchListeners = [], | ||
optionsUsed = !!$attrs.datepickerOptions; | ||
@@ -35,4 +37,7 @@ // Modes chain | ||
if ($attrs.datepickerOptions) { | ||
angular.forEach([ | ||
if (optionsUsed) { | ||
[ | ||
'customClass', | ||
'dateDisabled', | ||
'datepickerMode', | ||
'formatDay', | ||
@@ -54,4 +59,12 @@ 'formatDayHeader', | ||
'yearRows' | ||
], function(key) { | ||
].forEach(function(key) { | ||
switch (key) { | ||
case 'customClass': | ||
case 'dateDisabled': | ||
$scope[key] = $scope.datepickerOptions[key] || angular.noop; | ||
break; | ||
case 'datepickerMode': | ||
$scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ? | ||
$scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode; | ||
break; | ||
case 'formatDay': | ||
@@ -63,3 +76,5 @@ case 'formatDayHeader': | ||
case 'formatYear': | ||
self[key] = angular.isDefined($scope.datepickerOptions[key]) ? $interpolate($scope.datepickerOptions[key])($scope.$parent) : datepickerConfig[key]; | ||
self[key] = angular.isDefined($scope.datepickerOptions[key]) ? | ||
$interpolate($scope.datepickerOptions[key])($scope.$parent) : | ||
datepickerConfig[key]; | ||
break; | ||
@@ -109,5 +124,6 @@ case 'showWeeks': | ||
self[key] = $scope[key] = angular.isDefined(value) ? value : datepickerOptions[key]; | ||
if (key === 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key]) || | ||
key === 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key])) { | ||
if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) || | ||
key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) { | ||
$scope.datepickerMode = self[key]; | ||
$scope.datepickerOptions.datepickerMode = self[key]; | ||
} | ||
@@ -122,3 +138,3 @@ }); | ||
if ($scope.datepickerOptions.initDate) { | ||
this.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date(); | ||
self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date(); | ||
$scope.$watch(function() { return $scope.datepickerOptions.initDate; }, function(initDate) { | ||
@@ -131,3 +147,3 @@ if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { | ||
} else { | ||
this.activeDate = new Date(); | ||
self.activeDate = new Date(); | ||
} | ||
@@ -140,2 +156,6 @@ } | ||
self[key] = angular.isDefined($attrs[key]) ? $interpolate($attrs[key])($scope.$parent) : datepickerConfig[key]; | ||
if (angular.isDefined($attrs[key]) && datepickerAttributeWarning) { | ||
$log.warn('uib-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead'); | ||
} | ||
}); | ||
@@ -147,5 +167,19 @@ | ||
$scope.$parent.$eval($attrs[key]) : datepickerConfig[key]; | ||
if (angular.isDefined($attrs[key]) && datepickerAttributeWarning) { | ||
$log.warn('uib-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead'); | ||
} | ||
}); | ||
angular.forEach(['dateDisabled', 'customClass'], function(key) { | ||
if (angular.isDefined($attrs[key]) && datepickerAttributeWarning) { | ||
$log.warn('uib-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead'); | ||
} | ||
}); | ||
if (angular.isDefined($attrs.startingDay)) { | ||
if (datepickerAttributeWarning) { | ||
$log.warn('uib-datepicker startingDay attribute usage is deprecated, use datepicker-options attribute instead'); | ||
} | ||
self.startingDay = $scope.$parent.$eval($attrs.startingDay); | ||
@@ -161,2 +195,6 @@ } else if (angular.isNumber(datepickerConfig.startingDay)) { | ||
if ($attrs[key]) { | ||
if (datepickerAttributeWarning) { | ||
$log.warn('uib-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead'); | ||
} | ||
watchListeners.push($scope.$parent.$watch($attrs[key], function(value) { | ||
@@ -182,2 +220,6 @@ if (value) { | ||
if ($attrs[key]) { | ||
if (datepickerAttributeWarning) { | ||
$log.warn('uib-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead'); | ||
} | ||
watchListeners.push($scope.$parent.$watch($attrs[key], function(value) { | ||
@@ -196,6 +238,12 @@ self[key] = $scope[key] = angular.isDefined(value) ? value : $attrs[key]; | ||
if (angular.isDefined($attrs.initDate)) { | ||
this.activeDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone) || new Date(); | ||
if (datepickerAttributeWarning) { | ||
$log.warn('uib-datepicker initDate attribute usage is deprecated, use datepicker-options attribute instead'); | ||
} | ||
var initDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone); | ||
this.activeDate = !isNaN(initDate) ? initDate : new Date(); | ||
watchListeners.push($scope.$parent.$watch($attrs.initDate, function(initDate) { | ||
if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { | ||
self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone); | ||
initDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone); | ||
self.activeDate = !isNaN(initDate) ? initDate : new Date(); | ||
self.refreshView(); | ||
@@ -207,5 +255,11 @@ } | ||
} | ||
if ($attrs.datepickerMode && datepickerAttributeWarning) { | ||
$log.warn('uib-datepicker datepickerMode attribute usage is deprecated, use datepicker-options attribute instead'); | ||
} | ||
$scope.datepickerMode = $scope.datepickerMode || | ||
datepickerConfig.datepickerMode; | ||
} | ||
$scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; | ||
$scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); | ||
@@ -233,5 +287,3 @@ | ||
if (ngModelCtrl.$modelValue) { | ||
this.activeDate = ngModelCtrl.$modelValue; | ||
} | ||
this.activeDate = ngModelCtrl.$modelValue || new Date(); | ||
@@ -299,3 +351,3 @@ ngModelCtrl.$render = function() { | ||
this.maxDate && this.compare(date, this.maxDate) > 0 || | ||
$attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}); | ||
$scope.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}); | ||
}; | ||
@@ -325,3 +377,5 @@ | ||
self.activeDate = date; | ||
$scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1]; | ||
setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]); | ||
$scope.$emit('uib:datepicker.mode'); | ||
} | ||
@@ -345,3 +399,5 @@ }; | ||
$scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction]; | ||
setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]); | ||
$scope.$emit('uib:datepicker.mode'); | ||
}; | ||
@@ -390,2 +446,9 @@ | ||
}); | ||
function setMode(mode) { | ||
$scope.datepickerMode = mode; | ||
if (optionsUsed) { | ||
$scope.datepickerOptions.datepickerMode = mode; | ||
} | ||
} | ||
}]) | ||
@@ -696,2 +759,4 @@ | ||
.value('uibDatepickerPopupAttributeWarning', true) | ||
.constant('uibDatepickerPopupConfig', { | ||
@@ -713,14 +778,15 @@ altInputFormats: [], | ||
onOpenFocus: true, | ||
showButtonBar: true | ||
showButtonBar: true, | ||
placement: 'auto bottom-left' | ||
}) | ||
.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', | ||
function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig) { | ||
.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', 'uibDatepickerPopupAttributeWarning', | ||
function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, datepickerPopupAttributeWarning) { | ||
var cache = {}, | ||
isHtml5DateInput = false; | ||
var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus, | ||
datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, | ||
datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl, | ||
ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = []; | ||
scope.watchData = {}; | ||
$scope.watchData = {}; | ||
@@ -730,17 +796,29 @@ this.init = function(_ngModel_) { | ||
ngModelOptions = _ngModel_.$options || datepickerConfig.ngModelOptions; | ||
closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection; | ||
appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; | ||
onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus; | ||
datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl; | ||
datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl; | ||
altInputFormats = angular.isDefined(attrs.altInputFormats) ? scope.$parent.$eval(attrs.altInputFormats) : datepickerPopupConfig.altInputFormats; | ||
closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ? | ||
$scope.$parent.$eval($attrs.closeOnDateSelection) : | ||
datepickerPopupConfig.closeOnDateSelection; | ||
appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ? | ||
$scope.$parent.$eval($attrs.datepickerAppendToBody) : | ||
datepickerPopupConfig.appendToBody; | ||
onOpenFocus = angular.isDefined($attrs.onOpenFocus) ? | ||
$scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus; | ||
datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ? | ||
$attrs.datepickerPopupTemplateUrl : | ||
datepickerPopupConfig.datepickerPopupTemplateUrl; | ||
datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ? | ||
$attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl; | ||
altInputFormats = angular.isDefined($attrs.altInputFormats) ? | ||
$scope.$parent.$eval($attrs.altInputFormats) : | ||
datepickerPopupConfig.altInputFormats; | ||
scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; | ||
$scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ? | ||
$scope.$parent.$eval($attrs.showButtonBar) : | ||
datepickerPopupConfig.showButtonBar; | ||
if (datepickerPopupConfig.html5Types[attrs.type]) { | ||
dateFormat = datepickerPopupConfig.html5Types[attrs.type]; | ||
if (datepickerPopupConfig.html5Types[$attrs.type]) { | ||
dateFormat = datepickerPopupConfig.html5Types[$attrs.type]; | ||
isHtml5DateInput = true; | ||
} else { | ||
dateFormat = attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup; | ||
attrs.$observe('uibDatepickerPopup', function(value, oldValue) { | ||
dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup; | ||
$attrs.$observe('uibDatepickerPopup', function(value, oldValue) { | ||
var newDateFormat = value || datepickerPopupConfig.datepickerPopup; | ||
@@ -764,3 +842,3 @@ // Invalidate the $modelValue to ensure that formatters re-run | ||
if (isHtml5DateInput && attrs.uibDatepickerPopup) { | ||
if (isHtml5DateInput && $attrs.uibDatepickerPopup) { | ||
throw new Error('HTML5 date input types do not support custom formats.'); | ||
@@ -771,4 +849,9 @@ } | ||
popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>'); | ||
scope.ngModelOptions = angular.copy(ngModelOptions); | ||
scope.ngModelOptions.timezone = null; | ||
$scope.ngModelOptions = angular.copy(ngModelOptions); | ||
$scope.ngModelOptions.timezone = null; | ||
if ($scope.ngModelOptions.updateOnDefault === true) { | ||
$scope.ngModelOptions.updateOn = $scope.ngModelOptions.updateOn ? | ||
$scope.ngModelOptions.updateOn + ' default' : 'default'; | ||
} | ||
popupEl.attr({ | ||
@@ -786,3 +869,3 @@ 'ng-model': 'date', | ||
if (isHtml5DateInput) { | ||
if (attrs.type === 'month') { | ||
if ($attrs.type === 'month') { | ||
datepickerEl.attr('datepicker-mode', '"month"'); | ||
@@ -793,19 +876,16 @@ datepickerEl.attr('min-mode', 'month'); | ||
if (scope.datepickerOptions) { | ||
angular.forEach(scope.datepickerOptions, function(value, option) { | ||
// Ignore this options, will be managed later | ||
if (['minDate', 'maxDate', 'minMode', 'maxMode', 'initDate', 'datepickerMode'].indexOf(option) === -1) { | ||
datepickerEl.attr(cameltoDash(option), value); | ||
} else { | ||
datepickerEl.attr(cameltoDash(option), 'datepickerOptions.' + option); | ||
} | ||
}); | ||
if ($scope.datepickerOptions) { | ||
datepickerEl.attr('datepicker-options', 'datepickerOptions'); | ||
} | ||
angular.forEach(['minMode', 'maxMode', 'datepickerMode', 'shortcutPropagation'], function(key) { | ||
if (attrs[key]) { | ||
var getAttribute = $parse(attrs[key]); | ||
if ($attrs[key]) { | ||
if (datepickerPopupAttributeWarning) { | ||
$log.warn('uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead'); | ||
} | ||
var getAttribute = $parse($attrs[key]); | ||
var propConfig = { | ||
get: function() { | ||
return getAttribute(scope.$parent); | ||
return getAttribute($scope.$parent); | ||
} | ||
@@ -820,7 +900,7 @@ }; | ||
propConfig.set = function(v) { | ||
setAttribute(scope.$parent, v); | ||
setAttribute($scope.$parent, v); | ||
}; | ||
} | ||
Object.defineProperty(scope.watchData, key, propConfig); | ||
Object.defineProperty($scope.watchData, key, propConfig); | ||
} | ||
@@ -830,6 +910,10 @@ }); | ||
angular.forEach(['minDate', 'maxDate', 'initDate'], function(key) { | ||
if (attrs[key]) { | ||
var getAttribute = $parse(attrs[key]); | ||
if ($attrs[key]) { | ||
if (datepickerPopupAttributeWarning) { | ||
$log.warn('uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead'); | ||
} | ||
watchListeners.push(scope.$parent.$watch(getAttribute, function(value) { | ||
var getAttribute = $parse($attrs[key]); | ||
watchListeners.push($scope.$parent.$watch(getAttribute, function(value) { | ||
if (key === 'minDate' || key === 'maxDate') { | ||
@@ -844,5 +928,6 @@ if (value === null) { | ||
scope.watchData[key] = value === null ? null : cache[key]; | ||
$scope.watchData[key] = value === null ? null : cache[key]; | ||
} else { | ||
scope.watchData[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone); | ||
var date = value ? new Date(value) : new Date(); | ||
$scope.watchData[key] = dateParser.fromTimezone(date, ngModelOptions.timezone); | ||
} | ||
@@ -855,3 +940,7 @@ })); | ||
if (attrs.dateDisabled) { | ||
if ($attrs.dateDisabled) { | ||
if (datepickerPopupAttributeWarning) { | ||
$log.warn('uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead'); | ||
} | ||
datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); | ||
@@ -861,8 +950,16 @@ } | ||
angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', 'showWeeks', 'startingDay', 'yearRows', 'yearColumns'], function(key) { | ||
if (angular.isDefined(attrs[key])) { | ||
datepickerEl.attr(cameltoDash(key), attrs[key]); | ||
if (angular.isDefined($attrs[key])) { | ||
if (datepickerPopupAttributeWarning) { | ||
$log.warn('uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead'); | ||
} | ||
datepickerEl.attr(cameltoDash(key), $attrs[key]); | ||
} | ||
}); | ||
if (attrs.customClass) { | ||
if ($attrs.customClass) { | ||
if (datepickerPopupAttributeWarning) { | ||
$log.warn('uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead'); | ||
} | ||
datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })'); | ||
@@ -878,17 +975,17 @@ } | ||
if (ngModel.$isEmpty(value)) { | ||
scope.date = value; | ||
$scope.date = value; | ||
return value; | ||
} | ||
scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone); | ||
$scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone); | ||
if (angular.isNumber(scope.date)) { | ||
scope.date = new Date(scope.date); | ||
if (angular.isNumber($scope.date)) { | ||
$scope.date = new Date($scope.date); | ||
} | ||
return dateParser.filter(scope.date, dateFormat); | ||
return dateParser.filter($scope.date, dateFormat); | ||
}); | ||
} else { | ||
ngModel.$formatters.push(function(value) { | ||
scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone); | ||
$scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone); | ||
return value; | ||
@@ -900,8 +997,8 @@ }); | ||
ngModel.$viewChangeListeners.push(function() { | ||
scope.date = parseDateString(ngModel.$viewValue); | ||
$scope.date = parseDateString(ngModel.$viewValue); | ||
}); | ||
element.on('keydown', inputKeydownBind); | ||
$element.on('keydown', inputKeydownBind); | ||
$popup = $compile(popupEl)(scope); | ||
$popup = $compile(popupEl)($scope); | ||
// Prevent jQuery cache memory leak (template is now redundant after linking) | ||
@@ -913,10 +1010,10 @@ popupEl.remove(); | ||
} else { | ||
element.after($popup); | ||
$element.after($popup); | ||
} | ||
scope.$on('$destroy', function() { | ||
if (scope.isOpen === true) { | ||
$scope.$on('$destroy', function() { | ||
if ($scope.isOpen === true) { | ||
if (!$rootScope.$$phase) { | ||
scope.$apply(function() { | ||
scope.isOpen = false; | ||
$scope.$apply(function() { | ||
$scope.isOpen = false; | ||
}); | ||
@@ -927,4 +1024,8 @@ } | ||
$popup.remove(); | ||
element.off('keydown', inputKeydownBind); | ||
$element.off('keydown', inputKeydownBind); | ||
$document.off('click', documentClickBind); | ||
if (scrollParentEl) { | ||
scrollParentEl.off('scroll', positionPopup); | ||
} | ||
angular.element($window).off('resize', positionPopup); | ||
@@ -938,7 +1039,7 @@ //Clear all watch listeners on destroy | ||
scope.getText = function(key) { | ||
return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; | ||
$scope.getText = function(key) { | ||
return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; | ||
}; | ||
scope.isDisabled = function(date) { | ||
$scope.isDisabled = function(date) { | ||
if (date === 'today') { | ||
@@ -948,7 +1049,7 @@ date = new Date(); | ||
return scope.watchData.minDate && scope.compare(date, cache.minDate) < 0 || | ||
scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0; | ||
return $scope.watchData.minDate && $scope.compare(date, cache.minDate) < 0 || | ||
$scope.watchData.maxDate && $scope.compare(date, cache.maxDate) > 0; | ||
}; | ||
scope.compare = function(date1, date2) { | ||
$scope.compare = function(date1, date2) { | ||
return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); | ||
@@ -958,29 +1059,31 @@ }; | ||
// Inner change | ||
scope.dateSelection = function(dt) { | ||
$scope.dateSelection = function(dt) { | ||
if (angular.isDefined(dt)) { | ||
scope.date = dt; | ||
$scope.date = dt; | ||
} | ||
var date = scope.date ? dateParser.filter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function | ||
element.val(date); | ||
var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function | ||
$element.val(date); | ||
ngModel.$setViewValue(date); | ||
if (closeOnDateSelection) { | ||
scope.isOpen = false; | ||
element[0].focus(); | ||
$scope.isOpen = false; | ||
$element[0].focus(); | ||
} | ||
}; | ||
scope.keydown = function(evt) { | ||
$scope.keydown = function(evt) { | ||
if (evt.which === 27) { | ||
evt.stopPropagation(); | ||
scope.isOpen = false; | ||
element[0].focus(); | ||
$scope.isOpen = false; | ||
$element[0].focus(); | ||
} | ||
}; | ||
scope.select = function(date) { | ||
$scope.select = function(date, evt) { | ||
evt.stopPropagation(); | ||
if (date === 'today') { | ||
var today = new Date(); | ||
if (angular.isDate(scope.date)) { | ||
date = new Date(scope.date); | ||
if (angular.isDate($scope.date)) { | ||
date = new Date($scope.date); | ||
date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); | ||
@@ -991,34 +1094,51 @@ } else { | ||
} | ||
scope.dateSelection(date); | ||
$scope.dateSelection(date); | ||
}; | ||
scope.close = function() { | ||
scope.isOpen = false; | ||
element[0].focus(); | ||
$scope.close = function(evt) { | ||
evt.stopPropagation(); | ||
$scope.isOpen = false; | ||
$element[0].focus(); | ||
}; | ||
scope.disabled = angular.isDefined(attrs.disabled) || false; | ||
if (attrs.ngDisabled) { | ||
watchListeners.push(scope.$parent.$watch($parse(attrs.ngDisabled), function(disabled) { | ||
scope.disabled = disabled; | ||
$scope.disabled = angular.isDefined($attrs.disabled) || false; | ||
if ($attrs.ngDisabled) { | ||
watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) { | ||
$scope.disabled = disabled; | ||
})); | ||
} | ||
scope.$watch('isOpen', function(value) { | ||
$scope.$watch('isOpen', function(value) { | ||
if (value) { | ||
if (!scope.disabled) { | ||
scope.position = appendToBody ? $position.offset(element) : $position.position(element); | ||
scope.position.top = scope.position.top + element.prop('offsetHeight'); | ||
if (!$scope.disabled) { | ||
$timeout(function() { | ||
positionPopup(); | ||
$timeout(function() { | ||
if (onOpenFocus) { | ||
scope.$broadcast('uib:datepicker.focus'); | ||
$scope.$broadcast('uib:datepicker.focus'); | ||
} | ||
$document.on('click', documentClickBind); | ||
var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; | ||
if (appendToBody || $position.parsePlacement(placement)[2]) { | ||
scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element)); | ||
if (scrollParentEl) { | ||
scrollParentEl.on('scroll', positionPopup); | ||
} | ||
} else { | ||
scrollParentEl = null; | ||
} | ||
angular.element($window).on('resize', positionPopup); | ||
}, 0, false); | ||
} else { | ||
scope.isOpen = false; | ||
$scope.isOpen = false; | ||
} | ||
} else { | ||
$document.off('click', documentClickBind); | ||
if (scrollParentEl) { | ||
scrollParentEl.off('scroll', positionPopup); | ||
} | ||
angular.element($window).off('resize', positionPopup); | ||
} | ||
@@ -1032,6 +1152,6 @@ }); | ||
function parseDateString(viewValue) { | ||
var date = dateParser.parse(viewValue, dateFormat, scope.date); | ||
var date = dateParser.parse(viewValue, dateFormat, $scope.date); | ||
if (isNaN(date)) { | ||
for (var i = 0; i < altInputFormats.length; i++) { | ||
date = dateParser.parse(viewValue, altInputFormats[i], scope.date); | ||
date = dateParser.parse(viewValue, altInputFormats[i], $scope.date); | ||
if (!isNaN(date)) { | ||
@@ -1072,3 +1192,3 @@ return date; | ||
if (!attrs.ngRequired && !value) { | ||
if (!$attrs.ngRequired && !value) { | ||
return true; | ||
@@ -1097,3 +1217,3 @@ } | ||
function documentClickBind(event) { | ||
if (!scope.isOpen && scope.disabled) { | ||
if (!$scope.isOpen && $scope.disabled) { | ||
return; | ||
@@ -1103,9 +1223,9 @@ } | ||
var popup = $popup[0]; | ||
var dpContainsTarget = element[0].contains(event.target); | ||
var dpContainsTarget = $element[0].contains(event.target); | ||
// The popup node may not be an element node | ||
// In some browsers (IE) only element nodes have the 'contains' function | ||
var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target); | ||
if (scope.isOpen && !(dpContainsTarget || popupContainsTarget)) { | ||
scope.$apply(function() { | ||
scope.isOpen = false; | ||
if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) { | ||
$scope.$apply(function() { | ||
$scope.isOpen = false; | ||
}); | ||
@@ -1116,17 +1236,33 @@ } | ||
function inputKeydownBind(evt) { | ||
if (evt.which === 27 && scope.isOpen) { | ||
if (evt.which === 27 && $scope.isOpen) { | ||
evt.preventDefault(); | ||
evt.stopPropagation(); | ||
scope.$apply(function() { | ||
scope.isOpen = false; | ||
$scope.$apply(function() { | ||
$scope.isOpen = false; | ||
}); | ||
element[0].focus(); | ||
} else if (evt.which === 40 && !scope.isOpen) { | ||
$element[0].focus(); | ||
} else if (evt.which === 40 && !$scope.isOpen) { | ||
evt.preventDefault(); | ||
evt.stopPropagation(); | ||
scope.$apply(function() { | ||
scope.isOpen = true; | ||
$scope.$apply(function() { | ||
$scope.isOpen = true; | ||
}); | ||
} | ||
} | ||
function positionPopup() { | ||
if ($scope.isOpen) { | ||
var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup')); | ||
var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; | ||
var position = $position.positionElements($element, dpElement, placement, appendToBody); | ||
dpElement.css({top: position.top + 'px', left: position.left + 'px'}); | ||
if (dpElement.hasClass('uib-position-measure')) { | ||
dpElement.removeClass('uib-position-measure'); | ||
} | ||
} | ||
} | ||
$scope.$on('uib:datepicker.mode', function() { | ||
$timeout(positionPopup, 0, false); | ||
}); | ||
}]) | ||
@@ -1133,0 +1269,0 @@ |
@@ -11,13 +11,29 @@ angular.module('ui.bootstrap.demo').controller('DatepickerDemoCtrl', function ($scope) { | ||
$scope.inlineOptions = { | ||
customClass: getDayClass, | ||
minDate: new Date(), | ||
showWeeks: true | ||
}; | ||
$scope.dateOptions = { | ||
dateDisabled: disabled, | ||
formatYear: 'yy', | ||
maxDate: new Date(2020, 5, 22), | ||
minDate: new Date(), | ||
startingDay: 1 | ||
}; | ||
// Disable weekend selection | ||
$scope.disabled = function(date, mode) { | ||
function disabled(data) { | ||
var date = data.date, | ||
mode = data.mode; | ||
return mode === 'day' && (date.getDay() === 0 || date.getDay() === 6); | ||
}; | ||
} | ||
$scope.toggleMin = function() { | ||
$scope.minDate = $scope.minDate ? null : new Date(); | ||
$scope.inlineOptions.minDate = $scope.inlineOptions.minDate ? null : new Date(); | ||
$scope.dateOptions.minDate = $scope.inlineOptions.minDate; | ||
}; | ||
$scope.toggleMin(); | ||
$scope.maxDate = new Date(2020, 5, 22); | ||
@@ -36,7 +52,2 @@ $scope.open1 = function() { | ||
$scope.dateOptions = { | ||
formatYear: 'yy', | ||
startingDay: 1 | ||
}; | ||
$scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate']; | ||
@@ -58,15 +69,16 @@ $scope.format = $scope.formats[0]; | ||
afterTomorrow.setDate(tomorrow.getDate() + 1); | ||
$scope.events = | ||
[ | ||
{ | ||
date: tomorrow, | ||
status: 'full' | ||
}, | ||
{ | ||
date: afterTomorrow, | ||
status: 'partially' | ||
} | ||
]; | ||
$scope.events = [ | ||
{ | ||
date: tomorrow, | ||
status: 'full' | ||
}, | ||
{ | ||
date: afterTomorrow, | ||
status: 'partially' | ||
} | ||
]; | ||
$scope.getDayClass = function(date, mode) { | ||
function getDayClass(data) { | ||
var date = data.date, | ||
mode = data.mode; | ||
if (mode === 'day') { | ||
@@ -85,3 +97,3 @@ var dayToCheck = new Date(date).setHours(0,0,0,0); | ||
return ''; | ||
}; | ||
} | ||
}); |
@@ -15,104 +15,2 @@ Our datepicker is flexible and fully customizable. | ||
* `custom-class (date, mode)` | ||
<small class="badge">$</small> - | ||
An optional expression to add classes based on passing a date and current mode. | ||
* `date-disabled (date, mode)` | ||
<small class="badge">$</small> - | ||
An optional expression to disable visible options based on passing a date and current mode. | ||
* `datepicker-mode` | ||
<small class="badge">$</small> | ||
<small class="badge">C</small> | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `day`)_ - | ||
Current mode of the datepicker _(day|month|year)_. Can be used to initialize the datepicker in a specific mode. | ||
* `datepicker-options` | ||
<small class="badge">$</small> - | ||
An optional object to configure the datepicker in one place. If this attribute is used, all supported options must be specified instead of the attributes. | ||
The supported options are: | ||
- formatDay | ||
- formatDayHeader | ||
- formatDayTitle | ||
- formatMonth | ||
- formatMonthTitle | ||
- formatYear | ||
- initDate | ||
- maxDate | ||
- maxMode | ||
- minDate | ||
- minMode | ||
- shortcutPropagation | ||
- showWeeks | ||
- startingDay | ||
- yearColumns | ||
- yearRows | ||
* `format-day` | ||
<small class="badge">C</small> | ||
_(Default: `dd`)_ - | ||
Format of day in month. | ||
* `format-month` | ||
<small class="badge">C</small> | ||
_(Default: `MMMM`)_ - | ||
Format of month in year. | ||
* `format-year` | ||
<small class="badge">C</small> | ||
_(Default: `yyyy`)_ - | ||
Format of year in year range. | ||
* `format-day-header` | ||
<small class="badge">C</small> | ||
_(Default: `EEE`)_ - | ||
Format of day in week header. | ||
* `format-day-title` | ||
<small class="badge">C</small> | ||
_(Default: `MMMM yyyy`)_ - | ||
Format of title when selecting day. | ||
* `format-month-title` | ||
<small class="badge">C</small> | ||
_(Default: `yyyy`)_ - | ||
Format of title when selecting month. | ||
* `init-date` | ||
<small class="badge">$</small> | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `null`)_ - | ||
The initial date view when no model value is specified. | ||
* `max-date` | ||
<small class="badge">$</small> | ||
<small class="badge">C</small> | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `null`)_ - | ||
Defines the maximum available date. | ||
* `max-mode` | ||
<small class="badge">$</small> | ||
<small class="badge">C</small> | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `year`)_ - | ||
Sets an upper limit for mode. | ||
* `min-date` | ||
<small class="badge">$</small> | ||
<small class="badge">C</small> | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `null`)_ - | ||
Defines the minimum available date. | ||
* `min-mode` | ||
<small class="badge">$</small> | ||
<small class="badge">C</small> | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `day`)_ - | ||
Sets a lower limit for mode. | ||
* `ng-model` | ||
@@ -130,35 +28,107 @@ <small class="badge">$</small> | ||
* timezone | ||
* `shortcut-propagation` | ||
<small class="badge">$</small> | ||
<small class="badge">C</small> | ||
_(Default: `false`)_ - | ||
An option to disable the propagation of the keydown event. | ||
* `show-weeks` | ||
<small class="badge">$</small> | ||
<small class="badge">C</small> | ||
_(Default: `true`)_ - | ||
Whether to display week numbers. | ||
* `starting-day` | ||
<small class="badge">$</small> | ||
<small class="badge">C</small> | ||
*(Default: `$locale.DATETIME_FORMATS.FIRSTDAYOFWEEK`)* - | ||
Starting day of the week from 0-6 (0=Sunday, ..., 6=Saturday). | ||
* `template-url` | ||
_(Default: `uib/template/datepicker/datepicker.html`)_ - | ||
Add the ability to override the template used on the component. | ||
Apart from the previous settings, to configure the uib-datepicker you need to create an object in Javascript with all the options and use it on the `datepicker-options` attribute: | ||
* `year-rows` | ||
<small class="badge">$</small> | ||
<small class="badge">C</small> | ||
_(Default: `4`)_ - | ||
Number of rows displayed in year selection. | ||
* `datepicker-options` | ||
<small class="badge">$</small> - | ||
An object to configure the datepicker in one place. | ||
* `year-columns` | ||
<small class="badge">$</small> | ||
<small class="badge">C</small> | ||
_(Default: `5`)_ - | ||
* `customClass (date, mode)` - | ||
An optional expression to add classes based on passing a date and current mode. | ||
* `dateDisabled (date, mode)` - | ||
An optional expression to disable visible options based on passing a date and current mode. | ||
* `datepickerMode` | ||
<small class="badge">C</small> | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `day`)_ - | ||
Current mode of the datepicker _(day|month|year)_. Can be used to initialize the datepicker in a specific mode. | ||
* `formatDay` | ||
<small class="badge">C</small> | ||
_(Default: `dd`)_ - | ||
Format of day in month. | ||
* `formatMonth` | ||
<small class="badge">C</small> | ||
_(Default: `MMMM`)_ - | ||
Format of month in year. | ||
* `formatYear` | ||
<small class="badge">C</small> | ||
_(Default: `yyyy`)_ - | ||
Format of year in year range. | ||
* `formatDayHeader` | ||
<small class="badge">C</small> | ||
_(Default: `EEE`)_ - | ||
Format of day in week header. | ||
* `formatDayTitle` | ||
<small class="badge">C</small> | ||
_(Default: `MMMM yyyy`)_ - | ||
Format of title when selecting day. | ||
* `formatMonthTitle` | ||
<small class="badge">C</small> | ||
_(Default: `yyyy`)_ - | ||
Format of title when selecting month. | ||
* `initDate` | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `null`)_ - | ||
The initial date view when no model value is specified. | ||
* `maxDate` | ||
<small class="badge">C</small> | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `null`)_ - | ||
Defines the maximum available date. | ||
* `maxMode` | ||
<small class="badge">C</small> | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `year`)_ - | ||
Sets an upper limit for mode. | ||
* `minDate` | ||
<small class="badge">C</small> | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `null`)_ - | ||
Defines the minimum available date. | ||
* `minMode` | ||
<small class="badge">C</small> | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `day`)_ - | ||
Sets a lower limit for mode. | ||
* `shortcutPropagation` | ||
<small class="badge">C</small> | ||
_(Default: `false`)_ - | ||
An option to disable the propagation of the keydown event. | ||
* `showWeeks` | ||
<small class="badge">C</small> | ||
_(Default: `true`)_ - | ||
Whether to display week numbers. | ||
* `startingDay` | ||
<small class="badge">C</small> | ||
*(Default: `$locale.DATETIME_FORMATS.FIRSTDAYOFWEEK`)* - | ||
Starting day of the week from 0-6 (0=Sunday, ..., 6=Saturday). | ||
* `yearRows` | ||
<small class="badge">C</small> | ||
_(Default: `4`)_ - | ||
Number of rows displayed in year selection. | ||
* `yearColumns` | ||
<small class="badge">C</small> | ||
_(Default: `5`)_ - | ||
Number of columns displayed in year selection. | ||
@@ -215,3 +185,3 @@ | ||
_(Default: `uib/template/datepicker/datepicker.html`)_ - | ||
Add the ability to override the template used on the component (inner uib-datepicker). | ||
Add the ability to override the template used on the component (inner uib-datepicker). | ||
@@ -241,2 +211,20 @@ * `is-open` | ||
* `popup-placement` | ||
<small class="badge">C</small> | ||
_(Default: `auto bottom-left`, Config: 'placement')_ - | ||
Passing in 'auto' separated by a space before the placement will enable auto positioning, e.g: "auto bottom-left". The popup will attempt to position where it fits in the closest scrollable ancestor. Accepts: | ||
* `top` - popup on top, horizontally centered on input element. | ||
* `top-left` - popup on top, left edge aligned with input element left edge. | ||
* `top-right` - popup on top, right edge aligned with input element right edge. | ||
* `bottom` - popup on bottom, horizontally centered on input element. | ||
* `bottom-left` - popup on bottom, left edge aligned with input element left edge. | ||
* `bottom-right` - popup on bottom, right edge aligned with input element right edge. | ||
* `left` - popup on left, vertically centered on input element. | ||
* `left-top` - popup on left, top edge aligned with input element top edge. | ||
* `left-bottom` - popup on left, bottom edge aligned with input element bottom edge. | ||
* `right` - popup on right, vertically centered on input element. | ||
* `right-top` - popup on right, top edge aligned with input element top edge. | ||
* `right-bottom` - popup on right, bottom edge aligned with input element bottom edge. | ||
* `uib-datepicker-popup` | ||
@@ -247,5 +235,2 @@ <small class="badge">C</small> | ||
**Note:** With the exception of `ng-model[-options]` and `templateUrl`, you can configure the wrapped datepicker using its attributes in the popup as well. | ||
But beware **this possibility may be deprecated on the near future.** | ||
### Keyboard support | ||
@@ -252,0 +237,0 @@ |
@@ -224,8 +224,11 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) | ||
var openContainer = appendTo ? appendTo : $element; | ||
var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass); | ||
$animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() { | ||
if (angular.isDefined(isOpen) && isOpen !== wasOpen) { | ||
toggleInvoker($scope, { open: !!isOpen }); | ||
} | ||
}); | ||
if (hasOpenClass === !isOpen) { | ||
$animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() { | ||
if (angular.isDefined(isOpen) && isOpen !== wasOpen) { | ||
toggleInvoker($scope, { open: !!isOpen }); | ||
} | ||
}); | ||
} | ||
@@ -232,0 +235,0 @@ if (isOpen) { |
@@ -34,3 +34,3 @@ angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope, $uibModal, $log) { | ||
// Please note that $modalInstance represents a modal window (instance) dependency. | ||
// Please note that $uibModalInstance represents a modal window (instance) dependency. | ||
// It is not the same as the $uibModal service used above. | ||
@@ -37,0 +37,0 @@ |
@@ -122,2 +122,4 @@ `$uibModal` is a service to create modal windows. | ||
Also, when using `bindToController`, you can define an `$onInit` method in the controller that will fire upon initialization. | ||
--- | ||
@@ -124,0 +126,0 @@ |
@@ -204,2 +204,8 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap']) | ||
$q.when(animationPromise).then(function() { | ||
// Notify {@link $modalStack} that modal is rendered. | ||
var modal = $modalStack.getTop(); | ||
if (modal) { | ||
$modalStack.modalRendered(modal.key); | ||
} | ||
/** | ||
@@ -226,8 +232,2 @@ * If something within the freshly-opened modal already has focus (perhaps via a | ||
}); | ||
// Notify {@link $modalStack} that modal is rendered. | ||
var modal = $modalStack.getTop(); | ||
if (modal) { | ||
$modalStack.modalRendered(modal.key); | ||
} | ||
}); | ||
@@ -484,3 +484,5 @@ } | ||
.then(function() { | ||
$animate.addClass(appendToElement, modalBodyClass); | ||
if (!modal.scope.$$uibDestructionScheduled) { | ||
$animate.addClass(appendToElement, modalBodyClass); | ||
} | ||
}); | ||
@@ -691,2 +693,5 @@ | ||
angular.extend(ctrlInstance, providedScope); | ||
if (angular.isFunction(ctrlInstance.$onInit)) { | ||
ctrlInstance.$onInit(); | ||
} | ||
} | ||
@@ -693,0 +698,0 @@ |
@@ -308,2 +308,38 @@ describe('$uibResolve', function() { | ||
it('should resolve rendered promise when animation is complete', function() { | ||
var modalInstance = { | ||
result: $q.defer(), | ||
opened: $q.defer(), | ||
closed: $q.defer(), | ||
rendered: $q.defer(), | ||
close: function (result) { | ||
return $uibModalStack.close(modalInstance, result); | ||
}, | ||
dismiss: function (reason) { | ||
return $uibModalStack.dismiss(modalInstance, reason); | ||
} | ||
}; | ||
var rendered = false; | ||
modalInstance.rendered.promise.then(function() { | ||
rendered = true; | ||
}); | ||
$uibModalStack.open(modalInstance, { | ||
appendTo: angular.element(document.body), | ||
scope: $rootScope.$new(), | ||
deferred: modalInstance.result, | ||
renderDeferred: modalInstance.rendered, | ||
closedDeferred: modalInstance.closed, | ||
content: '<div id="test">test</div>' | ||
}); | ||
$rootScope.$digest(); | ||
expect(rendered).toBe(false); | ||
$animate.flush(); | ||
expect(rendered).toBe(true); | ||
}); | ||
it('should not throw an exception on a second dismiss', function() { | ||
@@ -590,2 +626,4 @@ var modal = open({template: '<div>Content</div>'}); | ||
expect(modal.result).toBeResolvedWith('closed ok'); | ||
element.remove(); | ||
} | ||
@@ -793,2 +831,23 @@ | ||
}); | ||
it('should have $onInit called', function() { | ||
var $scope = $rootScope.$new(true); | ||
var $onInit = jasmine.createSpy('$onInit'); | ||
$scope.foo = 'bar'; | ||
open({ | ||
template: '<div>{{test.fromCtrl}} {{test.closeDismissPresent()}} {{test.foo}}</div>', | ||
controller: function($uibModalInstance) { | ||
this.$onInit = $onInit; | ||
this.fromCtrl = 'Content from ctrl'; | ||
this.closeDismissPresent = function() { | ||
return angular.isFunction(this.$close) && angular.isFunction(this.$dismiss); | ||
}; | ||
}, | ||
controllerAs: 'test', | ||
bindToController: true, | ||
scope: $scope | ||
}); | ||
expect($document).toHaveModalOpenWithContent('Content from ctrl true bar', 'div'); | ||
expect($onInit).toHaveBeenCalled(); | ||
}); | ||
}); | ||
@@ -1063,2 +1122,4 @@ | ||
expect($document).not.toHaveModalOpenWithContent('Content', 'div'); | ||
element.remove(); | ||
}); | ||
@@ -1186,2 +1247,12 @@ | ||
}); | ||
it('should not add the modal-open class if modal is closed before animation', function() { | ||
var modal = open({ | ||
template: '<div>dummy modal</div>' | ||
}, true); | ||
close(modal); | ||
expect(body).not.toHaveClass('modal-open'); | ||
}); | ||
}); | ||
@@ -1188,0 +1259,0 @@ }); |
@@ -21,3 +21,3 @@ A lightweight pagination directive that is focused on ... providing pagination & will take care of visualising a pagination bar and enable / disable buttons correctly! | ||
Whether to display Previous / Next buttons. | ||
* `first-text` | ||
@@ -45,3 +45,3 @@ <small class="badge">C</small> | ||
Text for Last button. | ||
* `max-size` | ||
@@ -52,3 +52,3 @@ <small class="badge">$</small> | ||
Limit number for pagination size. | ||
* `next-text` | ||
@@ -62,3 +62,3 @@ <small class="badge">C</small> | ||
This can be used to call a function whenever the page changes. | ||
* `ng-disabled` | ||
@@ -81,2 +81,6 @@ <small class="badge">$</small> | ||
* `page-label` | ||
_(Default: `angular.identity`)_ - | ||
An optional expression to override the page label based on passing the current page indexes. Supports page number with `$page` in the template. | ||
* `previous-text` | ||
@@ -96,3 +100,3 @@ <small class="badge">C</small> | ||
Override the template for the component with a custom provided template | ||
* `total-items` | ||
@@ -99,0 +103,0 @@ <small class="badge">$</small> |
@@ -8,3 +8,4 @@ angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging']) | ||
forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses, | ||
boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers; | ||
boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers, | ||
pageLabel = angular.isDefined($attrs.pageLabel) ? function(idx) { return $scope.$parent.$eval($attrs.pageLabel, {$page: idx}); } : angular.identity; | ||
$scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks; | ||
@@ -61,3 +62,3 @@ $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks; | ||
for (var number = startPage; number <= endPage; number++) { | ||
var page = makePage(number, number, number === currentPage); | ||
var page = makePage(number, pageLabel(number), number === currentPage); | ||
pages.push(page); | ||
@@ -64,0 +65,0 @@ } |
@@ -879,3 +879,6 @@ describe('pagination directive', function() { | ||
beforeEach(function() { | ||
element = $compile('<uib-pagination boundary-links="true" first-text="<<" previous-text="<" next-text=">" last-text=">>" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope); | ||
$rootScope.pageLabel = function(id) { | ||
return 'test_'+ id; | ||
}; | ||
element = $compile('<uib-pagination boundary-links="true" page-label="pageLabel($page)" first-text="<<" previous-text="<" next-text=">" last-text=">>" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope); | ||
$rootScope.$digest(); | ||
@@ -894,2 +897,9 @@ }); | ||
}); | ||
it('has the label of the page as text in each page item', function() { | ||
for (var i = 1; i <= 5; i++) { | ||
// +1 because the first element is a < | ||
expect(getPaginationEl(i+1).text()).toEqual('test_'+i); | ||
} | ||
}); | ||
}); | ||
@@ -896,0 +906,0 @@ |
@@ -22,3 +22,3 @@ angular.module('ui.bootstrap.paging', []) | ||
if ($attrs.itemsPerPage) { | ||
ctrl._watchers.push($scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { | ||
ctrl._watchers.push($scope.$parent.$watch($attrs.itemsPerPage, function(value) { | ||
ctrl.itemsPerPage = parseInt(value, 10); | ||
@@ -25,0 +25,0 @@ $scope.totalPages = ctrl.calculateTotalPages(); |
@@ -33,3 +33,4 @@ A lightweight, extensible directive for fancy popover creation. The popover | ||
<small class="badge">$</small> | ||
_(Default: `false`)_ - | ||
<small class="badge">C</small> | ||
_(Default: `false`, Config: `appendToBody`)_ - | ||
Should the popover be appended to '$body' instead of the parent element? | ||
@@ -36,0 +37,0 @@ |
@@ -57,4 +57,22 @@ The `$uibPosition` service provides a set of DOM utilities used internally to absolute-position an element in relation to another element (tooltips, popovers, typeaheads etc...). | ||
#### isScrollable(element, includeHidden) | ||
Determines if an element is scrollable. | ||
##### parameters | ||
* `element` | ||
_(Type: `element`)_ - | ||
The element to check. | ||
* `includeHidden` | ||
_(Type: `boolean`, Default: `false`, optional)_ - Should scroll style of 'hidden' be considered. | ||
##### returns | ||
* _(Type: `boolean`)_ - | ||
Whether the element is scrollable. | ||
#### scrollParent(element, includeHidden) | ||
Gets the closest scrollable ancestor. Concept from the jQueryUI [scrollParent.js](https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js). | ||
@@ -95,11 +113,11 @@ | ||
The width of the element. | ||
* `height` | ||
_(Type: `number`)_ - | ||
The height of the element. | ||
* `top` | ||
_(Type: `number`)_ - | ||
Distance to top edge of offset parent. | ||
* `left` | ||
@@ -110,3 +128,3 @@ _(Type: `number`)_ - | ||
#### offset(element) | ||
A read-only equivalent of jQuery's [offset](http://api.jquery.com/offset/) function, distance to viewport. | ||
@@ -127,17 +145,17 @@ | ||
The width of the element. | ||
* `height` | ||
_(Type: `number`)_ - | ||
The height of the element. | ||
* `top` | ||
_(Type: `number`)_ - | ||
Distance to top edge of the viewport. | ||
* `left` | ||
_(Type: `number`)_ - | ||
Distance to left edge of the viewport. | ||
#### viewportOffset(element, useDocument, includePadding) | ||
Gets the elements available space relative to the closest scrollable ancestor. Accounts for padding, border, and scrollbar width. | ||
@@ -152,7 +170,7 @@ Right and bottom dimensions represent the distance to the respective edge of the viewport element, not the top and left edge. | ||
The element to get the viewport offset for. | ||
* `useDocument` | ||
_(Type: `boolean`, Default: `false`, optional)_ - | ||
Should the viewport be the document element instead of the first scrollable element. | ||
* `includePadding` | ||
@@ -169,11 +187,11 @@ _(Type: `boolean`, Default: `true`, optional)_ - | ||
Distance to top content edge of the viewport. | ||
* `bottom` | ||
_(Type: `number`)_ - | ||
Distance to bottom content edge of the viewport. | ||
* `left` | ||
_(Type: `number`)_ - | ||
Distance to left content edge of the viewport. | ||
* `right` | ||
@@ -184,3 +202,3 @@ _(Type: `number`)_ - | ||
#### parsePlacement(placement) | ||
Gets an array of placement values parsed from a placement string. Along with the 'auto' indicator, supported placement strings are: | ||
@@ -218,7 +236,7 @@ | ||
The primary placement. | ||
* `[1]` | ||
_(Type: `string`)_ - | ||
The secondary placement. | ||
* `[2]` | ||
@@ -237,7 +255,7 @@ _(Type: `boolean`)_ - | ||
The element to position against. | ||
* `targetElement` | ||
_(Type: `element`)_ - | ||
The element to position. | ||
* `placement` | ||
@@ -258,7 +276,7 @@ _(Type: `string`, Default: `top`, optional)_ - | ||
The targetElement top value. | ||
* `left` | ||
_(Type: `number`)_ - | ||
The targetElement left value. | ||
* `right` | ||
@@ -274,8 +292,8 @@ _(Type: `number`)_ - | ||
* `element` | ||
_(Type: `element`)_ - | ||
* `element` | ||
_(Type: `element`)_ - | ||
The element to position the arrow element for. | ||
* `placement` | ||
_(Type: `string`)_ - | ||
The placement for the element. |
@@ -36,3 +36,3 @@ angular.module('ui.bootstrap.position', []) | ||
getRawNode: function(elem) { | ||
return elem[0] || elem; | ||
return elem.nodeName ? elem : elem[0] || elem; | ||
}, | ||
@@ -84,3 +84,3 @@ | ||
if (angular.isUndefined(SCROLLBAR_WIDTH)) { | ||
var scrollElem = angular.element('<div style="position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll;"></div>'); | ||
var scrollElem = angular.element('<div class="uib-position-scrollbar-measure"></div>'); | ||
$document.find('body').append(scrollElem); | ||
@@ -96,2 +96,19 @@ SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth; | ||
/** | ||
* Checks to see if the element is scrollable. | ||
* | ||
* @param {element} elem - The element to check. | ||
* @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered, | ||
* default is false. | ||
* | ||
* @returns {boolean} Whether the element is scrollable. | ||
*/ | ||
isScrollable: function(elem, includeHidden) { | ||
elem = this.getRawNode(elem); | ||
var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal; | ||
var elemStyle = $window.getComputedStyle(elem); | ||
return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX); | ||
}, | ||
/** | ||
* Provides the closest scrollable ancestor. | ||
@@ -482,6 +499,13 @@ * A port of the jQuery UI scrollParent method: | ||
var arrowCss = { | ||
top: '', | ||
bottom: '', | ||
left: '', | ||
right: '' | ||
}; | ||
placement = this.parsePlacement(placement); | ||
if (placement[1] === 'center') { | ||
// no adjustment necessary - just reset styles | ||
angular.element(arrowElem).css({top: '', bottom: '', right: '', left: '', margin: ''}); | ||
angular.element(arrowElem).css(arrowCss); | ||
return; | ||
@@ -502,10 +526,2 @@ } | ||
var arrowCss = { | ||
top: 'auto', | ||
bottom: 'auto', | ||
left: 'auto', | ||
right: 'auto', | ||
margin: 0 | ||
}; | ||
switch (placement[0]) { | ||
@@ -512,0 +528,0 @@ case 'top': |
@@ -44,2 +44,16 @@ describe('$uibPosition service', function () { | ||
describe('rawnode', function() { | ||
it('returns the raw DOM element from an angular element', function() { | ||
var angularEl = angular.element('<div></div>'); | ||
var el = $uibPosition.getRawNode(angularEl); | ||
expect(el.nodeName).toBe('DIV'); | ||
}); | ||
it('returns the raw DOM element from a select element', function() { | ||
var angularEl = angular.element('<select><option value="value">value</option></select>'); | ||
var el = $uibPosition.getRawNode(angularEl); | ||
expect(el.nodeName).toBe('SELECT'); | ||
}); | ||
}); | ||
describe('offset', function() { | ||
@@ -221,2 +235,23 @@ it('returns getBoundingClientRect by default', function() { | ||
describe('isScrollable', function() { | ||
var el; | ||
afterEach(function() { | ||
el.remove(); | ||
}); | ||
it('should return true if the element is scrollable', function() { | ||
el = angular.element('<div style="overflow: auto"></div>'); | ||
$document.find('body').append(el); | ||
expect($uibPosition.isScrollable(el)).toBe(true); | ||
}); | ||
it('should return false if the element is scrollable', function() { | ||
el = angular.element('<div></div>'); | ||
$document.find('body').append(el); | ||
expect($uibPosition.isScrollable(el)).toBe(false); | ||
}); | ||
}); | ||
describe('scrollParent', function() { | ||
@@ -223,0 +258,0 @@ var el; |
@@ -14,3 +14,3 @@ A progress bar directive that is focused on providing feedback on the progress of a workflow or action. | ||
_(Default: `null`)_ - | ||
Bootstrap style type. Possible values are 'success', 'info', 'warning', and, 'error' to use Bootstrap's pre-existing styling, or any desired custom suffix. | ||
Bootstrap style type. Possible values are 'success', 'info', 'warning', and, 'danger' to use Bootstrap's pre-existing styling, or any desired custom suffix. | ||
@@ -17,0 +17,0 @@ * `max` |
@@ -13,3 +13,3 @@ angular.module('ui.bootstrap.progressbar', []) | ||
this.bars = []; | ||
$scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max; | ||
$scope.max = getMaxOrDefault(); | ||
@@ -23,3 +23,3 @@ this.addBar = function(bar, element, attrs) { | ||
bar.max = $scope.max; | ||
bar.max = getMaxOrDefault(); | ||
bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar'; | ||
@@ -55,8 +55,13 @@ | ||
$scope.$watch('max', function(max) { | ||
//$attrs.$observe('maxParam', function(maxParam) { | ||
$scope.$watch('maxParam', function(maxParam) { | ||
self.bars.forEach(function(bar) { | ||
bar.max = $scope.max; | ||
bar.max = getMaxOrDefault(); | ||
bar.recalculatePercentage(); | ||
}); | ||
}); | ||
function getMaxOrDefault () { | ||
return angular.isDefined($scope.maxParam) ? $scope.maxParam : progressConfig.max; | ||
} | ||
}]) | ||
@@ -71,3 +76,3 @@ | ||
scope: { | ||
max: '=?' | ||
maxParam: '=?max' | ||
}, | ||
@@ -101,3 +106,3 @@ templateUrl: 'uib/template/progressbar/progress.html' | ||
value: '=', | ||
max: '=?', | ||
maxParam: '=?max', | ||
type: '@' | ||
@@ -104,0 +109,0 @@ }, |
@@ -136,2 +136,26 @@ describe('progressbar directive', function() { | ||
describe('"max" attribute using object', function() { | ||
beforeEach(inject(function() { | ||
element = $compile('<uib-progressbar max="settings.max" animate="false" value="settings.value">{{settings.value}}/{{settings.max}}</uib-progressbar>')($rootScope); | ||
$rootScope.$digest(); | ||
})); | ||
it('should not modify outside object', function() { | ||
if (typeof $rootScope.settings === 'object') { | ||
// angular set's up the nested object therefore we have to check like this to avoid test crash | ||
expect($rootScope.settings.max).toBeUndefined(); | ||
} | ||
expect($rootScope.settings).toBeUndefined(); | ||
expect(getBar(0).attr('aria-valuemax')).toBe('100'); | ||
$rootScope.settings = { | ||
max: 300, | ||
value: 40 | ||
}; | ||
$rootScope.$digest(); | ||
expect($rootScope.settings.max).toBe(300); | ||
expect(getBar(0).attr('aria-valuemax')).toBe('300'); | ||
}); | ||
}); | ||
describe('"type" attribute', function() { | ||
@@ -138,0 +162,0 @@ beforeEach(inject(function() { |
@@ -29,3 +29,3 @@ Rating directive that will take care of visualising a star rating bar. | ||
* `readonly` | ||
* `read-only` | ||
<small class="badge">$</small> | ||
@@ -32,0 +32,0 @@ <i class="icon-eye-open"></i> |
@@ -88,3 +88,3 @@ angular.module('ui.bootstrap.rating', []) | ||
scope: { | ||
readonly: '=?', | ||
readonly: '=?readOnly', | ||
onHover: '&', | ||
@@ -91,0 +91,0 @@ onLeave: '&' |
@@ -128,5 +128,5 @@ describe('rating directive', function() { | ||
it('handles readonly attribute', function() { | ||
it('handles read-only attribute', function() { | ||
$rootScope.isReadonly = true; | ||
element = $compile('<uib-rating ng-model="rate" readonly="isReadonly"></uib-rating>')($rootScope); | ||
element = $compile('<uib-rating ng-model="rate" read-only="isReadonly"></uib-rating>')($rootScope); | ||
$rootScope.$digest(); | ||
@@ -133,0 +133,0 @@ |
@@ -5,2 +5,7 @@ AngularJS version of the tabs directive. | ||
* `active` | ||
<i class="glyphicon glyphicon-eye-open"></i> | ||
_(Default: `Index of first tab`)_ - | ||
Active index of tab. | ||
* `justified` | ||
@@ -11,2 +16,6 @@ <small class="badge">$</small> | ||
* `template-url` | ||
_(Default: `uib/template/tabs/tabset.html`)_ - | ||
A URL representing the location of a template to use for the main component. | ||
* `type` | ||
@@ -29,2 +38,6 @@ _(Defaults: `tabs`)_ - | ||
* `classes` | ||
<small class="badge">$</small> - | ||
An optional string of space-separated CSS classes. | ||
* `deselect()` | ||
@@ -43,2 +56,5 @@ <small class="badge">$</small> - | ||
* `index` - | ||
Tab index. Must be unique. | ||
* `select()` | ||
@@ -48,2 +64,6 @@ <small class="badge">$</small> - | ||
* `template-url` | ||
_(Default: `uib/template/tabs/tab.html`)_ - | ||
A URL representing the location of a template to use for the tab heading. | ||
### Tabset heading | ||
@@ -50,0 +70,0 @@ |
@@ -5,17 +5,24 @@ angular.module('ui.bootstrap.tabs', []) | ||
var ctrl = this, | ||
tabs = ctrl.tabs = $scope.tabs = []; | ||
oldIndex; | ||
ctrl.tabs = []; | ||
ctrl.select = function(selectedTab) { | ||
angular.forEach(tabs, function(tab) { | ||
if (tab.active && tab !== selectedTab) { | ||
tab.active = false; | ||
tab.onDeselect(); | ||
selectedTab.selectCalled = false; | ||
ctrl.select = function(index) { | ||
if (!destroyed) { | ||
var previousIndex = findTabIndex(oldIndex); | ||
var previousSelected = ctrl.tabs[previousIndex]; | ||
if (previousSelected) { | ||
previousSelected.tab.onDeselect(); | ||
previousSelected.tab.active = false; | ||
} | ||
}); | ||
selectedTab.active = true; | ||
// only call select if it has not already been called | ||
if (!selectedTab.selectCalled) { | ||
selectedTab.onSelect(); | ||
selectedTab.selectCalled = true; | ||
var selected = ctrl.tabs[index]; | ||
if (selected) { | ||
selected.tab.onSelect(); | ||
selected.tab.active = true; | ||
ctrl.active = selected.index; | ||
oldIndex = selected.index; | ||
} else if (!selected && angular.isNumber(oldIndex)) { | ||
ctrl.active = null; | ||
oldIndex = null; | ||
} | ||
} | ||
@@ -25,11 +32,21 @@ }; | ||
ctrl.addTab = function addTab(tab) { | ||
tabs.push(tab); | ||
// we can't run the select function on the first tab | ||
// since that would select it twice | ||
if (tabs.length === 1 && tab.active !== false) { | ||
tab.active = true; | ||
} else if (tab.active) { | ||
ctrl.select(tab); | ||
} else { | ||
tab.active = false; | ||
ctrl.tabs.push({ | ||
tab: tab, | ||
index: tab.index | ||
}); | ||
ctrl.tabs.sort(function(t1, t2) { | ||
if (t1.index > t2.index) { | ||
return 1; | ||
} | ||
if (t1.index < t2.index) { | ||
return -1; | ||
} | ||
return 0; | ||
}); | ||
if (tab.index === ctrl.active || !angular.isNumber(ctrl.active) && ctrl.tabs.length === 1) { | ||
var newActiveIndex = findTabIndex(tab.index); | ||
ctrl.select(newActiveIndex); | ||
} | ||
@@ -39,12 +56,19 @@ }; | ||
ctrl.removeTab = function removeTab(tab) { | ||
var index = tabs.indexOf(tab); | ||
//Select a new tab if the tab to be removed is selected and not destroyed | ||
if (tab.active && tabs.length > 1 && !destroyed) { | ||
//If this is the last tab, select the previous tab. else, the next tab. | ||
var newActiveIndex = index === tabs.length - 1 ? index - 1 : index + 1; | ||
ctrl.select(tabs[newActiveIndex]); | ||
var index = findTabIndex(tab.index); | ||
if (tab.index === ctrl.active) { | ||
var newActiveTabIndex = index === ctrl.tabs.length - 1 ? | ||
index - 1 : index + 1 % ctrl.tabs.length; | ||
ctrl.select(newActiveTabIndex); | ||
} | ||
tabs.splice(index, 1); | ||
ctrl.tabs.splice(index, 1); | ||
}; | ||
$scope.$watch('tabset.active', function(val) { | ||
if (angular.isNumber(val) && val !== oldIndex) { | ||
ctrl.select(findTabIndex(val)); | ||
} | ||
}); | ||
var destroyed; | ||
@@ -54,2 +78,10 @@ $scope.$on('$destroy', function() { | ||
}); | ||
function findTabIndex(index) { | ||
for (var i = 0; i < ctrl.tabs.length; i++) { | ||
if (ctrl.tabs[i].index === index) { | ||
return i; | ||
} | ||
} | ||
} | ||
}]) | ||
@@ -61,10 +93,20 @@ | ||
replace: true, | ||
scope: { | ||
scope: {}, | ||
bindToController: { | ||
active: '=?', | ||
type: '@' | ||
}, | ||
controller: 'UibTabsetController', | ||
templateUrl: 'uib/template/tabs/tabset.html', | ||
controllerAs: 'tabset', | ||
templateUrl: function(element, attrs) { | ||
return attrs.templateUrl || 'uib/template/tabs/tabset.html'; | ||
}, | ||
link: function(scope, element, attrs) { | ||
scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; | ||
scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; | ||
scope.vertical = angular.isDefined(attrs.vertical) ? | ||
scope.$parent.$eval(attrs.vertical) : false; | ||
scope.justified = angular.isDefined(attrs.justified) ? | ||
scope.$parent.$eval(attrs.justified) : false; | ||
if (angular.isUndefined(attrs.active)) { | ||
scope.active = 0; | ||
} | ||
} | ||
@@ -78,7 +120,10 @@ }; | ||
replace: true, | ||
templateUrl: 'uib/template/tabs/tab.html', | ||
templateUrl: function(element, attrs) { | ||
return attrs.templateUrl || 'uib/template/tabs/tab.html'; | ||
}, | ||
transclude: true, | ||
scope: { | ||
active: '=?', | ||
heading: '@', | ||
index: '=?', | ||
classes: '@?', | ||
onSelect: '&select', //This callback is called in contentHeadingTransclude | ||
@@ -93,8 +138,2 @@ //once it inserts the tab's content into the dom | ||
link: function(scope, elm, attrs, tabsetCtrl, transclude) { | ||
scope.$watch('active', function(active) { | ||
if (active) { | ||
tabsetCtrl.select(scope); | ||
} | ||
}); | ||
scope.disabled = false; | ||
@@ -107,5 +146,25 @@ if (attrs.disable) { | ||
if (angular.isUndefined(attrs.index)) { | ||
if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) { | ||
scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1; | ||
} else { | ||
scope.index = 0; | ||
} | ||
} | ||
if (angular.isUndefined(attrs.classes)) { | ||
scope.classes = ''; | ||
} | ||
scope.select = function() { | ||
if (!scope.disabled) { | ||
scope.active = true; | ||
var index; | ||
for (var i = 0; i < tabsetCtrl.tabs.length; i++) { | ||
if (tabsetCtrl.tabs[i].tab === scope) { | ||
index = i; | ||
break; | ||
} | ||
} | ||
tabsetCtrl.select(index); | ||
} | ||
@@ -146,3 +205,3 @@ }; | ||
link: function(scope, elm, attrs) { | ||
var tab = scope.$eval(attrs.uibTabContentTransclude); | ||
var tab = scope.$eval(attrs.uibTabContentTransclude).tab; | ||
@@ -149,0 +208,0 @@ //Now our tab is ready to be transcluded: both the tab heading area |
@@ -18,3 +18,3 @@ describe('tabs', function() { | ||
expect(t.length).toEqual(titlesArray.length); | ||
for (var i=0; i<t.length; i++) { | ||
for (var i = 0; i < t.length; i++) { | ||
expect(t.eq(i).text().trim()).toEqual(titlesArray[i]); | ||
@@ -27,3 +27,3 @@ } | ||
expect(c.length).toEqual(contentsArray.length); | ||
for (var i=0; i<c.length; i++) { | ||
for (var i = 0; i < c.length; i++) { | ||
expect(c.eq(i).text().trim()).toEqual(contentsArray[i]); | ||
@@ -38,3 +38,5 @@ } | ||
scope.second = '2'; | ||
scope.actives = {}; | ||
scope.active = 1; | ||
scope.firstClass = 'first-class'; | ||
scope.secondClass = 'second-class-1 second-class-2'; | ||
scope.selectFirst = jasmine.createSpy(); | ||
@@ -45,7 +47,7 @@ scope.selectSecond = jasmine.createSpy(); | ||
elm = $compile([ | ||
'<uib-tabset class="hello" data-pizza="pepperoni">', | ||
' <uib-tab heading="First Tab {{first}}" active="actives.one" select="selectFirst()" deselect="deselectFirst()">', | ||
'<uib-tabset class="hello" data-pizza="pepperoni" active="active">', | ||
' <uib-tab index="1" heading="First Tab {{first}}" classes="{{firstClass}}" select="selectFirst()" deselect="deselectFirst()">', | ||
' first content is {{first}}', | ||
' </uib-tab>', | ||
' <uib-tab active="actives.two" select="selectSecond()" deselect="deselectSecond()">', | ||
' <uib-tab index="2" classes="{{secondClass}}" select="selectSecond()" deselect="deselectSecond()">', | ||
' <uib-tab-heading><b>Second</b> Tab {{second}}</uib-tab-heading>', | ||
@@ -63,2 +65,5 @@ ' second content is {{second}}', | ||
expect(elm.attr('data-pizza')).toBe('pepperoni'); | ||
//Ensure that we have bootstrap 4 link class so things are future proofed. | ||
var link = $(elm.find('a')[0]); | ||
expect(link).toHaveClass('nav-link'); | ||
}); | ||
@@ -79,6 +84,14 @@ | ||
expect(titles().eq(1)).not.toHaveClass('active'); | ||
expect(scope.actives.one).toBe(true); | ||
expect(scope.actives.two).toBeFalsy(); | ||
expect(scope.active).toBe(1); | ||
}); | ||
it('should set optional classes on each tab', function() { | ||
expect(titles().eq(0)).toHaveClass(scope.firstClass); | ||
var secondClassArr = scope.secondClass.split(' '); | ||
secondClassArr.forEach(function(clazz) { | ||
expect(titles().eq(1)).toHaveClass(clazz); | ||
}); | ||
}); | ||
it('should change active on click', function() { | ||
@@ -89,4 +102,3 @@ titles().eq(1).find('> a').click(); | ||
expect(titles().eq(1)).toHaveClass('active'); | ||
expect(scope.actives.one).toBe(false); | ||
expect(scope.actives.two).toBe(true); | ||
expect(scope.active).toBe(2); | ||
}); | ||
@@ -103,6 +115,7 @@ | ||
titles().eq(1).find('> a').click(); | ||
expect(scope.deselectFirst).toHaveBeenCalled(); | ||
titles().eq(0).find('> a').click(); | ||
expect(scope.deselectSecond).toHaveBeenCalled(); | ||
titles().eq(1).find('> a').click(); | ||
expect(scope.deselectFirst).toHaveBeenCalled(); | ||
expect(scope.deselectFirst.calls.count()).toBe(2); | ||
}); | ||
@@ -115,5 +128,5 @@ }); | ||
function makeTab(active) { | ||
function makeTab(index) { | ||
return { | ||
active: !!active, | ||
index: index, | ||
select: jasmine.createSpy() | ||
@@ -123,13 +136,14 @@ }; | ||
scope.tabs = [ | ||
makeTab(), makeTab(), makeTab(true), makeTab() | ||
makeTab(1), makeTab(3), makeTab(5), makeTab(7) | ||
]; | ||
scope.active = 5; | ||
elm = $compile([ | ||
'<uib-tabset>', | ||
' <uib-tab active="tabs[0].active" select="tabs[0].select()">', | ||
'<uib-tabset active="active">', | ||
' <uib-tab index="1" select="tabs[0].select()">', | ||
' </uib-tab>', | ||
' <uib-tab active="tabs[1].active" select="tabs[1].select()">', | ||
' <uib-tab index="3" select="tabs[1].select()">', | ||
' </uib-tab>', | ||
' <uib-tab active="tabs[2].active" select="tabs[2].select()">', | ||
' <uib-tab index="5" select="tabs[2].select()">', | ||
' </uib-tab>', | ||
' <uib-tab active="tabs[3].active" select="tabs[3].select()">', | ||
' <uib-tab index="7" select="tabs[3].select()">', | ||
' </uib-tab>', | ||
@@ -145,3 +159,3 @@ '</uib-tabset>' | ||
if (activeTab === tab) { | ||
expect(tab.active).toBe(true); | ||
expect(scope.active).toBe(tab.index); | ||
//It should only call select ONCE for each select | ||
@@ -152,3 +166,3 @@ expect(tab.select).toHaveBeenCalled(); | ||
} else { | ||
expect(tab.active).toBe(false); | ||
expect(scope.active).not.toBe(tab.index); | ||
expect(_titles.eq(i)).not.toHaveClass('active'); | ||
@@ -165,2 +179,37 @@ } | ||
describe('without active binding and index attributes', function() { | ||
beforeEach(inject(function($compile, $rootScope) { | ||
scope = $rootScope.$new(); | ||
scope.first = '1'; | ||
scope.second = '2'; | ||
elm = $compile([ | ||
'<uib-tabset>', | ||
' <uib-tab heading="First Tab {{first}}">', | ||
' first content is {{first}}', | ||
' </uib-tab>', | ||
' <uib-tab heading="Second Tab {{second}}">', | ||
' second content is {{second}}', | ||
' </uib-tab>', | ||
'</uib-tabset>' | ||
].join('\n'))(scope); | ||
scope.$apply(); | ||
return elm; | ||
})); | ||
it('should bind tabs content and set first tab active', function() { | ||
expectContents(['first content is 1', 'second content is 2']); | ||
expect(titles().eq(0)).toHaveClass('active'); | ||
expect(titles().eq(1)).not.toHaveClass('active'); | ||
expect(elm.controller('uibTabset').active).toBe(0); | ||
}); | ||
it('should change active on click', function() { | ||
titles().eq(1).find('> a').click(); | ||
expect(contents().eq(1)).toHaveClass('active'); | ||
expect(titles().eq(0)).not.toHaveClass('active'); | ||
expect(titles().eq(1)).toHaveClass('active'); | ||
expect(elm.controller('uibTabset').active).toBe(1); | ||
}); | ||
}); | ||
describe('tab callback order', function() { | ||
@@ -171,3 +220,2 @@ var execOrder; | ||
execOrder = []; | ||
scope.actives = {}; | ||
@@ -180,5 +228,5 @@ scope.execute = function(id) { | ||
'<div>', | ||
' <uib-tabset class="hello" data-pizza="pepperoni">', | ||
' <uib-tab heading="First Tab" active="actives.one" select="execute(\'select1\')" deselect="execute(\'deselect1\')"></uib-tab>', | ||
' <uib-tab select="execute(\'select2\')" deselect="execute(\'deselect2\')"></uib-tab>', | ||
' <uib-tabset class="hello" data-pizza="pepperoni" active="active">', | ||
' <uib-tab index="1" heading="First Tab" select="execute(\'select1\')" deselect="execute(\'deselect1\')"></uib-tab>', | ||
' <uib-tab index="2" select="execute(\'select2\')" deselect="execute(\'deselect2\')"></uib-tab>', | ||
' </uib-tabset>', | ||
@@ -210,2 +258,20 @@ '</div>' | ||
describe('custom template', function() { | ||
var $compile, $templateCache; | ||
beforeEach(inject(function($rootScope, _$compile_, _$templateCache_) { | ||
scope = $rootScope; | ||
$compile = _$compile_; | ||
$templateCache = _$templateCache_; | ||
})); | ||
it('should support custom templates', function() { | ||
$templateCache.put('foo/bar.html', '<div>baz</div>'); | ||
elm = $compile('<uib-tabset template-url="foo/bar.html"></uib-tabset>')(scope); | ||
scope.$digest(); | ||
expect(elm.html()).toBe('baz'); | ||
}); | ||
}); | ||
describe('uib-tab', function() { | ||
@@ -236,2 +302,13 @@ var $compile, $templateCache; | ||
}); | ||
it('should support custom templates', function() { | ||
$templateCache.put('foo/bar.html', '<li>baz</li>'); | ||
elm = $compile('<uib-tabset><uib-tab template-url="foo/bar.html"></uib-tab></uib-tabset>')(scope); | ||
scope.$digest(); | ||
var tabTitle = titles().eq(0); | ||
expect(tabTitle.html()).toBe('baz'); | ||
}); | ||
}); | ||
@@ -247,7 +324,8 @@ | ||
scope.tabs = [ | ||
makeTab(), makeTab(), makeTab(true), makeTab() | ||
makeTab(1), makeTab(3), makeTab(5), makeTab(7) | ||
]; | ||
scope.active = 5; | ||
elm = $compile([ | ||
'<uib-tabset>', | ||
' <uib-tab ng-repeat="t in tabs" active="t.active" select="t.select()">', | ||
'<uib-tabset active="active">', | ||
' <uib-tab index="t.index" ng-repeat="t in tabs" active="t.active" select="t.select()">', | ||
' <uib-tab-heading><b>heading</b> {{index}}</uib-tab-heading>', | ||
@@ -261,5 +339,5 @@ ' content {{$index}}', | ||
function makeTab(active) { | ||
function makeTab(index) { | ||
return { | ||
active: !!active, | ||
index: index, | ||
select: jasmine.createSpy() | ||
@@ -280,3 +358,3 @@ }; | ||
if (activeTab === tab) { | ||
expect(tab.active).toBe(true); | ||
expect(scope.active).toBe(tab.index); | ||
//It should only call select ONCE for each select | ||
@@ -288,3 +366,3 @@ expect(tab.select).toHaveBeenCalled(); | ||
} else { | ||
expect(tab.active).toBe(false); | ||
expect(scope.active).not.toBe(tab.index); | ||
expect(_titles.eq(i)).not.toHaveClass('active'); | ||
@@ -305,4 +383,4 @@ } | ||
it('should switch active when setting active=true', function() { | ||
scope.$apply('tabs[2].active = true'); | ||
it('should switch active when changing active index', function() { | ||
scope.$apply('active = 5'); | ||
expectTabActive(scope.tabs[2]); | ||
@@ -312,3 +390,3 @@ }); | ||
it('should deselect all when no tabs are active', function() { | ||
angular.forEach(scope.tabs, function(t) { t.active = false; }); | ||
scope.active = 101; | ||
scope.$apply(); | ||
@@ -318,3 +396,3 @@ expectTabActive(null); | ||
scope.tabs[2].active = true; | ||
scope.active = 5; | ||
scope.$apply(); | ||
@@ -330,13 +408,13 @@ expectTabActive(scope.tabs[2]); | ||
scope.tabs = [ | ||
makeTab(), makeTab(), makeTab(true), makeTab() | ||
makeTab(2), makeTab(3), makeTab(5), makeTab(8) | ||
]; | ||
scope.foo = {active: true}; | ||
scope.active = 13; | ||
scope.select = jasmine.createSpy(); | ||
elm = $compile([ | ||
'<uib-tabset>', | ||
' <uib-tab ng-repeat="t in tabs" active="t.active" select="select()">', | ||
'<uib-tabset active="active">', | ||
' <uib-tab index="t.index" ng-repeat="t in tabs" select="t.select()">', | ||
' <uib-tab-heading><b>heading</b> {{index}}</uib-tab-heading>', | ||
' content {{$index}}', | ||
' </uib-tab>', | ||
' <uib-tab active="foo.active" select="select()">', | ||
' <uib-tab index="13" select="select()">', | ||
' <uib-tab-heading><b>heading</b> foo</uib-tab-heading>', | ||
@@ -359,9 +437,9 @@ ' content foo', | ||
elm = $compile([ | ||
'<uib-tabset>', | ||
' <uib-tab>', | ||
'<uib-tabset active="active">', | ||
' <uib-tab index="0">', | ||
' <uib-tab-heading ng-bind-html="myHtml" ng-show="value"></uib-tab-heading>', | ||
' </uib-tab>', | ||
' <uib-tab><data-uib-tab-heading>1</data-uib-tab-heading></uib-tab>', | ||
' <uib-tab><div data-uib-tab-heading>2</div></uib-tab>', | ||
' <uib-tab><div uib-tab-heading>3</div></uib-tab>', | ||
' <uib-tab index="1"><data-uib-tab-heading>1</data-uib-tab-heading></uib-tab>', | ||
' <uib-tab index="2"><div data-uib-tab-heading>2</div></uib-tab>', | ||
' <uib-tab index="3"><div uib-tab-heading>3</div></uib-tab>', | ||
'</uib-tabset>' | ||
@@ -406,8 +484,8 @@ ].join('\n'))(scope); | ||
elm = $compile([ | ||
'<uib-tabset>', | ||
'<uib-tabset active="active">', | ||
' <!-- a comment -->', | ||
' <div>div that makes troubles</div>', | ||
' <uib-tab heading="first">First Static</uib-tab>', | ||
' <uib-tab index="0" heading="first">First Static</uib-tab>', | ||
' <div>another div that may do evil</div>', | ||
' <uib-tab ng-repeat="tab in tabs | filter:tabIsAvailable" active="tab.active" heading="{{tab.title}}">some content</uib-tab>', | ||
' <uib-tab index="$index + 1" ng-repeat="tab in tabs | filter:tabIsAvailable" active="tab.active" heading="{{tab.title}}">some content</uib-tab>', | ||
' <!-- another comment -->', | ||
@@ -418,7 +496,7 @@ ' <uib-tab heading="mid">Mid Static</uib-tab>', | ||
' <span>yet another span that may do evil</span>', | ||
' <uib-tab ng-repeat="tab in tabs | filter:tabIsAvailable" active="tab.active" heading="Second {{tab.title}}">some content</uib-tab>', | ||
' <uib-tab index="$index + 4" ng-repeat="tab in tabs | filter:tabIsAvailable" heading="Second {{tab.title}}">some content</uib-tab>', | ||
' a text node', | ||
' <span>yet another span that may do evil</span>', | ||
' <!-- another comment -->', | ||
' <uib-tab heading="last">Last Static</uib-tab>', | ||
' <uib-tab index="7" heading="last">Last Static</uib-tab>', | ||
' a text node', | ||
@@ -470,10 +548,5 @@ ' <span>yet another span that may do evil</span>', | ||
describe('uib-tabset controller', function() { | ||
function mockTab(isActive) { | ||
var _isActive; | ||
if (isActive || isActive === false) { | ||
_isActive = isActive; | ||
} | ||
function mockTab(index) { | ||
return { | ||
active: _isActive, | ||
index: index, | ||
onSelect : angular.noop, | ||
@@ -493,6 +566,11 @@ onDeselect : angular.noop | ||
it('should mark given tab selected', function() { | ||
var tab = mockTab(); | ||
ctrl.tabs = [ | ||
{ | ||
tab: mockTab(0), | ||
index: 0 | ||
} | ||
]; | ||
ctrl.select(tab); | ||
expect(tab.active).toBe(true); | ||
ctrl.select(0); | ||
expect(ctrl.active).toBe(0); | ||
}); | ||
@@ -502,3 +580,3 @@ | ||
it('should deselect other tabs', function() { | ||
var tab1 = mockTab(), tab2 = mockTab(), tab3 = mockTab(); | ||
var tab1 = mockTab(1), tab2 = mockTab(2), tab3 = mockTab(3); | ||
@@ -509,16 +587,10 @@ ctrl.addTab(tab1); | ||
ctrl.select(tab1); | ||
expect(tab1.active).toBe(true); | ||
expect(tab2.active).toBe(false); | ||
expect(tab3.active).toBe(false); | ||
ctrl.select(0); | ||
expect(ctrl.active).toBe(1); | ||
ctrl.select(tab2); | ||
expect(tab1.active).toBe(false); | ||
expect(tab2.active).toBe(true); | ||
expect(tab3.active).toBe(false); | ||
ctrl.select(1); | ||
expect(ctrl.active).toBe(2); | ||
ctrl.select(tab3); | ||
expect(tab1.active).toBe(false); | ||
expect(tab2.active).toBe(false); | ||
expect(tab3.active).toBe(true); | ||
ctrl.select(2); | ||
expect(ctrl.active).toBe(3); | ||
}); | ||
@@ -530,3 +602,3 @@ }); | ||
it('should append tab', function() { | ||
var tab1 = mockTab(), tab2 = mockTab(); | ||
var tab1 = mockTab(1), tab2 = mockTab(2); | ||
@@ -536,33 +608,48 @@ expect(ctrl.tabs).toEqual([]); | ||
ctrl.addTab(tab1); | ||
expect(ctrl.tabs).toEqual([tab1]); | ||
expect(ctrl.tabs).toEqual([ | ||
{ | ||
tab: tab1, | ||
index: 1 | ||
} | ||
]); | ||
ctrl.addTab(tab2); | ||
expect(ctrl.tabs).toEqual([tab1, tab2]); | ||
expect(ctrl.tabs).toEqual([ | ||
{ | ||
tab: tab1, | ||
index: 1 | ||
}, | ||
{ | ||
tab: tab2, | ||
index: 2 | ||
} | ||
]); | ||
}); | ||
it('should select the first one', function() { | ||
var tab1 = mockTab(), tab2 = mockTab(); | ||
var tab1 = mockTab(1), tab2 = mockTab(2); | ||
ctrl.addTab(tab1); | ||
expect(tab1.active).toBe(true); | ||
expect(ctrl.active).toBe(1); | ||
ctrl.addTab(tab2); | ||
expect(tab1.active).toBe(true); | ||
expect(ctrl.active).toBe(1); | ||
}); | ||
it('should not select first active === false tab as selected', function() { | ||
var tab = mockTab(false); | ||
var tab = mockTab(0); | ||
ctrl.active = 1; | ||
ctrl.addTab(tab); | ||
expect(tab.active).toBe(false); | ||
expect(ctrl.active).toBe(1); | ||
}); | ||
it('should select a tab added that\'s already active', function() { | ||
var tab1 = mockTab(), tab2 = mockTab(true); | ||
it('should retain active state when adding tab of different index', function() { | ||
var tab1 = mockTab(1), tab2 = mockTab(2); | ||
ctrl.active = 2; | ||
ctrl.addTab(tab1); | ||
expect(tab1.active).toBe(true); | ||
expect(ctrl.active).toBe(2); | ||
ctrl.addTab(tab2); | ||
expect(tab1.active).toBe(false); | ||
expect(tab2.active).toBe(true); | ||
expect(ctrl.active).toBe(2); | ||
}); | ||
@@ -575,3 +662,3 @@ }); | ||
scope = $rootScope.$new(); | ||
elm = $compile('<uib-tabset><uib-tab heading="1">Hello</uib-tab><uib-tab ng-repeat="i in list" heading="tab {{i}}">content {{i}}</uib-tab></uib-tabset>')(scope); | ||
elm = $compile('<uib-tabset active="active"><uib-tab index="0" heading="1">Hello</uib-tab><uib-tab index="$index + 1" ng-repeat="i in list" heading="tab {{i}}">content {{i}}</uib-tab></uib-tabset>')(scope); | ||
scope.$apply(); | ||
@@ -617,23 +704,24 @@ | ||
var selectList = [], | ||
deselectList = [], | ||
getTab = function(active) { | ||
return { | ||
active: active, | ||
select : function() { | ||
selectList.push('select'); | ||
}, | ||
deselect : function() { | ||
deselectList.push('deselect'); | ||
} | ||
}; | ||
deselectList = [], | ||
getTab = function(index) { | ||
return { | ||
index: index, | ||
select: function() { | ||
selectList.push('select'); | ||
}, | ||
deselect: function() { | ||
deselectList.push('deselect'); | ||
} | ||
}; | ||
}; | ||
scope = $rootScope.$new(); | ||
scope.tabs = [ | ||
getTab(true), | ||
getTab(false) | ||
getTab(0), | ||
getTab(1) | ||
]; | ||
scope.active = 1; | ||
elm = $compile([ | ||
'<uib-tabset>', | ||
' <uib-tab ng-repeat="t in tabs" active="t.active" select="t.select()" deselect="t.deselect()">', | ||
'<uib-tabset active="active">', | ||
' <uib-tab index="$index" ng-repeat="t in tabs" active="t.active" select="t.select()" deselect="t.deselect()">', | ||
' <uib-tab-heading><b>heading</b> {{index}}</uib-tab-heading>', | ||
@@ -660,5 +748,5 @@ ' content {{$index}}', | ||
function makeTab(disable) { | ||
function makeTab(disable, index) { | ||
return { | ||
active: false, | ||
index: index, | ||
select: jasmine.createSpy(), | ||
@@ -669,7 +757,8 @@ disable: disable | ||
scope.tabs = [ | ||
makeTab(false), makeTab(true), makeTab(false), makeTab(true) | ||
makeTab(false, 0), makeTab(true, 1), makeTab(false, 2), makeTab(true, 3) | ||
]; | ||
scope.active = 1; | ||
elm = $compile([ | ||
'<uib-tabset>', | ||
' <uib-tab ng-repeat="t in tabs" active="t.active" select="t.select()" disable="t.disable">', | ||
'<uib-tabset active="active">', | ||
' <uib-tab index="$index" ng-repeat="t in tabs" select="t.select()" disable="t.disable">', | ||
' <uib-tab-heading><b>heading</b> {{index}}</uib-tab-heading>', | ||
@@ -687,3 +776,3 @@ ' content {{$index}}', | ||
if (activeTab === tab) { | ||
expect(tab.active).toBe(true); | ||
expect(scope.active).toBe(tab.index); | ||
expect(tab.select.calls.count()).toBe(tab.disable ? 0 : 1); | ||
@@ -694,3 +783,3 @@ expect(_titles.eq(i)).toHaveClass('active'); | ||
} else { | ||
expect(tab.active).toBe(false); | ||
expect(scope.active).not.toBe(tab.index); | ||
expect(_titles.eq(i)).not.toHaveClass('active'); | ||
@@ -697,0 +786,0 @@ } |
@@ -31,3 +31,4 @@ A lightweight, extensible directive for fancy tooltip creation. The tooltip | ||
<small class="badge">$</small> | ||
_(Default: `false`)_ - | ||
<small class="badge">C</small> | ||
_(Default: `false`, Config: `appendToBody`)_ - | ||
Should the tooltip be appended to '$body' instead of the parent element? | ||
@@ -34,0 +35,0 @@ |
@@ -134,3 +134,3 @@ /** | ||
'origin-scope="origScope" ' + | ||
'style="visibility: hidden; display: block; top: -9999px; left: -9999px;"' + | ||
'class="uib-position-measure"' + | ||
'>' + | ||
@@ -158,2 +158,3 @@ '</div>'; | ||
var observers = []; | ||
var lastPlacement; | ||
@@ -166,32 +167,25 @@ var positionTooltip = function() { | ||
positionTimeout = $timeout(function() { | ||
// Reset the positioning. | ||
tooltip.css({ top: 0, left: 0 }); | ||
// Now set the calculated positioning. | ||
var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody); | ||
tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px', visibility: 'visible' }); | ||
tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' }); | ||
// If the placement class is prefixed, still need | ||
// to remove the TWBS standard class. | ||
if (options.placementClassPrefix) { | ||
tooltip.removeClass('top bottom left right'); | ||
if (!tooltip.hasClass(ttPosition.placement.split('-')[0])) { | ||
tooltip.removeClass(lastPlacement.split('-')[0]); | ||
tooltip.addClass(ttPosition.placement.split('-')[0]); | ||
} | ||
tooltip.removeClass( | ||
options.placementClassPrefix + 'top ' + | ||
options.placementClassPrefix + 'top-left ' + | ||
options.placementClassPrefix + 'top-right ' + | ||
options.placementClassPrefix + 'bottom ' + | ||
options.placementClassPrefix + 'bottom-left ' + | ||
options.placementClassPrefix + 'bottom-right ' + | ||
options.placementClassPrefix + 'left ' + | ||
options.placementClassPrefix + 'left-top ' + | ||
options.placementClassPrefix + 'left-bottom ' + | ||
options.placementClassPrefix + 'right ' + | ||
options.placementClassPrefix + 'right-top ' + | ||
options.placementClassPrefix + 'right-bottom'); | ||
if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) { | ||
tooltip.removeClass(options.placementClassPrefix + lastPlacement); | ||
tooltip.addClass(options.placementClassPrefix + ttPosition.placement); | ||
} | ||
var placement = ttPosition.placement.split('-'); | ||
tooltip.addClass(placement[0] + ' ' + options.placementClassPrefix + ttPosition.placement); | ||
$position.positionArrow(tooltip, ttPosition.placement); | ||
// first time through tt element will have the | ||
// uib-position-measure class or if the placement | ||
// has changed we need to position the arrow. | ||
if (tooltip.hasClass('uib-position-measure')) { | ||
$position.positionArrow(tooltip, ttPosition.placement); | ||
tooltip.removeClass('uib-position-measure'); | ||
} else if (lastPlacement !== ttPosition.placement) { | ||
$position.positionArrow(tooltip, ttPosition.placement); | ||
} | ||
lastPlacement = ttPosition.placement; | ||
@@ -371,2 +365,4 @@ positionTimeout = null; | ||
ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement; | ||
var placement = $position.parsePlacement(ttScope.placement); | ||
lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0]; | ||
@@ -461,2 +457,4 @@ var delay = parseInt(attrs[prefix + 'PopupDelay'], 10); | ||
ttScope.placement = val ? val : options.placement; | ||
var placement = $position.parsePlacement(ttScope.placement); | ||
lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0]; | ||
if (ttScope.isOpen) { | ||
@@ -548,3 +546,3 @@ positionTooltip(); | ||
appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody; | ||
// Make sure tooltip is destroyed and removed. | ||
@@ -651,4 +649,2 @@ scope.$on('$destroy', function onDestroyTooltip() { | ||
element.addClass(position[0]); | ||
} else { | ||
element.addClass('top'); | ||
} | ||
@@ -655,0 +651,0 @@ |
@@ -97,3 +97,3 @@ Typeahead is a AngularJS version of [Bootstrap v2's typeahead plugin](http://getbootstrap.com/2.3.2/javascript.html#typeahead). | ||
_(Default: `false`)_ - | ||
Should it automatically select an item when there is one option that exactly matches the user input? | ||
Automatically select the item when it is the only one that exactly matches the user input. | ||
@@ -103,3 +103,3 @@ * `typeahead-show-hint` | ||
_(Default: `false`)_ - | ||
Should input show hint that matches the first option? | ||
Show hint when the first option matches. | ||
@@ -106,0 +106,0 @@ * `typeahead-template-url` |
@@ -67,6 +67,10 @@ describe('typeahead tests', function() { | ||
var triggerKeyDown = function(element, keyCode) { | ||
var triggerKeyDown = function(element, keyCode, options) { | ||
options = options || {}; | ||
var inputEl = findInput(element); | ||
var e = $.Event('keydown'); | ||
e.which = keyCode; | ||
if (options.shiftKey) { | ||
e.shiftKey = true; | ||
} | ||
inputEl.trigger(e); | ||
@@ -200,2 +204,26 @@ }; | ||
it('should support changing min-length', function() { | ||
$scope.typeAheadMinLength = 2; | ||
var element = prepareInputEl('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue" typeahead-min-length="typeAheadMinLength"></div>'); | ||
changeInputValueTo(element, 'b'); | ||
expect(element).toBeClosed(); | ||
$scope.typeAheadMinLength = 0; | ||
$scope.$digest(); | ||
changeInputValueTo(element, ''); | ||
expect(element).toBeOpenWithActive(3, 0); | ||
$scope.typeAheadMinLength = 2; | ||
$scope.$digest(); | ||
changeInputValueTo(element, 'b'); | ||
expect(element).toBeClosed(); | ||
}); | ||
it('should support custom model selecting function', function() { | ||
@@ -1192,2 +1220,7 @@ $scope.updaterFn = function(selectedItem) { | ||
}); | ||
it("should set tab index on hint input element", function(){ | ||
var hintEl = findInput(element); | ||
expect(hintEl.attr('tabindex')).toEqual('-1'); | ||
}); | ||
}); | ||
@@ -1328,2 +1361,19 @@ | ||
it("should not capture tab when shift key is pressed", function(){ | ||
$scope.select_count = 0; | ||
$scope.onSelect = function($item, $model, $label) { | ||
$scope.select_count = $scope.select_count + 1; | ||
}; | ||
var element = prepareInputEl('<div><input ng-model="result" ng-keydown="keyDownEvent = $event" uib-typeahead="item for item in source | filter:$viewValue" typeahead-on-select="onSelect($item, $model, $label)" typeahead-focus-first="false"></div>'); | ||
changeInputValueTo(element, 'b'); | ||
// down key should be captured and focus first element | ||
triggerKeyDown(element, 40); | ||
triggerKeyDown(element, 9, {shiftKey: true}); | ||
expect($scope.keyDownEvent.isDefaultPrevented()).toBeFalsy(); | ||
expect($scope.select_count).toEqual(0); | ||
expect(element).toBeClosed(); | ||
}); | ||
it('should capture enter or tab when an item is focused', function() { | ||
@@ -1330,0 +1380,0 @@ $scope.select_count = 0; |
@@ -42,2 +42,6 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position']) | ||
originalScope.$watch(attrs.typeaheadMinLength, function (newVal) { | ||
minLength = !newVal && newVal !== 0 ? 1 : newVal; | ||
}); | ||
//minimal wait time after last character typed before typeahead kicks-in | ||
@@ -129,2 +133,3 @@ var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; | ||
hintInputElem.attr('placeholder', ''); | ||
hintInputElem.attr('tabindex', '-1'); | ||
hintInputElem.val(''); | ||
@@ -370,4 +375,9 @@ hintInputElem.css({ | ||
// if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results | ||
if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) { | ||
/** | ||
* if there's nothing selected (i.e. focusFirst) and enter or tab is hit | ||
* or | ||
* shift + tab is pressed to bring focus to the previous element | ||
* then clear the results | ||
*/ | ||
if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13) || evt.which === 9 && !!evt.shiftKey) { | ||
resetMatches(); | ||
@@ -374,0 +384,0 @@ scope.$digest(); |
@@ -6,3 +6,3 @@ angular.module("uib/template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { | ||
" <h4 class=\"panel-title\">\n" + | ||
" <a role=\"button\" data-toggle=\"collapse\" href aria-expanded=\"{{isOpen}}\" aria-controls=\"{{::panelId}}\" tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\"><span ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n" + | ||
" <a role=\"button\" data-toggle=\"collapse\" href aria-expanded=\"{{isOpen}}\" aria-controls=\"{{::panelId}}\" tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\"><span uib-accordion-header ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n" + | ||
" </h4>\n" + | ||
@@ -9,0 +9,0 @@ " </div>\n" + |
@@ -18,3 +18,4 @@ angular.module("uib/template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) { | ||
" </ol>\n" + | ||
"</div>"); | ||
"</div>\n" + | ||
""); | ||
}]); |
@@ -7,3 +7,4 @@ angular.module("uib/template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { | ||
" <uib-yearpicker ng-switch-when=\"year\" tabindex=\"0\"></uib-yearpicker>\n" + | ||
"</div>"); | ||
"</div>\n" + | ||
""); | ||
}]); |
angular.module("uib/template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) { | ||
$templateCache.put("uib/template/datepicker/popup.html", | ||
"<div>\n" + | ||
" <ul class=\"uib-datepicker-popup dropdown-menu\" dropdown-nested ng-if=\"isOpen\" ng-style=\"{top: position.top+'px', left: position.left+'px'}\" ng-keydown=\"keydown($event)\" ng-click=\"$event.stopPropagation()\">\n" + | ||
" <ul class=\"uib-datepicker-popup dropdown-menu uib-position-measure\" dropdown-nested ng-if=\"isOpen\" ng-keydown=\"keydown($event)\" ng-click=\"$event.stopPropagation()\">\n" + | ||
" <li ng-transclude></li>\n" + | ||
" <li ng-if=\"showButtonBar\" class=\"uib-button-bar\">\n" + | ||
" <span class=\"btn-group pull-left\">\n" + | ||
" <button type=\"button\" class=\"btn btn-sm btn-info uib-datepicker-current\" ng-click=\"select('today')\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" + | ||
" <button type=\"button\" class=\"btn btn-sm btn-danger uib-clear\" ng-click=\"select(null)\">{{ getText('clear') }}</button>\n" + | ||
" </span>\n" + | ||
" <button type=\"button\" class=\"btn btn-sm btn-success pull-right uib-close\" ng-click=\"close()\">{{ getText('close') }}</button>\n" + | ||
" <span class=\"btn-group pull-left\">\n" + | ||
" <button type=\"button\" class=\"btn btn-sm btn-info uib-datepicker-current\" ng-click=\"select('today', $event)\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" + | ||
" <button type=\"button\" class=\"btn btn-sm btn-danger uib-clear\" ng-click=\"select(null, $event)\">{{ getText('clear') }}</button>\n" + | ||
" </span>\n" + | ||
" <button type=\"button\" class=\"btn btn-sm btn-success pull-right uib-close\" ng-click=\"close($event)\">{{ getText('close') }}</button>\n" + | ||
" </li>\n" + | ||
@@ -13,0 +13,0 @@ " </ul>\n" + |
angular.module("uib/template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { | ||
$templateCache.put("uib/template/tabs/tab.html", | ||
"<li ng-class=\"{active: active, disabled: disabled}\" class=\"uib-tab\">\n" + | ||
" <a href ng-click=\"select()\" uib-tab-heading-transclude>{{heading}}</a>\n" + | ||
"<li ng-class=\"[{active: active, disabled: disabled}, classes]\" class=\"uib-tab nav-item\">\n" + | ||
" <a href ng-click=\"select()\" class=\"nav-link\" uib-tab-heading-transclude>{{heading}}</a>\n" + | ||
"</li>\n" + | ||
""); | ||
}]); |
angular.module("uib/template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { | ||
$templateCache.put("uib/template/tabs/tabset.html", | ||
"<div>\n" + | ||
" <ul class=\"nav nav-{{type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" + | ||
" <ul class=\"nav nav-{{tabset.type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" + | ||
" <div class=\"tab-content\">\n" + | ||
" <div class=\"tab-pane\" \n" + | ||
" ng-repeat=\"tab in tabs\" \n" + | ||
" ng-class=\"{active: tab.active}\"\n" + | ||
" <div class=\"tab-pane\"\n" + | ||
" ng-repeat=\"tab in tabset.tabs\"\n" + | ||
" ng-class=\"{active: tabset.active === tab.index}\"\n" + | ||
" uib-tab-content-transclude=\"tab\">\n" + | ||
@@ -10,0 +10,0 @@ " </div>\n" + |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1754132
208
35683
162
24