Compare commits
246 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2dee1bdeb | |||
| 409eec4019 | |||
| b90485b2a4 | |||
| 0d79005f8d | |||
| e538d2f71f | |||
| f5b357fd11 | |||
| 761db7b4b7 | |||
| 86ee770834 | |||
| 47c4544e23 | |||
| e8479514cd | |||
| cf3f870735 | |||
| 7d3a7502fa | |||
| 71dc691323 | |||
| 42f3cc847a | |||
| b755559ec9 | |||
| 9e6d2a0a14 | |||
| 2596b9805f | |||
| 5a3e44a146 | |||
| 4872c05a32 | |||
| fc686bb7a3 | |||
| 7f664f9f45 | |||
| 955848c3b1 | |||
| 7a1d54c8c3 | |||
| 0ed1a59aef | |||
| f2722b59a5 | |||
| 57ed7e4f7f | |||
| 90848144e8 | |||
| a4d7076c8e | |||
| c824731ae8 | |||
| 1653afa210 | |||
| 7e2f2c1bad | |||
| 61f365abfd | |||
| ec2d9ad605 | |||
| 08b50ccb1c | |||
| 44ab0a8106 | |||
| 1962485504 | |||
| 8a4f625ef6 | |||
| 7be665399f | |||
| 2edb5d38cb | |||
| 4e06553f7c | |||
| 25ff206767 | |||
| 997813f0eb | |||
| 25db68e903 | |||
| 78559761dc | |||
| 14384fc40f | |||
| 58f1813aca | |||
| 9666c64f4a | |||
| f9f7e02d15 | |||
| a4bea6f229 | |||
| c00851a18b | |||
| c687acd489 | |||
| 3f70d76327 | |||
| 08ee30a91e | |||
| 7671bd6ed4 | |||
| 1a132dd757 | |||
| 9881a27397 | |||
| 294b151342 | |||
| 969eb9c74d | |||
| 048d85a2ad | |||
| 876e72a0df | |||
| 6734908108 | |||
| 515bcf2933 | |||
| 148371fb6d | |||
| 8047c06258 | |||
| 3e8ecfffe0 | |||
| ddcacb7a83 | |||
| 915eda0540 | |||
| f9eede7555 | |||
| fb5fabf580 | |||
| aef24cde4b | |||
| b893a93f82 | |||
| 6352f13e85 | |||
| 76cd65ef1a | |||
| 84cc2cf5cc | |||
| 2ade0545a9 | |||
| f5ff12c0a4 | |||
| f147d22f5c | |||
| f798a47d48 | |||
| 43a1f75e7c | |||
| 4a5e6a7418 | |||
| af6c2aaca0 | |||
| 61415e1968 | |||
| 5123e38a49 | |||
| ca20be4667 | |||
| d9dd5803ae | |||
| 86f528730b | |||
| fbd0845c95 | |||
| 5597e2fa99 | |||
| 6bad7592b9 | |||
| 0023d1bd97 | |||
| be0d0564c2 | |||
| 9f3e387473 | |||
| 58e0433b96 | |||
| 87a373936d | |||
| e8e4ef1070 | |||
| c61d16a933 | |||
| 6bfb4cdbdf | |||
| cac6a45345 | |||
| 8a53327bd6 | |||
| 9f06a8bfdb | |||
| 5173779f84 | |||
| 1616e9720d | |||
| b77f02cdce | |||
| 39d9b9886d | |||
| 2d831bcc2a | |||
| d846e2d788 | |||
| 409b7aa3b3 | |||
| ed3400bb68 | |||
| 1cbd73d227 | |||
| 433e536e6b | |||
| 1ec099767f | |||
| 4e0e34f47a | |||
| 7457fb0e63 | |||
| 23ef372a17 | |||
| 164811ab35 | |||
| 1de58a3e5d | |||
| 7e320e0e92 | |||
| d6fe9c7e7a | |||
| 75188211aa | |||
| 4e68778561 | |||
| a68cc20dd4 | |||
| ba113a050e | |||
| 0d8cec6e4f | |||
| a47bcedad4 | |||
| 387c6e7bbc | |||
| 9b24e1dfed | |||
| 5f4eedd0e7 | |||
| 96d52ce150 | |||
| 55cf26bf28 | |||
| 5fdcbd6b90 | |||
| e92fb7fa7c | |||
| 1a870a3bb1 | |||
| 6038403179 | |||
| fb38d0db2e | |||
| 5046bd4e56 | |||
| 2458c2863d | |||
| 3819bbe8fa | |||
| e2016fd2eb | |||
| 34a1443a85 | |||
| 35ced04445 | |||
| 90fa2f682a | |||
| 4fef0377bb | |||
| b0466d9e6e | |||
| 4b912228be | |||
| 9a606dca7c | |||
| 6d5b84a0ba | |||
| cce00970f1 | |||
| 23b91d951e | |||
| 053035234c | |||
| a075824087 | |||
| 13c14af9a2 | |||
| 280ddaf47e | |||
| dc85f67b2d | |||
| 2ffb86f201 | |||
| aec5304ef5 | |||
| eb02778297 | |||
| 26d399557e | |||
| 9436b9e7bd | |||
| b2984b0416 | |||
| 5188463f69 | |||
| a417ec2ab9 | |||
| d923df9100 | |||
| dd09148893 | |||
| 5741be9d5b | |||
| 1611248daa | |||
| e51373d84b | |||
| 4c40d9db7f | |||
| f88067a04f | |||
| 0137179ee6 | |||
| 2b48259a35 | |||
| 241fea8355 | |||
| f0e661c7c0 | |||
| 78db2cefe7 | |||
| 5eab8ffc49 | |||
| 68200bbccb | |||
| 68ed7ab477 | |||
| d96d53ec32 | |||
| aef08d5555 | |||
| a27a4e2204 | |||
| a887d2b4c9 | |||
| bba3f27e3d | |||
| 3d7191cc06 | |||
| 32f78b8718 | |||
| 4044f08980 | |||
| 33cbd0f4a2 | |||
| 3b8f18e373 | |||
| 0f3a5214f4 | |||
| 37f9cd26e5 | |||
| 169c50403c | |||
| 38a334c7d8 | |||
| c05aefe96d | |||
| 98eb8f1a77 | |||
| 1c6c7b913c | |||
| 1519c4b46d | |||
| 86ee9c3af1 | |||
| a08ad700f6 | |||
| 4ec35e4dcb | |||
| daf5c820e3 | |||
| d767afbd2a | |||
| 701e0cbded | |||
| fc02fd1e30 | |||
| 4fe5d9936d | |||
| e0fcc004c0 | |||
| cca1460eb0 | |||
| c83d0a8daf | |||
| a3964d4d81 | |||
| 434c1c595b | |||
| 48f62a6dad | |||
| 1fa358fa6b | |||
| 393d00e415 | |||
| 80748994a3 | |||
| e50f602488 | |||
| f57302707c | |||
| de504fb512 | |||
| daf3b2e7f7 | |||
| 41e670c34a | |||
| d1c49fe342 | |||
| f6735dc2bb | |||
| 03e60479d4 | |||
| 449c0d11d6 | |||
| c534cb4a94 | |||
| 45165ba58b | |||
| 8590ea7240 | |||
| 44fb19bc83 | |||
| fbc873cfb2 | |||
| ad91c82164 | |||
| 46ff62dd75 | |||
| 34adfbce01 | |||
| 307d8992a8 | |||
| fa46e16eab | |||
| 6955cf0079 | |||
| c7be087ca0 | |||
| 1b888d4b0e | |||
| 5ef56e2de8 | |||
| bc7c55aec8 | |||
| 316e96c9b0 | |||
| 8747b585f4 | |||
| e15a22a8d4 | |||
| bb36e40582 | |||
| 7f93bfc16f | |||
| 1714a388b4 | |||
| 66c416cf77 | |||
| f2004a9186 | |||
| 54ac06af80 | |||
| ffb5529771 | |||
| d6b9ee17e5 |
@@ -6,6 +6,8 @@ existing issues (both open and closed) prior to opening any new issue and ensure
|
||||
|
||||
### Link to minimally-working plunker that reproduces the issue:
|
||||
|
||||
### Steps to reproduce the issue:
|
||||
|
||||
### Version of Angular, UIBS, and Bootstrap
|
||||
|
||||
Angular:
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "5.7"
|
||||
- "5.9"
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
addons:
|
||||
@@ -13,7 +13,7 @@ addons:
|
||||
before_install:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- npm install --quiet -g grunt-cli karma
|
||||
- npm install --quiet -g karma
|
||||
|
||||
script: grunt
|
||||
sudo: false
|
||||
|
||||
+416
-33
@@ -1,11 +1,381 @@
|
||||
<a name="2.5.0"></a>
|
||||
# [2.5.0](https://github.com/angular-ui/bootstrap/compare/2.4.0...v2.5.0) (2017-01-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **angular:** add compatibility with Angular 1.6([0d79005](https://github.com/angular-ui/bootstrap/commit/0d79005)), closes [#6427](https://github.com/angular-ui/bootstrap/issues/6427) [#6360](https://github.com/angular-ui/bootstrap/issues/6360)
|
||||
* **carousel:** remove transition buffering([86ee770](https://github.com/angular-ui/bootstrap/commit/86ee770)), closes [#6367](https://github.com/angular-ui/bootstrap/issues/6367) [#5967](https://github.com/angular-ui/bootstrap/issues/5967)
|
||||
* **dropdown:** do nothing if not open when clicking([761db7b](https://github.com/angular-ui/bootstrap/commit/761db7b)), closes [#6414](https://github.com/angular-ui/bootstrap/issues/6414)
|
||||
* **tooltip:** unbind keypress listener on hide([f5b357f](https://github.com/angular-ui/bootstrap/commit/f5b357f)), closes [#6423](https://github.com/angular-ui/bootstrap/issues/6423) [#6405](https://github.com/angular-ui/bootstrap/issues/6405)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **dropdown:** make dropdown-append-to-body configurable ([#6356](https://github.com/angular-ui/bootstrap/issues/6356))([7d3a750](https://github.com/angular-ui/bootstrap/commit/7d3a750))
|
||||
* **pagination:** Added menu and menuitem roles (closes [#6383](https://github.com/angular-ui/bootstrap/issues/6383)) ([#6386](https://github.com/angular-ui/bootstrap/issues/6386))([71dc691](https://github.com/angular-ui/bootstrap/commit/71dc691)), closes [#6383](https://github.com/angular-ui/bootstrap/issues/6383) [(#6386](https://github.com/(/issues/6386)
|
||||
|
||||
|
||||
|
||||
<a name="2.4.0"></a>
|
||||
# [2.4.0](https://github.com/angular-ui/bootstrap/compare/2.3.2...v2.4.0) (2016-12-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **dateparser:** allow overriding of parsers([5a3e44a](https://github.com/angular-ui/bootstrap/commit/5a3e44a)), closes [#6370](https://github.com/angular-ui/bootstrap/issues/6370) [#6373](https://github.com/angular-ui/bootstrap/issues/6373)
|
||||
|
||||
|
||||
|
||||
<a name="2.3.2"></a>
|
||||
## [2.3.2](https://github.com/angular-ui/bootstrap/compare/2.3.1...v2.3.2) (2016-12-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **dropdown:** re-add close([955848c](https://github.com/angular-ui/bootstrap/commit/955848c)), closes [#6382](https://github.com/angular-ui/bootstrap/issues/6382) [#6321](https://github.com/angular-ui/bootstrap/issues/6321) [#6357](https://github.com/angular-ui/bootstrap/issues/6357) [#6364](https://github.com/angular-ui/bootstrap/issues/6364)
|
||||
|
||||
|
||||
|
||||
<a name="2.3.1"></a>
|
||||
## [2.3.1](https://github.com/angular-ui/bootstrap/compare/2.3.0...v2.3.1) (2016-12-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **dateparser:** add new date format for angular 1.5+ only([f2722b5](https://github.com/angular-ui/bootstrap/commit/f2722b5)), closes [#6349](https://github.com/angular-ui/bootstrap/issues/6349)
|
||||
|
||||
* **datepickerPopup:** change to toTimezone only([1962485](https://github.com/angular-ui/bootstrap/commit/1962485)), fixes [#6235](https://github.com/angular-ui/bootstrap/issues/6235)
|
||||
|
||||
* **modal:** revert focus behavior on open([8a4f625](https://github.com/angular-ui/bootstrap/commit/8a4f625)), closes [#6295](https://github.com/angular-ui/bootstrap/issues/6295)
|
||||
|
||||
<a name="2.3.0"></a>
|
||||
# [2.3.0](https://github.com/angular-ui/bootstrap/compare/2.2.0...2.3.0) (2016-11-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **dateparser:** add LLLL support([25ff206](https://github.com/angular-ui/bootstrap/commit/25ff206)), closes [#6281](https://github.com/angular-ui/bootstrap/issues/6281)
|
||||
|
||||
|
||||
|
||||
<a name="2.2.0"></a>
|
||||
# [2.2.0](https://github.com/angular-ui/bootstrap/compare/2.1.4...2.2.0) (2016-10-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **dropdown:** exit keybind is not open ([14384fc](https://github.com/angular-ui/bootstrap/commit/14384fc)), closes [#6278](https://github.com/angular-ui/bootstrap/issues/6278) [#6208](https://github.com/angular-ui/bootstrap/issues/6208)
|
||||
* **modal:** improve ARIA support. ([f9f7e02](https://github.com/angular-ui/bootstrap/commit/f9f7e02)), closes [#6203](https://github.com/angular-ui/bootstrap/issues/6203)
|
||||
* **position:** correct scrollbar width calculation ([58f1813](https://github.com/angular-ui/bootstrap/commit/58f1813)), closes [#6273](https://github.com/angular-ui/bootstrap/issues/6273)
|
||||
* **tooltip:** cancel timeout when hidden ([7855976](https://github.com/angular-ui/bootstrap/commit/7855976)), closes [#6226](https://github.com/angular-ui/bootstrap/issues/6226) [#6221](https://github.com/angular-ui/bootstrap/issues/6221)
|
||||
|
||||
### Features
|
||||
|
||||
* **timepicker:** add validation information ([9666c64](https://github.com/angular-ui/bootstrap/commit/9666c64)), closes [#6230](https://github.com/angular-ui/bootstrap/issues/6230) [#6259](https://github.com/angular-ui/bootstrap/issues/6259)
|
||||
|
||||
|
||||
|
||||
<a name="2.1.4"></a>
|
||||
## [2.1.4](https://github.com/angular-ui/bootstrap/compare/2.1.3...2.1.4) (2016-09-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datepicker:** improve accessibility ([3f70d76](https://github.com/angular-ui/bootstrap/commit/3f70d76)), closes [#6247](https://github.com/angular-ui/bootstrap/issues/6247)
|
||||
* **dropdown:** prevent premature scope removal ([08ee30a](https://github.com/angular-ui/bootstrap/commit/08ee30a)), closes [#6238](https://github.com/angular-ui/bootstrap/issues/6238) [#6225](https://github.com/angular-ui/bootstrap/issues/6225)
|
||||
|
||||
|
||||
|
||||
<a name="2.1.3"></a>
|
||||
## [2.1.3](https://github.com/angular-ui/bootstrap/compare/2.1.2...2.1.3) (2016-08-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modal:** compile only once with component ([969eb9c](https://github.com/angular-ui/bootstrap/commit/969eb9c)), closes [#6202](https://github.com/angular-ui/bootstrap/issues/6202) [#6201](https://github.com/angular-ui/bootstrap/issues/6201)
|
||||
|
||||
|
||||
|
||||
<a name="2.1.2"></a>
|
||||
## [2.1.2](https://github.com/angular-ui/bootstrap/compare/2.1.1...2.1.2) (2016-08-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **collapse:** revert change to transition css ([515bcf2](https://github.com/angular-ui/bootstrap/commit/515bcf2)), closes [#6196](https://github.com/angular-ui/bootstrap/issues/6196) [#6194](https://github.com/angular-ui/bootstrap/issues/6194)
|
||||
* **datepicker:** fix accidental global ([ddcacb7](https://github.com/angular-ui/bootstrap/commit/ddcacb7)), closes [#6188](https://github.com/angular-ui/bootstrap/issues/6188)
|
||||
* **modal:** close and dismiss bindings on component ([3e8ecff](https://github.com/angular-ui/bootstrap/commit/3e8ecff)), closes [#6192](https://github.com/angular-ui/bootstrap/issues/6192) [#6191](https://github.com/angular-ui/bootstrap/issues/6191)
|
||||
|
||||
|
||||
|
||||
<a name="2.1.1"></a>
|
||||
## [2.1.1](https://github.com/angular-ui/bootstrap/compare/2.1.0...2.1.1) (2016-08-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **collapse:** default to css([aef24cd](https://github.com/angular-ui/bootstrap/commit/aef24cd)), closes [#6182](https://github.com/angular-ui/bootstrap/issues/6182) [#6045](https://github.com/angular-ui/bootstrap/issues/6045)
|
||||
* **modal:** switch to .append([fb5fabf](https://github.com/angular-ui/bootstrap/commit/fb5fabf)), closes [#6187](https://github.com/angular-ui/bootstrap/issues/6187) [#6186](https://github.com/angular-ui/bootstrap/issues/6186)
|
||||
|
||||
|
||||
|
||||
<a name="2.1.0"></a>
|
||||
# [2.1.0](https://github.com/angular-ui/bootstrap/compare/2.0.2...2.1.0) (2016-08-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **collapse:** remove unnecessary inherit ([ca20be4](https://github.com/angular-ui/bootstrap/commit/ca20be4)), closes [#6164](https://github.com/angular-ui/bootstrap/issues/6164) [#6163](https://github.com/angular-ui/bootstrap/issues/6163)
|
||||
* **collapse:** set overflow to hidden on transition ([84cc2cf](https://github.com/angular-ui/bootstrap/commit/84cc2cf)), closes [#6180](https://github.com/angular-ui/bootstrap/issues/6180) [#5474](https://github.com/angular-ui/bootstrap/issues/5474)
|
||||
* **datepickerPopup:** apply timezone conversion ([f147d22](https://github.com/angular-ui/bootstrap/commit/f147d22)), closes [#6173](https://github.com/angular-ui/bootstrap/issues/6173) [#6147](https://github.com/angular-ui/bootstrap/issues/6147)
|
||||
* **modal:** improve ARIA support ([4a5e6a7](https://github.com/angular-ui/bootstrap/commit/4a5e6a7)), closes [#4772](https://github.com/angular-ui/bootstrap/issues/4772)
|
||||
* **tooltip:** close tooltip on esc ([f5ff12c](https://github.com/angular-ui/bootstrap/commit/f5ff12c)), closes [#6177](https://github.com/angular-ui/bootstrap/issues/6177) [#6108](https://github.com/angular-ui/bootstrap/issues/6108)
|
||||
|
||||
### Features
|
||||
|
||||
* **modal:** add component support ([2ade054](https://github.com/angular-ui/bootstrap/commit/2ade054)), closes [#5683](https://github.com/angular-ui/bootstrap/issues/5683) [#6179](https://github.com/angular-ui/bootstrap/issues/6179)
|
||||
|
||||
|
||||
|
||||
<a name="2.0.2"></a>
|
||||
## [2.0.2](https://github.com/angular-ui/bootstrap/compare/2.0.1...2.0.2) (2016-08-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datepickerPopup:** correctly format to timezone ([fbd0845](https://github.com/angular-ui/bootstrap/commit/fbd0845)), closes [#6159](https://github.com/angular-ui/bootstrap/issues/6159) [#6105](https://github.com/angular-ui/bootstrap/issues/6105) [#6146](https://github.com/angular-ui/bootstrap/issues/6146) [#6147](https://github.com/angular-ui/bootstrap/issues/6147)
|
||||
* **dropdown:** fix keyboard-nav ([6bad759](https://github.com/angular-ui/bootstrap/commit/6bad759)), closes [#6102](https://github.com/angular-ui/bootstrap/issues/6102) [#6154](https://github.com/angular-ui/bootstrap/issues/6154)
|
||||
|
||||
|
||||
|
||||
<a name="2.0.1"></a>
|
||||
## [2.0.1](https://github.com/angular-ui/bootstrap/compare/2.0.0...2.0.1) (2016-08-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modal:** restore broken stacked modals([c61d16a](https://github.com/angular-ui/bootstrap/commit/c61d16a)), closes [#6103](https://github.com/angular-ui/bootstrap/issues/6103) [#6104](https://github.com/angular-ui/bootstrap/issues/6104)
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0"></a>
|
||||
# [2.0.0](https://github.com/angular-ui/bootstrap/compare/1.3.3...2.0.0) (2016-07-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **dateparser:** correctly format with literals([d846e2d](https://github.com/angular-ui/bootstrap/commit/d846e2d)), closes [#6055](https://github.com/angular-ui/bootstrap/issues/6055) [#5620](https://github.com/angular-ui/bootstrap/issues/5620) [#5802](https://github.com/angular-ui/bootstrap/issues/5802)
|
||||
* **datepickerPopup:** clear date when button is clicked([b0466d9](https://github.com/angular-ui/bootstrap/commit/b0466d9)), closes [#5945](https://github.com/angular-ui/bootstrap/issues/5945) [#5906](https://github.com/angular-ui/bootstrap/issues/5906)
|
||||
* **datepickerPopup:** specify dependency on datepicker([4fef037](https://github.com/angular-ui/bootstrap/commit/4fef037)), closes [#5954](https://github.com/angular-ui/bootstrap/issues/5954)
|
||||
* **datepickerPopup:** use value instead of viewValue([7e320e0](https://github.com/angular-ui/bootstrap/commit/7e320e0)), closes [#6007](https://github.com/angular-ui/bootstrap/issues/6007)
|
||||
* **dropdown:** align position correctly with scrollbar([2d831bc](https://github.com/angular-ui/bootstrap/commit/2d831bc)), closes [#6008](https://github.com/angular-ui/bootstrap/issues/6008) [#5942](https://github.com/angular-ui/bootstrap/issues/5942)
|
||||
* **dropdown:** bind event listener on dropdown menu([6038403](https://github.com/angular-ui/bootstrap/commit/6038403)), closes [#5982](https://github.com/angular-ui/bootstrap/issues/5982)
|
||||
* **modal:** check for overflow hidden([433e536](https://github.com/angular-ui/bootstrap/commit/433e536)), closes [#6037](https://github.com/angular-ui/bootstrap/issues/6037) [#6041](https://github.com/angular-ui/bootstrap/issues/6041)
|
||||
* **modal:** filter out non-tabbable elements([35ced04](https://github.com/angular-ui/bootstrap/commit/35ced04)), closes [#5963](https://github.com/angular-ui/bootstrap/issues/5963) [#5955](https://github.com/angular-ui/bootstrap/issues/5955)
|
||||
* **modal:** remove unused template from modal([1de58a3](https://github.com/angular-ui/bootstrap/commit/1de58a3))
|
||||
* **modal:** remove window class after animation([409b7aa](https://github.com/angular-ui/bootstrap/commit/409b7aa)), closes [#6056](https://github.com/angular-ui/bootstrap/issues/6056) [#6051](https://github.com/angular-ui/bootstrap/issues/6051)
|
||||
* **tooltip:** missed dependency for cjs([164811a](https://github.com/angular-ui/bootstrap/commit/164811a)), closes [#5999](https://github.com/angular-ui/bootstrap/issues/5999)
|
||||
* **tooltip:** reposition for placement top([34a1443](https://github.com/angular-ui/bootstrap/commit/34a1443)), closes [#5959](https://github.com/angular-ui/bootstrap/issues/5959)
|
||||
* **typeahead:** change to select class([13c14af](https://github.com/angular-ui/bootstrap/commit/13c14af)), closes [#5922](https://github.com/angular-ui/bootstrap/issues/5922) [#5848](https://github.com/angular-ui/bootstrap/issues/5848)
|
||||
* **typeahead:** clear validity in $digest([ed3400b](https://github.com/angular-ui/bootstrap/commit/ed3400b)), closes [#6033](https://github.com/angular-ui/bootstrap/issues/6033) [#6032](https://github.com/angular-ui/bootstrap/issues/6032)
|
||||
* **typeahead:** remove duplicate id attribute([6d5b84a](https://github.com/angular-ui/bootstrap/commit/6d5b84a)), closes [#5936](https://github.com/angular-ui/bootstrap/issues/5936) [#5926](https://github.com/angular-ui/bootstrap/issues/5926)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **accordion:** add appropriate tabindex on disabled([5f4eedd](https://github.com/angular-ui/bootstrap/commit/5f4eedd)), closes [#4067](https://github.com/angular-ui/bootstrap/issues/4067) [#5990](https://github.com/angular-ui/bootstrap/issues/5990)
|
||||
* **accordion:** remove replace: true usage([3819bbe](https://github.com/angular-ui/bootstrap/commit/3819bbe)), closes [#5985](https://github.com/angular-ui/bootstrap/issues/5985)
|
||||
* **alert:** remove replace: true usage([2458c28](https://github.com/angular-ui/bootstrap/commit/2458c28)), closes [#5986](https://github.com/angular-ui/bootstrap/issues/5986)
|
||||
* **carousel:** remove replace: true usage([5046bd4](https://github.com/angular-ui/bootstrap/commit/5046bd4)), closes [#5987](https://github.com/angular-ui/bootstrap/issues/5987)
|
||||
* **collapse:** add horizontal support([1ec0997](https://github.com/angular-ui/bootstrap/commit/1ec0997)), closes [#3593](https://github.com/angular-ui/bootstrap/issues/3593) [#6010](https://github.com/angular-ui/bootstrap/issues/6010)
|
||||
* **datepicker:** add monthColumns([39d9b98](https://github.com/angular-ui/bootstrap/commit/39d9b98)), closes [#5515](https://github.com/angular-ui/bootstrap/issues/5515) [#5935](https://github.com/angular-ui/bootstrap/issues/5935)
|
||||
* **datepicker:** add ngModelOptions support([23b91d9](https://github.com/angular-ui/bootstrap/commit/23b91d9)), closes [#5933](https://github.com/angular-ui/bootstrap/issues/5933) [#5825](https://github.com/angular-ui/bootstrap/issues/5825)
|
||||
* **datepicker:** remove replace: true usage([e92fb7f](https://github.com/angular-ui/bootstrap/commit/e92fb7f)), closes [#5988](https://github.com/angular-ui/bootstrap/issues/5988)
|
||||
* **datepickerPopup:** remove replace usage([a47bced](https://github.com/angular-ui/bootstrap/commit/a47bced)), closes [#5993](https://github.com/angular-ui/bootstrap/issues/5993)
|
||||
* **dropdown:** focus toggle on close from click([cce0097](https://github.com/angular-ui/bootstrap/commit/cce0097)), closes [#5934](https://github.com/angular-ui/bootstrap/issues/5934) [#5782](https://github.com/angular-ui/bootstrap/issues/5782)
|
||||
* **dropdown:** use .value() for uibDropdownConfig([7457fb0](https://github.com/angular-ui/bootstrap/commit/7457fb0)), closes [#6004](https://github.com/angular-ui/bootstrap/issues/6004) [#6006](https://github.com/angular-ui/bootstrap/issues/6006)
|
||||
* **modal:** append using $animate([1cbd73d](https://github.com/angular-ui/bootstrap/commit/1cbd73d)), closes [#6023](https://github.com/angular-ui/bootstrap/issues/6023) [#6029](https://github.com/angular-ui/bootstrap/issues/6029)
|
||||
* **modal:** remove replace usage([96d52ce](https://github.com/angular-ui/bootstrap/commit/96d52ce)), closes [#5989](https://github.com/angular-ui/bootstrap/issues/5989)
|
||||
* **pager:** remove replace usage([9b24e1d](https://github.com/angular-ui/bootstrap/commit/9b24e1d)), closes [#5991](https://github.com/angular-ui/bootstrap/issues/5991)
|
||||
* **pager:** toggle tabindex when disabled([0d8cec6](https://github.com/angular-ui/bootstrap/commit/0d8cec6)), closes [#5996](https://github.com/angular-ui/bootstrap/issues/5996)
|
||||
* **pagination:** disable tabbing when disabled([1a870a3](https://github.com/angular-ui/bootstrap/commit/1a870a3)), closes [#5984](https://github.com/angular-ui/bootstrap/issues/5984)
|
||||
* **pagination:** remove replace usage([387c6e7](https://github.com/angular-ui/bootstrap/commit/387c6e7)), closes [#5992](https://github.com/angular-ui/bootstrap/issues/5992)
|
||||
* **rating:** remove replace usage([d6fe9c7](https://github.com/angular-ui/bootstrap/commit/d6fe9c7)), closes [#5998](https://github.com/angular-ui/bootstrap/issues/5998)
|
||||
* **stackedMap:** improve perf of removeTop([a075824](https://github.com/angular-ui/bootstrap/commit/a075824)), closes [#5925](https://github.com/angular-ui/bootstrap/issues/5925) [#5932](https://github.com/angular-ui/bootstrap/issues/5932)
|
||||
* **timepicker:** avoid allowing to tab to controls([4e68778](https://github.com/angular-ui/bootstrap/commit/4e68778)), closes [#5930](https://github.com/angular-ui/bootstrap/issues/5930)
|
||||
* **timepicker:** remove replace usage([7518821](https://github.com/angular-ui/bootstrap/commit/7518821)), closes [#5997](https://github.com/angular-ui/bootstrap/issues/5997)
|
||||
* **tooltip:** add expression support([4b91222](https://github.com/angular-ui/bootstrap/commit/4b91222)), closes [#5221](https://github.com/angular-ui/bootstrap/issues/5221) [#5938](https://github.com/angular-ui/bootstrap/issues/5938)
|
||||
* **tooltip:** remove replace usage([1616e97](https://github.com/angular-ui/bootstrap/commit/1616e97)), closes [#5994](https://github.com/angular-ui/bootstrap/issues/5994)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* **dropdown:** change back to .constant()([4e0e34f](https://github.com/angular-ui/bootstrap/commit/4e0e34f))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* tooltip: The template structure changed slightly due to the removal of `replace: true` - see documentation examples in action to see differences in practice.
|
||||
* typeahead: This changes the selector used so that it doesn't select for the `li` tag specifically, but the elements with the class `uib-typeahead-match` - if using a custom template, this class needs to be added to the `li` element in the typeahead popup template.
|
||||
* rating: Due to the removal of `replace: true`, this results in a slight change to the HTML structure - see the documentation examples to see it in action.
|
||||
* timepicker: This removes `replace: true`, which changes the HTML structure slightly - see the documentation examples to see it in action.
|
||||
* datepickerPopup: Due to the nature of `replace: true`, this has a slight structural HTML change in the popup as a result - see documentation examples for the change in action.
|
||||
* pagination: Due to the removal of `replace: true`, this affects the HTML structure slightly - see documentation examples to see new usage.
|
||||
* pager: This removes `replace: true` usage from the pager, which causes a slight usage change - see documentation examples for new usage.
|
||||
* modal: This removes `replace: true` usage, causing some structural changes to the HTML - the major part here is that there is no more backdrop template, and the top level elements for the window/backdrop elements lose a little flexibility. See documentation examples for new structure.
|
||||
* modal: This introduces a minor behavior change in when the class is removed
|
||||
* carousel: Due to the removal of `replace: true`, this causes a slight HTML structure change to the carousel and the slide elements - see documentation demos to see how it changes. This also caused removal of the ngTouch built in support - if one is using ng-touch, one needs to add the `ng-swipe-left` and `ng-swipe-right` directives to the carousel element with relevant logic.
|
||||
* alert: This removes the `replace: true` usage, so this has an effect on how one uses the directive in the template - see documentation for reference
|
||||
* accordion: This removes usage of `replace: true` in the accordion group, which results in a template change where the template no longer needs to contain the panel itself, but its contents. The accordion group will add the `panel` class by default, so the user just needs to add the appropriate classes to the accordion group element. This allows the user to use ng-class as well to fully control the panel related classes, so `panel-class` now is unnecessary
|
||||
* tooltip: This removes support for plain strings and interpolations in tooltip-trigger and popover-trigger - please change these appropriately. See test changes in this commit for reference
|
||||
* typeahead: This change removes the `id` attribute on the first `<input>`
|
||||
element placed into the DOM when the `typeahead-show-hint` attribute is used
|
||||
and there is an `id` attribute present on the original `uib-typeahead` element.
|
||||
This could affect selectors if they are being used.
|
||||
* dropdown: This changes the focus behavior of the dropdown slightly, and potentially may break code built around current usage
|
||||
* datepicker: This modifies the current behavior around the datepicker & popup's ngModelOptions, which may affect custom components that are built around both
|
||||
* datepicker: As a result of removal of `replace: true`, there is the potential that this may break some CSS layout due to the slightly different HTML. Refer to the documentation examples to see the new structure.
|
||||
|
||||
|
||||
|
||||
<a name="1.3.3"></a>
|
||||
## [1.3.3](https://github.com/angular-ui/bootstrap/compare/1.3.2...1.3.3) (2016-05-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datepicker:** add intermediary valid date check([a417ec2](https://github.com/angular-ui/bootstrap/commit/a417ec2)), closes [#5872](https://github.com/angular-ui/bootstrap/issues/5872) [#5865](https://github.com/angular-ui/bootstrap/issues/5865)
|
||||
* **datepickerPopup:** convert numbers to date before parsing([5188463](https://github.com/angular-ui/bootstrap/commit/5188463)), closes [#5873](https://github.com/angular-ui/bootstrap/issues/5873) [#5871](https://github.com/angular-ui/bootstrap/issues/5871)
|
||||
* **dropdown:** align position with vertical scrollbar([2b48259](https://github.com/angular-ui/bootstrap/commit/2b48259)), closes [#5830](https://github.com/angular-ui/bootstrap/issues/5830) [#4317](https://github.com/angular-ui/bootstrap/issues/4317)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **accordion:** add alternative attribute support([4c40d9d](https://github.com/angular-ui/bootstrap/commit/4c40d9d)), closes [#5834](https://github.com/angular-ui/bootstrap/issues/5834) [#5839](https://github.com/angular-ui/bootstrap/issues/5839)
|
||||
* **modal:** add resolve values to template([dd09148](https://github.com/angular-ui/bootstrap/commit/dd09148)), closes [#5808](https://github.com/angular-ui/bootstrap/issues/5808) [#5857](https://github.com/angular-ui/bootstrap/issues/5857)
|
||||
* **tab:** allow strings for index([26d3995](https://github.com/angular-ui/bootstrap/commit/26d3995)), closes [#5713](https://github.com/angular-ui/bootstrap/issues/5713) [#5827](https://github.com/angular-ui/bootstrap/issues/5827)
|
||||
* **tabs:** pass the selected index to onDeselect([241fea8](https://github.com/angular-ui/bootstrap/commit/241fea8)), closes [#5820](https://github.com/angular-ui/bootstrap/issues/5820) [#5821](https://github.com/angular-ui/bootstrap/issues/5821)
|
||||
* **typeahead:** Add support for `should-select`([2ffb86f](https://github.com/angular-ui/bootstrap/commit/2ffb86f)), closes [#5671](https://github.com/angular-ui/bootstrap/issues/5671) [#5794](https://github.com/angular-ui/bootstrap/issues/5794)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* modal: Since this adds support for $resolve being exposed on $scope, it could potentially overwrite any pre-existing usage of it - this is an unlikely scenario, but marked as a breaking change in case this key is being used
|
||||
|
||||
|
||||
|
||||
<a name="1.3.2"></a>
|
||||
## [1.3.2](https://github.com/angular-ui/bootstrap/compare/1.3.1...1.3.2) (2016-04-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datepicker:** ensure datepicker is focused ([33cbd0f](https://github.com/angular-ui/bootstrap/commit/33cbd0f)), closes [#5761](https://github.com/angular-ui/bootstrap/issues/5761) [#5754](https://github.com/angular-ui/bootstrap/issues/5754)
|
||||
* **datepickerPopup:** do not show incorrect warning ([98eb8f1](https://github.com/angular-ui/bootstrap/commit/98eb8f1)), closes [#5743](https://github.com/angular-ui/bootstrap/issues/5743) [#5747](https://github.com/angular-ui/bootstrap/issues/5747)
|
||||
* **dropdown:** stop esc keydown event ([68200bb](https://github.com/angular-ui/bootstrap/commit/68200bb)), closes [#5778](https://github.com/angular-ui/bootstrap/issues/5778) [#5787](https://github.com/angular-ui/bootstrap/issues/5787)
|
||||
* **modal:** missing require in modal ([a887d2b](https://github.com/angular-ui/bootstrap/commit/a887d2b)), closes [#5786](https://github.com/angular-ui/bootstrap/issues/5786)
|
||||
* **modal:** scroll padding only added once ([aef08d5](https://github.com/angular-ui/bootstrap/commit/aef08d5)), closes [#5790](https://github.com/angular-ui/bootstrap/issues/5790) [#5789](https://github.com/angular-ui/bootstrap/issues/5789)
|
||||
* **site:** allow FastClick to be blocked ([37f9cd2](https://github.com/angular-ui/bootstrap/commit/37f9cd2)), closes [#5756](https://github.com/angular-ui/bootstrap/issues/5756)
|
||||
* **tooltip:** arrow position ([d96d53e](https://github.com/angular-ui/bootstrap/commit/d96d53e)), closes [#5785](https://github.com/angular-ui/bootstrap/issues/5785) [#5779](https://github.com/angular-ui/bootstrap/issues/5779)
|
||||
* **typeahead:** use $setViewValue on blur ([bba3f27](https://github.com/angular-ui/bootstrap/commit/bba3f27)), closes [#5769](https://github.com/angular-ui/bootstrap/issues/5769) [#5694](https://github.com/angular-ui/bootstrap/issues/5694)
|
||||
|
||||
### Features
|
||||
|
||||
* **modal:** add no css import script ([a27a4e2](https://github.com/angular-ui/bootstrap/commit/a27a4e2)), closes [#5788](https://github.com/angular-ui/bootstrap/issues/5788)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* dropdown: Stops propagation of keydown event when escape key is pressed. Removes keydown event from the document and moves it to the dropdown element.
|
||||
|
||||
|
||||
|
||||
<a name="1.3.1"></a>
|
||||
## [1.3.1](https://github.com/angular-ui/bootstrap/compare/1.3.0...1.3.1) (2016-04-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datepickerPopup:** register popup module ([86ee9c3](https://github.com/angular-ui/bootstrap/commit/86ee9c3)), closes [#5745](https://github.com/angular-ui/bootstrap/issues/5745)
|
||||
* **modal:** ensure correct index is set ([a08ad70](https://github.com/angular-ui/bootstrap/commit/a08ad70)), closes [#5733](https://github.com/angular-ui/bootstrap/issues/5733) [#5670](https://github.com/angular-ui/bootstrap/issues/5670)
|
||||
|
||||
|
||||
|
||||
<a name="1.3.0"></a>
|
||||
# [1.3.0](https://github.com/angular-ui/bootstrap/compare/1.2.5...1.3.0) (2016-04-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **carousel:** fix animation from programmatic trigger ([44fb19b](https://github.com/angular-ui/bootstrap/commit/44fb19b)), closes [#5674](https://github.com/angular-ui/bootstrap/issues/5674) [#5601](https://github.com/angular-ui/bootstrap/issues/5601)
|
||||
* modify CSS loaded for modules with dependencies ([8074899](https://github.com/angular-ui/bootstrap/commit/8074899)), closes [#5698](https://github.com/angular-ui/bootstrap/issues/5698)
|
||||
* **carousel:** use proper sort comparison ([434c1c5](https://github.com/angular-ui/bootstrap/commit/434c1c5)), closes [#5638](https://github.com/angular-ui/bootstrap/issues/5638) [#5702](https://github.com/angular-ui/bootstrap/issues/5702)
|
||||
* **modal:** body content shift ([c83d0a8](https://github.com/angular-ui/bootstrap/commit/c83d0a8)), closes [#2631](https://github.com/angular-ui/bootstrap/issues/2631) [#5711](https://github.com/angular-ui/bootstrap/issues/5711)
|
||||
* **popover:** rename title attribute ([cca1460](https://github.com/angular-ui/bootstrap/commit/cca1460)), closes [#5719](https://github.com/angular-ui/bootstrap/issues/5719) [#5721](https://github.com/angular-ui/bootstrap/issues/5721)
|
||||
* **tab:** add support for tab deselect prevention ([e0fcc00](https://github.com/angular-ui/bootstrap/commit/e0fcc00)), closes [#5720](https://github.com/angular-ui/bootstrap/issues/5720) [#5723](https://github.com/angular-ui/bootstrap/issues/5723)
|
||||
* **tab:** correctly identify index on removal ([03e6047](https://github.com/angular-ui/bootstrap/commit/03e6047)), closes [#5689](https://github.com/angular-ui/bootstrap/issues/5689)
|
||||
* **tab:** fix event handler call ([d767afb](https://github.com/angular-ui/bootstrap/commit/d767afb)), closes [#5720](https://github.com/angular-ui/bootstrap/issues/5720) [#5738](https://github.com/angular-ui/bootstrap/issues/5738)
|
||||
|
||||
### Features
|
||||
|
||||
* add support for loading without CSS ([de504fb](https://github.com/angular-ui/bootstrap/commit/de504fb)), closes [#5696](https://github.com/angular-ui/bootstrap/issues/5696)
|
||||
* **carousel:** disable prev/next arrows if at bounds ([a3964d4](https://github.com/angular-ui/bootstrap/commit/a3964d4)), closes [#5704](https://github.com/angular-ui/bootstrap/issues/5704) [#5708](https://github.com/angular-ui/bootstrap/issues/5708)
|
||||
* **dateparser:** use 68 as the yy format pivot year ([701e0cb](https://github.com/angular-ui/bootstrap/commit/701e0cb)), closes [#5735](https://github.com/angular-ui/bootstrap/issues/5735)
|
||||
* **datepicker:** deprecate literal usage ([fc02fd1](https://github.com/angular-ui/bootstrap/commit/fc02fd1)), closes [#5658](https://github.com/angular-ui/bootstrap/issues/5658) [#5732](https://github.com/angular-ui/bootstrap/issues/5732)
|
||||
* **datepicker:** remove deprecated code ([ad91c82](https://github.com/angular-ui/bootstrap/commit/ad91c82)), closes [#5660](https://github.com/angular-ui/bootstrap/issues/5660)
|
||||
* **datepicker:** watch for changes when falsy ([45165ba](https://github.com/angular-ui/bootstrap/commit/45165ba)), closes [#5672](https://github.com/angular-ui/bootstrap/issues/5672) [#5677](https://github.com/angular-ui/bootstrap/issues/5677)
|
||||
* **datepickerPopup:** split off popup from datepicker ([fbc873c](https://github.com/angular-ui/bootstrap/commit/fbc873c)), closes [#5676](https://github.com/angular-ui/bootstrap/issues/5676)
|
||||
* **dropdown:** remove $locationChangeSuccess listener ([c534cb4](https://github.com/angular-ui/bootstrap/commit/c534cb4)), closes [#5648](https://github.com/angular-ui/bootstrap/issues/5648) [#5680](https://github.com/angular-ui/bootstrap/issues/5680)
|
||||
* **timepicker:** remove automatic padding when empty ([449c0d1](https://github.com/angular-ui/bootstrap/commit/449c0d1)), closes [#5293](https://github.com/angular-ui/bootstrap/issues/5293) [#5299](https://github.com/angular-ui/bootstrap/issues/5299)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* carousel: This adds disabled CSS - there is a possibility this may break existing UI slightly for those adding custom CSS/making use of custom templates. Modify the template appropriately if necessary
|
||||
* timepicker: This removes automatic padding done by the timepicker
|
||||
when the input is empty - if that behavior is desired, write a custom
|
||||
directive implementing it
|
||||
* dropdown: The dropdown no longer will remain open on $locationChangeSuccess with autoclose set to disabled. Implement this logic inside app along with usage of isOpen two-way binding if this functionality is desired.
|
||||
* datepickerPopup: The datepicker popup is no longer a part of the
|
||||
datepicker module, but now a part of the datepickerPopup module. Please
|
||||
change code accordingly if including specific modules
|
||||
* datepicker: This removes inline datepicker attribute support and
|
||||
attribute pass-throughs in the popup
|
||||
|
||||
|
||||
|
||||
<a name="1.2.5"></a>
|
||||
## [1.2.5](https://github.com/angular-ui/bootstrap/compare/1.2.4...1.2.5) (2016-03-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datepicker:** dereference date initialization ([1b888d4](https://github.com/angular-ui/bootstrap/commit/1b888d4)), closes [#5643](https://github.com/angular-ui/bootstrap/issues/5643) [#5441](https://github.com/angular-ui/bootstrap/issues/5441)
|
||||
* **datepicker:** fix today button disabled condition ([316e96c](https://github.com/angular-ui/bootstrap/commit/316e96c)), closes [#5637](https://github.com/angular-ui/bootstrap/issues/5637) [#5622](https://github.com/angular-ui/bootstrap/issues/5622)
|
||||
* **modal:** dynamically fetch elements ([e15a22a](https://github.com/angular-ui/bootstrap/commit/e15a22a)), closes [#5630](https://github.com/angular-ui/bootstrap/issues/5630) [#5050](https://github.com/angular-ui/bootstrap/issues/5050) [#5421](https://github.com/angular-ui/bootstrap/issues/5421)
|
||||
* **popover:** add popover-html css ([54ac06a](https://github.com/angular-ui/bootstrap/commit/54ac06a)), closes [#5603](https://github.com/angular-ui/bootstrap/issues/5603) [#5602](https://github.com/angular-ui/bootstrap/issues/5602)
|
||||
* **tooltip:** css to support arrow positioning ([66c416c](https://github.com/angular-ui/bootstrap/commit/66c416c)), closes [#5610](https://github.com/angular-ui/bootstrap/issues/5610) [#5611](https://github.com/angular-ui/bootstrap/issues/5611)
|
||||
* **typeahead:** reset errors when clearing ([bc7c55a](https://github.com/angular-ui/bootstrap/commit/bc7c55a)), closes [#5641](https://github.com/angular-ui/bootstrap/issues/5641)
|
||||
|
||||
### Features
|
||||
|
||||
* **accordion:** add bound panel-class support ([ffb5529](https://github.com/angular-ui/bootstrap/commit/ffb5529)), closes [#5368](https://github.com/angular-ui/bootstrap/issues/5368) [#5596](https://github.com/angular-ui/bootstrap/issues/5596)
|
||||
* **modal:** exclude hidden elements from query ([5ef56e2](https://github.com/angular-ui/bootstrap/commit/5ef56e2)), closes [#5512](https://github.com/angular-ui/bootstrap/issues/5512) [#5644](https://github.com/angular-ui/bootstrap/issues/5644)
|
||||
* **rating:** ability to disable rating reset ([8747b58](https://github.com/angular-ui/bootstrap/commit/8747b58)), closes [#5532](https://github.com/angular-ui/bootstrap/issues/5532) [#5631](https://github.com/angular-ui/bootstrap/issues/5631)
|
||||
* **tab:** add select expressions ([bb36e40](https://github.com/angular-ui/bootstrap/commit/bb36e40)), closes [#5438](https://github.com/angular-ui/bootstrap/issues/5438)
|
||||
* **timepicker:** add pad-hours support ([c7be087](https://github.com/angular-ui/bootstrap/commit/c7be087)), closes [#4288](https://github.com/angular-ui/bootstrap/issues/4288) [#5633](https://github.com/angular-ui/bootstrap/issues/5633)
|
||||
|
||||
|
||||
|
||||
<a name="1.2.4"></a>
|
||||
## [1.2.4](https://github.com/angular-ui/bootstrap/compare/1.2.3...v1.2.4) (2016-03-06)
|
||||
## [1.2.4](https://github.com/angular-ui/bootstrap/compare/1.2.3...1.2.4) (2016-03-06)
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.2.3"></a>
|
||||
## [1.2.3](https://github.com/angular-ui/bootstrap/compare/1.2.2...v1.2.3) (2016-03-06)
|
||||
## [1.2.3](https://github.com/angular-ui/bootstrap/compare/1.2.2...1.2.3) (2016-03-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
@@ -24,7 +394,7 @@
|
||||
|
||||
|
||||
<a name="1.2.2"></a>
|
||||
## [1.2.2](https://github.com/angular-ui/bootstrap/compare/1.2.1...v1.2.2) (2016-03-03)
|
||||
## [1.2.2](https://github.com/angular-ui/bootstrap/compare/1.2.1...1.2.2) (2016-03-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
@@ -41,7 +411,7 @@
|
||||
|
||||
|
||||
<a name="1.2.1"></a>
|
||||
## [1.2.1](https://github.com/angular-ui/bootstrap/compare/1.2.0...v1.2.1) (2016-02-27)
|
||||
## [1.2.1](https://github.com/angular-ui/bootstrap/compare/1.2.0...1.2.1) (2016-02-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
@@ -51,7 +421,7 @@
|
||||
|
||||
|
||||
<a name="1.2.0"></a>
|
||||
# [1.2.0](https://github.com/angular-ui/bootstrap/compare/1.1.2...v1.2.0) (2016-02-26)
|
||||
# [1.2.0](https://github.com/angular-ui/bootstrap/compare/1.1.2...1.2.0) (2016-02-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
@@ -111,7 +481,7 @@ template
|
||||
|
||||
|
||||
<a name="1.1.2"></a>
|
||||
## [1.1.2](https://github.com/angular-ui/bootstrap/compare/1.1.1...v1.1.2) (2016-02-01)
|
||||
## [1.1.2](https://github.com/angular-ui/bootstrap/compare/1.1.1...1.1.2) (2016-02-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
@@ -129,7 +499,7 @@ template
|
||||
|
||||
|
||||
<a name="1.1.1"></a>
|
||||
## [1.1.1](https://github.com/angular-ui/bootstrap/compare/v1.1.0...v1.1.1) (2016-01-25)
|
||||
## [1.1.1](https://github.com/angular-ui/bootstrap/compare/1.1.0...1.1.1) (2016-01-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
@@ -179,7 +549,7 @@ template in one's app and provide the necessary CSS
|
||||
|
||||
|
||||
<a name="1.0.3"></a>
|
||||
# [1.0.3](https://github.com/angular-ui/bootstrap/compare/v1.0.2...v1.0.3) (2016-01-12)
|
||||
# [1.0.3](https://github.com/angular-ui/bootstrap/compare/1.0.2...1.0.3) (2016-01-12)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
@@ -189,7 +559,7 @@ template in one's app and provide the necessary CSS
|
||||
|
||||
|
||||
<a name="1.0.2"></a>
|
||||
# [1.0.2](https://github.com/angular-ui/bootstrap/compare/v1.0.1...v1.0.2) (2016-01-12)
|
||||
# [1.0.2](https://github.com/angular-ui/bootstrap/compare/1.0.1...1.0.2) (2016-01-12)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
@@ -199,7 +569,7 @@ template in one's app and provide the necessary CSS
|
||||
|
||||
|
||||
<a name="1.0.1"></a>
|
||||
# [1.0.1](https://github.com/angular-ui/bootstrap/compare/1.0.0...v1.0.1) (2016-01-12)
|
||||
# [1.0.1](https://github.com/angular-ui/bootstrap/compare/1.0.0...1.0.1) (2016-01-12)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
@@ -220,7 +590,7 @@ template in one's app and provide the necessary CSS
|
||||
|
||||
|
||||
<a name="1.0.0"></a>
|
||||
# [1.0.0](https://github.com/angular-ui/bootstrap/compare/0.14.3...v1.0.0) (2016-01-08)
|
||||
# [1.0.0](https://github.com/angular-ui/bootstrap/compare/0.14.3...1.0.0) (2016-01-08)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
@@ -365,7 +735,7 @@ $scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadCo
|
||||
|
||||
|
||||
<a name="0.14.3"></a>
|
||||
# [0.14.3](https://github.com/angular-ui/bootstrap/compare/0.14.2...v0.14.3) (2015-10-23)
|
||||
# [0.14.3](https://github.com/angular-ui/bootstrap/compare/0.14.2...0.14.3) (2015-10-23)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
@@ -390,7 +760,7 @@ $scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadCo
|
||||
|
||||
|
||||
<a name="0.14.2"></a>
|
||||
# [0.14.2](https://github.com/angular-ui/bootstrap/compare/0.14.1...v0.14.2) (2015-10-14)
|
||||
# [0.14.2](https://github.com/angular-ui/bootstrap/compare/0.14.1...0.14.2) (2015-10-14)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
@@ -402,7 +772,7 @@ $scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadCo
|
||||
|
||||
|
||||
<a name="0.14.1"></a>
|
||||
# [0.14.1](https://github.com/angular-ui/bootstrap/compare/0.14.0...v0.14.1) (2015-10-11)
|
||||
# [0.14.1](https://github.com/angular-ui/bootstrap/compare/0.14.0...0.14.1) (2015-10-11)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
@@ -585,7 +955,7 @@ $scope.typeaheadContainer = angular.element(document.querySelector('#typeaheadCo
|
||||
|
||||
|
||||
<a name"0.13.3"></a>
|
||||
# 0.13.3 (2015-08-09)
|
||||
# [0.13.3](https://github.com/angular-ui/bootstrap/compare/0.13.2...0.13.3) (2015-08-09)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
@@ -672,7 +1042,7 @@ Closes #4080
|
||||
|
||||
|
||||
<a name"0.13.2"></a>
|
||||
# 0.13.2 (2015-08-02)
|
||||
# [0.13.2](https://github.com/angular-ui/bootstrap/compare/0.13.1...0.13.2) (2015-08-02)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
@@ -718,7 +1088,7 @@ Closes #4080
|
||||
|
||||
|
||||
<a name"0.13.1"></a>
|
||||
# 0.13.1 (2015-07-23)
|
||||
# [0.13.1](https://github.com/angular-ui/bootstrap/compare/0.13.0...0.13.1) (2015-07-23)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
@@ -779,7 +1149,7 @@ Closes #4080
|
||||
|
||||
|
||||
<a name="0.13.0"></a>
|
||||
# 0.13.0 (2015-05-02)
|
||||
# [0.13.0](https://github.com/angular-ui/bootstrap/compare/0.12.1...0.13.0) (2015-05-02)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
@@ -878,7 +1248,8 @@ Closes #4080
|
||||
* **transition:** deprecate transition module ([8a552443](https://github.com/angular-ui/bootstrap/commit/8a552443741d1e5b4b29d9da9c7e9990fa37886c), closes [#3497](https://github.com/angular-ui/bootstrap/issues/3497))
|
||||
|
||||
|
||||
# 0.12.1 (2015-02-20)
|
||||
<a name="0.12.1"></a>
|
||||
# [0.12.1](https://github.com/angular-ui/bootstrap/compare/0.12.0...0.12.1) (2015-02-20)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
@@ -886,7 +1257,7 @@ Closes #4080
|
||||
- incorrect position when text wraps ([5726e3ef](http://github.com/angular-ui/bootstrap/commit/5726e3ef))
|
||||
|
||||
<a name="0.12.0"></a>
|
||||
# 0.12.0 (2014-11-16)
|
||||
# [0.12.0](https://github.com/angular-ui/bootstrap/compare/0.11.2...0.12.0) (2014-11-16)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
@@ -939,11 +1310,13 @@ once* and can no longer be changed after initialization.
|
||||
```
|
||||
|
||||
|
||||
# 0.11.2 (2014-09-26)
|
||||
<a name="0.11.2"></a>
|
||||
# [0.11.2](https://github.com/angular-ui/bootstrap/compare/0.11.1...0.11.2) (2014-09-26)
|
||||
|
||||
Revert breaking change in **dropdown** ([1a998c4](http://github.com/angular-ui/bootstrap/commit/1a998c4))
|
||||
|
||||
# 0.11.1 (2014-09-26)
|
||||
<a name="0.11.1"></a>
|
||||
# [0.11.1](https://github.com/angular-ui/bootstrap/compare/0.11.0...0.11.1) (2014-09-26)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -977,7 +1350,8 @@ Revert breaking change in **dropdown** ([1a998c4](http://github.com/angular-ui/b
|
||||
- allow multiple line expression ([c7db0df4](http://github.com/angular-ui/bootstrap/commit/c7db0df4))
|
||||
- replace ng-if with ng-show in matches popup ([a0be450d](http://github.com/angular-ui/bootstrap/commit/a0be450d))
|
||||
|
||||
# 0.11.0 (2014-05-01)
|
||||
<a name="0.11.0"></a>
|
||||
# [0.11.0](https://github.com/angular-ui/bootstrap/compare/0.10.0...0.11.0) (2014-05-01)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -1155,7 +1529,8 @@ Revert breaking change in **dropdown** ([1a998c4](http://github.com/angular-ui/b
|
||||
<tabset type="{{navtype}}" ...></tabset>
|
||||
```
|
||||
|
||||
# 0.10.0 (2014-01-13)
|
||||
<a name="0.10.0"></a>
|
||||
# [0.10.0](https://github.com/angular-ui/bootstrap/compare/0.9.0...0.10.0) (2014-01-13)
|
||||
|
||||
_This release adds AngularJS 1.2 support_
|
||||
|
||||
@@ -1176,7 +1551,8 @@ _This release adds AngularJS 1.2 support_
|
||||
- **tooltip:**
|
||||
- performance and scope fixes ([c0df3201](http://github.com/angular-ui/bootstrap/commit/c0df3201))
|
||||
|
||||
# 0.9.0 (2013-12-28)
|
||||
<a name="0.9.0"></a>
|
||||
# [0.9.0](https://github.com/angular-ui/bootstrap/compare/0.8.0...0.9.0) (2013-12-28)
|
||||
|
||||
_This release adds Bootstrap3 support_
|
||||
|
||||
@@ -1219,7 +1595,8 @@ _This release adds Bootstrap3 support_
|
||||
- **tooltip:**
|
||||
- re-position tooltip after draw ([a99b3608](http://github.com/angular-ui/bootstrap/commit/a99b3608))
|
||||
|
||||
# 0.8.0 (2013-12-28)
|
||||
<a name="0.8.0"></a>
|
||||
# [0.8.0](https://github.com/angular-ui/bootstrap/compare/0.7.0...0.8.0) (2013-12-28)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -1286,7 +1663,8 @@ _This release adds Bootstrap3 support_
|
||||
<progress><bar ng-repeat="obj in objs" value="obj.var" type="{{obj.type}}"></bar></progress>
|
||||
```
|
||||
|
||||
# 0.7.0 (2013-11-22)
|
||||
<a name="0.7.0"></a>
|
||||
# [0.7.0](https://github.com/angular-ui/bootstrap/compare/0.6.0...0.7.0) (2013-11-22)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -1334,7 +1712,8 @@ _This release adds Bootstrap3 support_
|
||||
- evaluate matches source against a correct scope ([fd21214d](http://github.com/angular-ui/bootstrap/commit/fd21214d))
|
||||
- support IE8 ([0e9f9980](http://github.com/angular-ui/bootstrap/commit/0e9f9980))
|
||||
|
||||
# 0.6.0 (2013-09-08)
|
||||
<a name="0.6.0"></a>
|
||||
# [0.6.0](https://github.com/angular-ui/bootstrap/compare/0.6.0...0.7.0) (2013-09-08)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -1413,7 +1792,8 @@ Check the documentation for the `$modal` service to migrate from `$dialog`
|
||||
|
||||
The placment='mouse' is gone with no equivalent
|
||||
|
||||
# 0.5.0 (2013-08-04)
|
||||
<a name="0.5.0"></a>
|
||||
# [0.5.0](https://github.com/angular-ui/bootstrap/compare/0.4.0...0.5.0) (2013-08-04)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -1496,7 +1876,8 @@ The placment='mouse' is gone with no equivalent
|
||||
<pagination first-text="{{var1}}" ...></pagination>
|
||||
```
|
||||
|
||||
# 0.4.0 (2013-06-24)
|
||||
<a name="0.4.0"></a>
|
||||
# [0.4.0](https://github.com/angular-ui/bootstrap/compare/0.3.0...0.4.0) (2013-06-24)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -1614,8 +1995,8 @@ The placment='mouse' is gone with no equivalent
|
||||
</tabset>
|
||||
```
|
||||
|
||||
|
||||
# 0.3.0 (2013-04-30)
|
||||
<a name="0.3.0"></a>
|
||||
# [0.3.0](https://github.com/angular-ui/bootstrap/compare/0.2.0...0.3.0) (2013-04-30)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -1656,7 +2037,8 @@ The placment='mouse' is gone with no equivalent
|
||||
- correctly higlight matches if query contains regexp-special chars ([467afcd6](https://github.com/angular-ui/bootstrap/commit/467afcd6))
|
||||
- fix matches pop-up positioning issues ([74beecdb](https://github.com/angular-ui/bootstrap/commit/74beecdb))
|
||||
|
||||
# 0.2.0 (2013-03-03)
|
||||
<a name="0.2.0"></a>
|
||||
# [0.2.0](https://github.com/angular-ui/bootstrap/compare/0.1.0...0.2.0) (2013-03-03)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -1690,6 +2072,7 @@ The placment='mouse' is gone with no equivalent
|
||||
- **typeahead:**
|
||||
- update inputs value on mapping where label is not derived from the model ([a5f64de](https://github.com/angular-ui/bootstrap/commit/a5f64de))
|
||||
|
||||
<a name="0.1.0"></a>
|
||||
# 0.1.0 (2013-02-02)
|
||||
|
||||
_Very first, initial release_.
|
||||
|
||||
+4
-4
@@ -9,8 +9,8 @@ module.exports = function(grunt) {
|
||||
grunt.util.linefeed = '\n';
|
||||
|
||||
grunt.initConfig({
|
||||
ngversion: '1.5.0',
|
||||
bsversion: '3.3.6',
|
||||
ngversion: '1.6.1',
|
||||
bsversion: '3.3.7',
|
||||
modules: [],//to be filled in by build task
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
dist: 'dist',
|
||||
@@ -232,7 +232,7 @@ module.exports = function(grunt) {
|
||||
name: name,
|
||||
moduleName: enquote(`ui.bootstrap.${name}`),
|
||||
displayName: ucwords(breakup(name, ' ')),
|
||||
srcFiles: grunt.file.expand([`src/${name}/*.js`, `!src/${name}/index.js`]),
|
||||
srcFiles: grunt.file.expand([`src/${name}/*.js`, `!src/${name}/index.js`, `!src/${name}/index-nocss.js`]),
|
||||
cssFiles: grunt.file.expand(`src/${name}/*.css`),
|
||||
tplFiles: grunt.file.expand(`template/${name}/*.html`),
|
||||
tpljsFiles: grunt.file.expand(`template/${name}/*.html.js`),
|
||||
@@ -264,7 +264,7 @@ module.exports = function(grunt) {
|
||||
|
||||
function dependenciesForModule(name) {
|
||||
var deps = [];
|
||||
grunt.file.expand([`src/${name}/*.js`, `!src/${name}/index.js`])
|
||||
grunt.file.expand([`src/${name}/*.js`, `!src/${name}/index.js`, `!src/${name}/index-nocss.js`])
|
||||
.map(grunt.file.read)
|
||||
.forEach(function(contents) {
|
||||
//Strategy: find where module is declared,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2012-2016 the AngularUI Team, https://github.com/organizations/angular-ui/teams/291112
|
||||
Copyright (c) 2012-2017 the AngularUI Team, https://github.com/organizations/angular-ui/teams/291112
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
[](https://gitter.im/angular-ui/bootstrap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](http://travis-ci.org/angular-ui/bootstrap)
|
||||
[](https://david-dm.org/angular-ui/bootstrap#info=devDependencies)
|
||||
[](https://cdnjs.com/libraries/angular-ui-bootstrap/)
|
||||
|
||||
### Quick links
|
||||
- [Demo](#demo)
|
||||
@@ -13,7 +14,7 @@
|
||||
- [NuGet](#install-with-nuget)
|
||||
- [Custom](#custom-build)
|
||||
- [Manual](#manual-download)
|
||||
- [Webpack](#webpack)
|
||||
- [Webpack / JSPM](#webpack--jspm)
|
||||
- [Support](#support)
|
||||
- [FAQ](#faq)
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
@@ -27,16 +28,16 @@
|
||||
|
||||
# Demo
|
||||
|
||||
Do you want to see directives in action? Visit http://angular-ui.github.io/bootstrap/!
|
||||
Do you want to see directives in action? Visit https://angular-ui.github.io/bootstrap/!
|
||||
|
||||
# Angular 2
|
||||
|
||||
Are you interested in Angular 2? We are on our way! Check out [ng-bootstrap](https://github.com/ui-bootstrap/core).
|
||||
Are you interested in Angular 2? We are on our way! Check out [ng-bootstrap](https://github.com/ui-bootstrap/core).
|
||||
|
||||
# Installation
|
||||
|
||||
Installation is easy as UI Bootstrap has minimal dependencies - only the AngularJS and Twitter Bootstrap's CSS are required.
|
||||
*Notes:*
|
||||
*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.
|
||||
|
||||
@@ -73,7 +74,7 @@ PM> Install-Package Angular.UI.Bootstrap
|
||||
|
||||
#### Custom build
|
||||
|
||||
Head over to http://angular-ui.github.io/bootstrap/ and hit the *Custom build* button to create your own custom UI Bootstrap build, just the way you like it.
|
||||
Head over to https://angular-ui.github.io/bootstrap/ and hit the *Custom build* button to create your own custom UI Bootstrap build, just the way you like it.
|
||||
|
||||
#### Manual download
|
||||
|
||||
@@ -89,9 +90,10 @@ When you are done downloading all the dependencies and project files the only re
|
||||
angular.module('myModule', ['ui.bootstrap']);
|
||||
```
|
||||
|
||||
# Webpack
|
||||
# Webpack / JSPM
|
||||
|
||||
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:
|
||||
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
|
||||
import accordion from 'angular-ui-bootstrap/src/accordion';
|
||||
@@ -112,6 +114,31 @@ 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`.
|
||||
|
||||
If you would prefer not to load your css through your JavaScript file loader/bundler, you can choose to import the `index-nocss.js` file instead, which is available for the modules:
|
||||
* carousel
|
||||
* datepicker
|
||||
* datepickerPopup
|
||||
* dropdown
|
||||
* modal
|
||||
* popover
|
||||
* position
|
||||
* timepicker
|
||||
* tooltip
|
||||
* typeahead
|
||||
|
||||
The other modules, such as `accordion` in the example below, do not have CSS resources to load, so you should continue to import them as normal:
|
||||
|
||||
```js
|
||||
import accordion from 'angular-ui-bootstrap/src/accordion';
|
||||
import typeahead from 'angular-ui-bootstrap/src/typeahead/index-nocss.js';
|
||||
|
||||
angular.module('myModule', [accordion, typeahead]);
|
||||
```
|
||||
|
||||
# Versioning
|
||||
|
||||
Pre-2.0.0 does not follow a particular versioning system. 2.0.0 and onwards follows [semantic versioning](http://semver.org/). All release changes can be viewed on our [changelog](CHANGELOG.md).
|
||||
|
||||
# Support
|
||||
|
||||
## FAQ
|
||||
|
||||
@@ -28,6 +28,7 @@ module.exports = function(config) {
|
||||
// list of files to exclude
|
||||
exclude: [
|
||||
'src/**/index.js',
|
||||
'src/**/index-nocss.js',
|
||||
'src/**/docs/*'
|
||||
],
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* global FastClick, smoothScroll */
|
||||
angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'plunker', 'ngTouch', 'ngAnimate', 'ngSanitize'], function($httpProvider){
|
||||
FastClick.attach(document.body);
|
||||
if (!!window.FastClick) {
|
||||
FastClick.attach(document.body);
|
||||
}
|
||||
delete $httpProvider.defaults.headers.common['X-Requested-With'];
|
||||
}).run(['$location', function($location){
|
||||
//Allows us to navigate to the correct element on initialization
|
||||
|
||||
@@ -73,7 +73,7 @@ section {
|
||||
|
||||
}
|
||||
|
||||
.navbar .collapse {
|
||||
.navbar-fixed-top .collapse {
|
||||
border-top: 1px solid #e7e7e7;
|
||||
margin-left: -15px;
|
||||
margin-right: -15px;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 8.2 KiB |
@@ -4,7 +4,7 @@ angular.module('plunker', [])
|
||||
|
||||
return function (ngVersion, bsVersion, version, module, content) {
|
||||
|
||||
var form = angular.element('<form style="display: none;" method="post" action="http://plnkr.co/edit/?p=preview" target="_blank"></form>');
|
||||
var form = angular.element('<form style="display: none;" method="post" action="https://plnkr.co/edit/?p=preview" target="_blank"></form>');
|
||||
var addField = function (name, value) {
|
||||
var input = angular.element('<input type="hidden" name="' + name + '">');
|
||||
input.attr('value', value);
|
||||
@@ -17,6 +17,7 @@ angular.module('plunker', [])
|
||||
' <head>\n' +
|
||||
' <script src="//ajax.googleapis.com/ajax/libs/angularjs/'+ngVersion+'/angular.js"></script>\n' +
|
||||
' <script src="//ajax.googleapis.com/ajax/libs/angularjs/'+ngVersion+'/angular-animate.js"></script>\n' +
|
||||
' <script src="//ajax.googleapis.com/ajax/libs/angularjs/'+ngVersion+'/angular-sanitize.js"></script>\n' +
|
||||
' <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-'+version+'.js"></script>\n' +
|
||||
' <script src="example.js"></script>\n' +
|
||||
' <link href="//netdna.bootstrapcdn.com/bootstrap/'+bsVersion+'/css/bootstrap.min.css" rel="stylesheet">\n' +
|
||||
@@ -28,7 +29,7 @@ angular.module('plunker', [])
|
||||
};
|
||||
|
||||
var scriptContent = function(content) {
|
||||
return "angular.module('ui.bootstrap.demo', ['ngAnimate', 'ui.bootstrap']);" + "\n" + content;
|
||||
return "angular.module('ui.bootstrap.demo', ['ngAnimate', 'ngSanitize', 'ui.bootstrap']);" + "\n" + content;
|
||||
};
|
||||
|
||||
addField('description', 'http://angular-ui.github.io/bootstrap/');
|
||||
|
||||
+10
-2
@@ -147,6 +147,13 @@
|
||||
<div class="page-header">
|
||||
<h1>Getting started</h1>
|
||||
</div>
|
||||
<h3>Angular 2</h3>
|
||||
<p>
|
||||
For Angular 2 support, check out
|
||||
<a href="https://ng-bootstrap.github.io" target="_blank">
|
||||
ng-bootstrap
|
||||
</a>, created by the UI Bootstrap team.
|
||||
</p>
|
||||
<h3>Dependencies</h3>
|
||||
<p>
|
||||
This repository contains a set of <strong>native AngularJS directives</strong> based on
|
||||
@@ -173,8 +180,9 @@
|
||||
<p>Alternativelly, if you are only interested in a subset of directives, you can
|
||||
<a ng-click="showBuildModal()">create your own build</a>.
|
||||
</p>
|
||||
<p>Whichever method you choose the good news that the overall size of a download is very small:
|
||||
<76kB for all directives (~20kB with gzip compression!)</p>
|
||||
<p>Whichever method you choose the good news that the overall size of a download is fairly small:
|
||||
122K minified for all directives with templates and 98K without (~31kB with gzip
|
||||
compression, with templates, and 28K gzipped without)</p>
|
||||
<h3>Installation</h3>
|
||||
<p>As soon as you've got all the files downloaded and included in your page you just need to declare
|
||||
a dependency on the <code>ui.bootstrap</code> <a href="http://docs.angularjs.org/guide/module">module</a>:<br>
|
||||
|
||||
+21
-11
@@ -1,8 +1,15 @@
|
||||
{
|
||||
"author": "https://github.com/angular-ui/bootstrap/graphs/contributors",
|
||||
"name": "angular-ui-bootstrap",
|
||||
"version": "1.2.4",
|
||||
"version": "2.5.1-SNAPSHOT",
|
||||
"description": "Native AngularJS (Angular) directives for Bootstrap",
|
||||
"homepage": "http://angular-ui.github.io/bootstrap/",
|
||||
"keywords": [
|
||||
"angularjs",
|
||||
"angular",
|
||||
"bootstrap",
|
||||
"ui"
|
||||
],
|
||||
"dependencies": {},
|
||||
"directories": {
|
||||
"lib": "src/"
|
||||
@@ -15,6 +22,7 @@
|
||||
],
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"demo": "grunt after-test && static dist -a 0.0.0.0 -H '{\"Cache-Control\": \"no-cache, must-revalidate\"}'",
|
||||
"test": "grunt"
|
||||
},
|
||||
"repository": {
|
||||
@@ -22,21 +30,22 @@
|
||||
"url": "https://github.com/angular-ui/bootstrap.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular": "1.5.0",
|
||||
"angular-mocks": "1.5.0",
|
||||
"angular-sanitize": "1.5.0",
|
||||
"angular": "1.6.1",
|
||||
"angular-mocks": "1.6.1",
|
||||
"angular-sanitize": "1.6.1",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-contrib-concat": "^0.5.1",
|
||||
"grunt-contrib-copy": "^0.8.0",
|
||||
"grunt-contrib-uglify": "^0.11.0",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"grunt-conventional-changelog": "^5.0.0",
|
||||
"grunt-cli": "^1.2.0",
|
||||
"grunt-contrib-concat": "^1.0.0",
|
||||
"grunt-contrib-copy": "^1.0.0",
|
||||
"grunt-contrib-uglify": "^1.0.1",
|
||||
"grunt-contrib-watch": "^1.0.0",
|
||||
"grunt-conventional-changelog": "^6.1.0",
|
||||
"grunt-ddescribe-iit": "0.0.6",
|
||||
"grunt-eslint": "^17.3.1",
|
||||
"grunt-html2js": "^0.3.0",
|
||||
"grunt-karma": "^0.12.0",
|
||||
"jasmine-core": "^2.2.0",
|
||||
"karma": "0.13.5",
|
||||
"karma": "0.13.22",
|
||||
"karma-chrome-launcher": "^0.2.0",
|
||||
"karma-coverage": "^0.5.0",
|
||||
"karma-firefox-launcher": "^0.1.4",
|
||||
@@ -44,8 +53,9 @@
|
||||
"load-grunt-tasks": "^3.3.0",
|
||||
"lodash": "^4.1.0",
|
||||
"marked": "^0.3.5",
|
||||
"node-static": "^0.7.8",
|
||||
"semver": "^5.0.1",
|
||||
"shelljs": "^0.5.1"
|
||||
"shelljs": "^0.6.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
||||
angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse', 'ui.bootstrap.tabindex'])
|
||||
|
||||
.constant('uibAccordionConfig', {
|
||||
closeOthers: true
|
||||
@@ -58,12 +58,13 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
||||
return {
|
||||
require: '^uibAccordion', // We need this directive to be inside an accordion
|
||||
transclude: true, // It transcludes the contents of the directive into the template
|
||||
replace: true, // The element containing the directive will be replaced with the template
|
||||
restrict: 'A',
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
|
||||
},
|
||||
scope: {
|
||||
heading: '@', // Interpolate the heading attribute onto this scope
|
||||
panelClass: '@?', // Ditto with panelClass
|
||||
isOpen: '=?',
|
||||
isDisabled: '=?'
|
||||
},
|
||||
@@ -73,6 +74,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
||||
};
|
||||
},
|
||||
link: function(scope, element, attrs, accordionCtrl) {
|
||||
element.addClass('panel');
|
||||
accordionCtrl.addGroup(scope);
|
||||
|
||||
scope.openClass = attrs.openClass || 'panel-open';
|
||||
@@ -123,11 +125,21 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
||||
link: function(scope, element, attrs, controller) {
|
||||
scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
|
||||
if (heading) {
|
||||
var elem = angular.element(element[0].querySelector('[uib-accordion-header]'));
|
||||
var elem = angular.element(element[0].querySelector(getHeaderSelectors()));
|
||||
elem.html('');
|
||||
elem.append(heading);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function getHeaderSelectors() {
|
||||
return 'uib-accordion-header,' +
|
||||
'data-uib-accordion-header,' +
|
||||
'x-uib-accordion-header,' +
|
||||
'uib\\:accordion-header,' +
|
||||
'[uib-accordion-header],' +
|
||||
'[data-uib-accordion-header],' +
|
||||
'[x-uib-accordion-header]';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<div ng-controller="AccordionDemoCtrl">
|
||||
<script type="text/ng-template" id="group-template.html">
|
||||
<div class="panel {{panelClass || 'panel-default'}}">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title" style="color:#fa39c3">
|
||||
<a href tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" uib-accordion-transclude="heading"><span
|
||||
ng-class="{'text-muted': isDisabled}">{{heading}}</span></a>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-collapse collapse" uib-collapse="!isOpen">
|
||||
<div class="panel-body" style="text-align: right" ng-transclude></div>
|
||||
</div>
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title" style="color:#fa39c3">
|
||||
<a href tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" uib-accordion-transclude="heading">
|
||||
<span uib-accordion-header ng-class="{'text-muted': isDisabled}">
|
||||
{{heading}}
|
||||
</span>
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-collapse collapse" uib-collapse="!isOpen">
|
||||
<div class="panel-body" style="text-align: right" ng-transclude></div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
@@ -25,29 +26,35 @@
|
||||
</label>
|
||||
</div>
|
||||
<uib-accordion close-others="oneAtATime">
|
||||
<uib-accordion-group heading="Static Header, initially expanded" is-open="status.isFirstOpen" is-disabled="status.isFirstDisabled">
|
||||
<div uib-accordion-group class="panel-default" heading="Static Header, initially expanded" is-open="status.isFirstOpen" is-disabled="status.isFirstDisabled">
|
||||
This content is straight in the template.
|
||||
</uib-accordion-group>
|
||||
<uib-accordion-group heading="{{group.title}}" ng-repeat="group in groups">
|
||||
</div>
|
||||
<div uib-accordion-group class="panel-default" heading="{{group.title}}" ng-repeat="group in groups">
|
||||
{{group.content}}
|
||||
</uib-accordion-group>
|
||||
<uib-accordion-group heading="Dynamic Body Content">
|
||||
</div>
|
||||
<div uib-accordion-group class="panel-default" heading="Dynamic Body Content">
|
||||
<p>The body of the uib-accordion group grows to fit the contents</p>
|
||||
<button type="button" class="btn btn-default btn-sm" ng-click="addItem()">Add Item</button>
|
||||
<div ng-repeat="item in items">{{item}}</div>
|
||||
</uib-accordion-group>
|
||||
<uib-accordion-group heading="Custom template" template-url="group-template.html">
|
||||
</div>
|
||||
<div uib-accordion-group class="panel-default" heading="Custom template" template-url="group-template.html">
|
||||
Hello
|
||||
</uib-accordion-group>
|
||||
<uib-accordion-group heading="Delete account" panel-class="panel-danger">
|
||||
</div>
|
||||
<div uib-accordion-group class="panel-default" is-open="status.isCustomHeaderOpen" template-url="group-template.html">
|
||||
<uib-accordion-heading>
|
||||
Custom template with custom header template <i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': status.isCustomHeaderOpen, 'glyphicon-chevron-right': !status.isCustomHeaderOpen}"></i>
|
||||
</uib-accordion-heading>
|
||||
World
|
||||
</div>
|
||||
<div uib-accordion-group class="panel-danger" heading="Delete account">
|
||||
<p>Please, to delete your account, click the button below</p>
|
||||
<button class="btn btn-danger">Delete</button>
|
||||
</uib-accordion-group>
|
||||
<uib-accordion-group is-open="status.open">
|
||||
</div>
|
||||
<div uib-accordion-group class="panel-default" is-open="status.open">
|
||||
<uib-accordion-heading>
|
||||
I can have markup, too! <i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': status.open, 'glyphicon-chevron-right': !status.open}"></i>
|
||||
</uib-accordion-heading>
|
||||
This is just some content to illustrate fancy headings.
|
||||
</uib-accordion-group>
|
||||
</div>
|
||||
</uib-accordion>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ angular.module('ui.bootstrap.demo').controller('AccordionDemoCtrl', function ($s
|
||||
};
|
||||
|
||||
$scope.status = {
|
||||
isCustomHeaderOpen: false,
|
||||
isFirstOpen: true,
|
||||
isFirstDisabled: false
|
||||
};
|
||||
|
||||
@@ -32,10 +32,6 @@ The body of each accordion group is transcluded into the body of the collapsible
|
||||
_(Default: `false`)_ -
|
||||
Whether accordion group is open or closed.
|
||||
|
||||
* `panel-class`
|
||||
_(Default: `panel-default`)_ -
|
||||
Add ability to use Bootstrap's contextual panel classes (panel-primary, panel-success, panel-info, etc...) or your own. This must be a string.
|
||||
|
||||
* `template-url`
|
||||
_(Default: `uib/template/accordion/accordion-group.html`)_ -
|
||||
Add the ability to override the template used on the component.
|
||||
@@ -48,6 +44,6 @@ If you're using a custom template for the `uib-accordion-group`, you'll need to
|
||||
|
||||
### Known issues
|
||||
|
||||
To use clickable elements within the accordion, you have override the accordion-group template to use div elements instead of anchor elements, and add `cursor: pointer` in your CSS. This is due to browsers interpreting anchor elements as the target of any click event, which triggers routing when certain elements such as buttons are nested inside the anchor element.
|
||||
To use clickable elements within the accordion, you have to override the accordion-group template to use div elements instead of anchor elements, and add `cursor: pointer` in your CSS. This is due to browsers interpreting anchor elements as the target of any click event, which triggers routing when certain elements such as buttons are nested inside the anchor element.
|
||||
|
||||
If custom classes on the accordion-group element are desired, one needs to either modify the template to remove the `ng-class` usage in the accordion-group template and use ng-class on the accordion-group element (not recommended), or use an interpolated expression in the class attribute, i.e. `<uib-accordion-group class="{{customClass()}}"></uib-accordion-group>`.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
require('../collapse');
|
||||
require('../tabindex');
|
||||
require('../../template/accordion/accordion-group.html.js');
|
||||
require('../../template/accordion/accordion.html.js');
|
||||
require('./accordion');
|
||||
|
||||
@@ -176,12 +176,12 @@ describe('uib-accordion', function() {
|
||||
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<uib-accordion-group heading="title 1" template-url="foo/bar.html"></uib-accordion-group>' +
|
||||
'<div uib-accordion-group heading="title 1" template-url="foo/bar.html"></div>' +
|
||||
'</uib-accordion>';
|
||||
|
||||
element = $compile(tpl)(scope);
|
||||
scope.$digest();
|
||||
expect(element.find('[template-url]').html()).toBe('baz');
|
||||
expect(element.find('[template-url]').html()).toBe('<div>baz</div>');
|
||||
}));
|
||||
|
||||
describe('with static panels', function() {
|
||||
@@ -189,8 +189,8 @@ describe('uib-accordion', function() {
|
||||
spyOn(Math, 'random').and.returnValue(0.1);
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<uib-accordion-group heading="title 1">Content 1</uib-accordion-group>' +
|
||||
'<uib-accordion-group heading="title 2">Content 2</uib-accordion-group>' +
|
||||
'<div uib-accordion-group heading="title 1">Content 1</div>' +
|
||||
'<div uib-accordion-group heading="title 2">Content 2</div>' +
|
||||
'</uib-accordion>';
|
||||
element = angular.element(tpl);
|
||||
$compile(element)(scope);
|
||||
@@ -288,8 +288,8 @@ describe('uib-accordion', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<uib-accordion-group heading="title 1" open-class="custom-open-class">Content 1</uib-accordion-group>' +
|
||||
'<uib-accordion-group heading="title 2" open-class="custom-open-class">Content 2</uib-accordion-group>' +
|
||||
'<div uib-accordion-group heading="title 1" open-class="custom-open-class">Content 1</div>' +
|
||||
'<div uib-accordion-group heading="title 2" open-class="custom-open-class">Content 2</div>' +
|
||||
'</uib-accordion>';
|
||||
element = angular.element(tpl);
|
||||
$compile(element)(scope);
|
||||
@@ -318,7 +318,7 @@ describe('uib-accordion', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}">{{group.content}}</uib-accordion-group>' +
|
||||
'<div uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}">{{group.content}}</div>' +
|
||||
'</uib-accordion>';
|
||||
element = angular.element(tpl);
|
||||
model = [
|
||||
@@ -363,8 +363,8 @@ describe('uib-accordion', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<uib-accordion-group heading="title 1" is-open="open.first">Content 1</uib-accordion-group>' +
|
||||
'<uib-accordion-group heading="title 2" is-open="open.second">Content 2</uib-accordion-group>' +
|
||||
'<div uib-accordion-group heading="title 1" is-open="open.first">Content 1</div>' +
|
||||
'<div uib-accordion-group heading="title 2" is-open="open.second">Content 2</div>' +
|
||||
'</uib-accordion>';
|
||||
element = angular.element(tpl);
|
||||
scope.open = { first: false, second: true };
|
||||
@@ -393,8 +393,8 @@ describe('uib-accordion', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<uib-accordion-group heading="title 1" is-open="open1"><div ng-repeat="item in items">{{item}}</div></uib-accordion-group>' +
|
||||
'<uib-accordion-group heading="title 2" is-open="open2">Static content</uib-accordion-group>' +
|
||||
'<div uib-accordion-group heading="title 1" is-open="open1"><div ng-repeat="item in items">{{item}}</div></div>' +
|
||||
'<div uib-accordion-group heading="title 2" is-open="open2">Static content</div>' +
|
||||
'</uib-accordion>';
|
||||
element = angular.element(tpl);
|
||||
scope.items = ['Item 1', 'Item 2', 'Item 3'];
|
||||
@@ -421,7 +421,7 @@ describe('uib-accordion', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}" is-open="group.open">{{group.content}}</uib-accordion-group>' +
|
||||
'<div uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}" is-open="group.open">{{group.content}}</div>' +
|
||||
'</uib-accordion>';
|
||||
element = angular.element(tpl);
|
||||
scope.groups = [
|
||||
@@ -456,7 +456,7 @@ describe('uib-accordion', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}" is-open="group.open" class="testClass">{{group.content}}</uib-accordion-group>' +
|
||||
'<div uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}" is-open="group.open" class="testClass">{{group.content}}</div>' +
|
||||
'</uib-accordion>';
|
||||
element = angular.element(tpl);
|
||||
scope.groups = [
|
||||
@@ -480,7 +480,7 @@ describe('uib-accordion', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<uib-accordion-group heading="title 1" is-disabled="disabled">Content 1</uib-accordion-group>' +
|
||||
'<div uib-accordion-group heading="title 1" is-disabled="disabled">Content 1</div>' +
|
||||
'</uib-accordion>';
|
||||
element = angular.element(tpl);
|
||||
scope.disabled = true;
|
||||
@@ -519,10 +519,10 @@ describe('uib-accordion', function() {
|
||||
function isDisabledStyleCheck() {
|
||||
var tpl =
|
||||
'<uib-accordion ng-init="a = [1,2,3]">' +
|
||||
'<uib-accordion-group heading="I get overridden" is-disabled="true">' +
|
||||
'<div uib-accordion-group heading="I get overridden" is-disabled="true">' +
|
||||
'<uib-accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </uib-accordion-heading>' +
|
||||
'Body' +
|
||||
'</uib-accordion-group>' +
|
||||
'</div>' +
|
||||
'</uib-accordion>';
|
||||
scope.disabled = true;
|
||||
element = $compile(tpl)(scope);
|
||||
@@ -536,10 +536,10 @@ describe('uib-accordion', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion ng-init="a = [1,2,3]">' +
|
||||
'<uib-accordion-group heading="I get overridden">' +
|
||||
'<div uib-accordion-group heading="I get overridden">' +
|
||||
'<uib-accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </uib-accordion-heading>' +
|
||||
'Body' +
|
||||
'</uib-accordion-group>' +
|
||||
'</div>' +
|
||||
'</uib-accordion>';
|
||||
element = $compile(tpl)(scope);
|
||||
scope.$digest();
|
||||
@@ -551,7 +551,7 @@ describe('uib-accordion', function() {
|
||||
});
|
||||
|
||||
it('attaches the same scope to the transcluded heading and body', function() {
|
||||
expect(findGroupLink(0).find('span.ng-scope').scope().$id).toBe(findGroupBody(0).find('span').scope().$id);
|
||||
expect(findGroupLink(0).scope().$id).toBe(findGroupBody(0).scope().$id);
|
||||
});
|
||||
|
||||
it('should wrap the transcluded content in a span', function() {
|
||||
@@ -565,10 +565,10 @@ describe('uib-accordion', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion ng-init="a = [1,2,3]">' +
|
||||
'<uib-accordion-group heading="I get overridden">' +
|
||||
'<div uib-accordion-group heading="I get overridden">' +
|
||||
'<div uib-accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </div>' +
|
||||
'Body' +
|
||||
'</uib-accordion-group>' +
|
||||
'</div>' +
|
||||
'</uib-accordion>';
|
||||
element = $compile(tpl)(scope);
|
||||
scope.$digest();
|
||||
@@ -580,7 +580,7 @@ describe('uib-accordion', function() {
|
||||
});
|
||||
|
||||
it('attaches the same scope to the transcluded heading and body', function() {
|
||||
expect(findGroupLink(0).find('span.ng-scope').scope().$id).toBe(findGroupBody(0).find('span').scope().$id);
|
||||
expect(findGroupLink(0).scope().$id).toBe(findGroupBody(0).scope().$id);
|
||||
});
|
||||
|
||||
it('should have disabled styling when is-disabled is true', isDisabledStyleCheck);
|
||||
@@ -588,7 +588,7 @@ describe('uib-accordion', function() {
|
||||
|
||||
describe('uib-accordion-heading, with repeating uib-accordion-groups', function() {
|
||||
it('should clone the uib-accordion-heading for each group', function() {
|
||||
element = $compile('<uib-accordion><uib-accordion-group ng-repeat="x in [1,2,3]"><uib-accordion-heading>{{x}}</uib-accordion-heading></uib-accordion-group></uib-accordion>')(scope);
|
||||
element = $compile('<uib-accordion><div uib-accordion-group ng-repeat="x in [1,2,3]"><uib-accordion-heading>{{x}}</uib-accordion-heading></div></uib-accordion>')(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
expect(groups.length).toBe(3);
|
||||
@@ -600,7 +600,7 @@ describe('uib-accordion', function() {
|
||||
|
||||
describe('uib-accordion-heading attribute, with repeating uib-accordion-groups', function() {
|
||||
it('should clone the uib-accordion-heading for each group', function() {
|
||||
element = $compile('<uib-accordion><uib-accordion-group ng-repeat="x in [1,2,3]"><div uib-accordion-heading>{{x}}</div></uib-accordion-group></uib-accordion>')(scope);
|
||||
element = $compile('<uib-accordion><div uib-accordion-group ng-repeat="x in [1,2,3]"><div uib-accordion-heading>{{x}}</div></div></uib-accordion>')(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
expect(groups.length).toBe(3);
|
||||
@@ -610,26 +610,15 @@ describe('uib-accordion', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('uib-accordion group panel class - #3968', function() {
|
||||
it('should use the default value when panel class is falsy', function() {
|
||||
element = $compile('<uib-accordion><uib-accordion-group heading="Heading">Content</uib-accordion-group></uib-accordion>')(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
expect(groups.eq(0)).toHaveClass('panel-default');
|
||||
describe('uib-accordion-heading attribute, with custom template', function() {
|
||||
it('should transclude heading to a template using data-uib-accordion-header', inject(function($templateCache) {
|
||||
$templateCache.put('foo/bar.html', '<div class="panel"><a uib-accordion-transclude="heading" class="accordion-toggle"><span data-uib-accordion-header></span></a><div ng-transclude></div></div>');
|
||||
|
||||
element = $compile('<uib-accordion><uib-accordion-group heading="Heading" panel-class="">Content</uib-accordion-group></uib-accordion>')(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
expect(groups.eq(0)).toHaveClass('panel-default');
|
||||
});
|
||||
|
||||
it('should use the specified value when not falsy', function() {
|
||||
element = $compile('<uib-accordion><uib-accordion-group heading="Heading" panel-class="custom-class">Content</uib-accordion-group></uib-accordion>')(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
expect(groups.eq(0)).toHaveClass('custom-class');
|
||||
expect(groups.eq(0)).not.toHaveClass('panel-default');
|
||||
});
|
||||
element = $compile('<uib-accordion><div uib-accordion-group template-url="foo/bar.html"><uib-accordion-heading>baz</uib-accordion-heading></div></uib-accordion>')(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
expect(findGroupLink(0).text()).toBe('baz');
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+7
-3
@@ -1,7 +1,12 @@
|
||||
angular.module('ui.bootstrap.alert', [])
|
||||
|
||||
.controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) {
|
||||
.controller('UibAlertController', ['$scope', '$element', '$attrs', '$interpolate', '$timeout', function($scope, $element, $attrs, $interpolate, $timeout) {
|
||||
$scope.closeable = !!$attrs.close;
|
||||
$element.addClass('alert');
|
||||
$attrs.$set('role', 'alert');
|
||||
if ($scope.closeable) {
|
||||
$element.addClass('alert-dismissible');
|
||||
}
|
||||
|
||||
var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
|
||||
$interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
|
||||
@@ -17,13 +22,12 @@ angular.module('ui.bootstrap.alert', [])
|
||||
return {
|
||||
controller: 'UibAlertController',
|
||||
controllerAs: 'alert',
|
||||
restrict: 'A',
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/alert/alert.html';
|
||||
},
|
||||
transclude: true,
|
||||
replace: true,
|
||||
scope: {
|
||||
type: '@',
|
||||
close: '&'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<div ng-controller="AlertDemoCtrl">
|
||||
<script type="text/ng-template" id="alert.html">
|
||||
<div class="alert" style="background-color:#fa39c3;color:white" role="alert">
|
||||
<div ng-transclude></div>
|
||||
</div>
|
||||
<div ng-transclude></div>
|
||||
</script>
|
||||
|
||||
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
|
||||
<uib-alert template-url="alert.html">A happy alert!</uib-alert>
|
||||
<div uib-alert ng-repeat="alert in alerts" ng-class="'alert-' + (alert.type || 'warning')" close="closeAlert($index)">{{alert.msg}}</div>
|
||||
<div uib-alert template-url="alert.html" style="background-color:#fa39c3;color:white">A happy alert!</div>
|
||||
<button type="button" class='btn btn-default' ng-click="addAlert()">Add Alert</button>
|
||||
</div>
|
||||
|
||||
@@ -5,15 +5,11 @@ This directive can be used both to generate alerts from static and dynamic model
|
||||
* `close()`
|
||||
<small class="badge">$</small> -
|
||||
A callback function that gets fired when an `alert` is closed. If the attribute exists, a close button is displayed as well.
|
||||
|
||||
|
||||
* `dismiss-on-timeout`
|
||||
_(Default: `none`)_ -
|
||||
Takes the number of milliseconds that specify the timeout duration, after which the alert will be closed. This attribute requires the presence of the `close` attribute.
|
||||
|
||||
|
||||
* `template-url`
|
||||
_(Default: `uib/template/alert/alert.html`)_ -
|
||||
Add the ability to override the template used in the component.
|
||||
|
||||
* `type`
|
||||
_(Default: `warning`)_ -
|
||||
Defines the type of the alert. Go to [bootstrap page](http://getbootstrap.com/components/#alerts) to see the type of alerts available.
|
||||
|
||||
@@ -12,9 +12,10 @@ describe('uib-alert', function() {
|
||||
|
||||
element = angular.element(
|
||||
'<div>' +
|
||||
'<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}"' +
|
||||
'<div uib-alert ng-repeat="alert in alerts" ' +
|
||||
'ng-class="\'alert-\' + (alert.type || \'warning\')" ' +
|
||||
'close="removeAlert($index)">{{alert.msg}}' +
|
||||
'</uib-alert>' +
|
||||
'</div>' +
|
||||
'</div>');
|
||||
|
||||
scope.alerts = [
|
||||
@@ -35,13 +36,13 @@ describe('uib-alert', function() {
|
||||
}
|
||||
|
||||
function findContent(index) {
|
||||
return element.find('div[ng-transclude] span').eq(index);
|
||||
return element.find('div[ng-transclude]').eq(index);
|
||||
}
|
||||
|
||||
it('should expose the controller to the view', function() {
|
||||
$templateCache.put('uib/template/alert/alert.html', '<div>{{alert.text}}</div>');
|
||||
|
||||
element = $compile('<uib-alert></uib-alert>')(scope);
|
||||
element = $compile('<div uib-alert></div>')(scope);
|
||||
scope.$digest();
|
||||
|
||||
var ctrl = element.controller('uib-alert');
|
||||
@@ -50,16 +51,16 @@ describe('uib-alert', function() {
|
||||
ctrl.text = 'foo';
|
||||
scope.$digest();
|
||||
|
||||
expect(element.html()).toBe('foo');
|
||||
expect(element.html()).toBe('<div class="ng-binding">foo</div>');
|
||||
});
|
||||
|
||||
it('should support custom templates', function() {
|
||||
$templateCache.put('foo/bar.html', '<div>baz</div>');
|
||||
|
||||
element = $compile('<uib-alert template-url="foo/bar.html"></uib-alert>')(scope);
|
||||
element = $compile('<div uib-alert template-url="foo/bar.html"></div>')(scope);
|
||||
scope.$digest();
|
||||
|
||||
expect(element.html()).toBe('baz');
|
||||
expect(element.html()).toBe('<div>baz</div>');
|
||||
});
|
||||
|
||||
it('should generate alerts using ng-repeat', function() {
|
||||
@@ -67,23 +68,6 @@ describe('uib-alert', function() {
|
||||
expect(alerts.length).toEqual(3);
|
||||
});
|
||||
|
||||
it('should use correct classes for different alert types', function() {
|
||||
var alerts = createAlerts();
|
||||
expect(alerts.eq(0)).toHaveClass('alert-success');
|
||||
expect(alerts.eq(1)).toHaveClass('alert-error');
|
||||
expect(alerts.eq(2)).toHaveClass('alert-warning');
|
||||
});
|
||||
|
||||
it('should respect alert type binding', function() {
|
||||
var alerts = createAlerts();
|
||||
expect(alerts.eq(0)).toHaveClass('alert-success');
|
||||
|
||||
scope.alerts[0].type = 'error';
|
||||
scope.$digest();
|
||||
|
||||
expect(alerts.eq(0)).toHaveClass('alert-error');
|
||||
});
|
||||
|
||||
it('should show the alert content', function() {
|
||||
var alerts = createAlerts();
|
||||
|
||||
@@ -115,22 +99,15 @@ describe('uib-alert', function() {
|
||||
});
|
||||
|
||||
it('should not show close button and have the dismissible class if no close callback specified', function() {
|
||||
element = $compile('<uib-alert>No close</uib-alert>')(scope);
|
||||
element = $compile('<div uib-alert>No close</div>')(scope);
|
||||
scope.$digest();
|
||||
expect(findCloseButton(0)).toBeHidden();
|
||||
expect(element).not.toHaveClass('alert-dismissible');
|
||||
});
|
||||
|
||||
it('should be possible to add additional classes for alert', function() {
|
||||
var element = $compile('<uib-alert class="alert-block" type="info">Default alert!</uib-alert>')(scope);
|
||||
scope.$digest();
|
||||
expect(element).toHaveClass('alert-block');
|
||||
expect(element).toHaveClass('alert-info');
|
||||
});
|
||||
|
||||
it('should close automatically if dismiss-on-timeout is defined on the element', function() {
|
||||
scope.removeAlert = jasmine.createSpy();
|
||||
$compile('<uib-alert close="removeAlert()" dismiss-on-timeout="500">Default alert!</uib-alert>')(scope);
|
||||
$compile('<div uib-alert close="removeAlert()" dismiss-on-timeout="500">Default alert!</div>')(scope);
|
||||
scope.$digest();
|
||||
|
||||
$timeout.flush();
|
||||
@@ -140,7 +117,7 @@ describe('uib-alert', function() {
|
||||
it('should not close immediately with a dynamic dismiss-on-timeout', function() {
|
||||
scope.removeAlert = jasmine.createSpy();
|
||||
scope.dismissTime = 500;
|
||||
$compile('<uib-alert close="removeAlert()" dismiss-on-timeout="{{dismissTime}}">Default alert!</uib-alert>')(scope);
|
||||
$compile('<div uib-alert close="removeAlert()" dismiss-on-timeout="{{dismissTime}}">Default alert!</div>')(scope);
|
||||
scope.$digest();
|
||||
|
||||
$timeout.flush(100);
|
||||
|
||||
@@ -44,3 +44,7 @@ With the buttons directive, we can make a group of buttons behave like a set of
|
||||
* `toggleEvent`
|
||||
_(Default: `click`)_ -
|
||||
Event used to toggle the buttons.
|
||||
|
||||
### Known issues
|
||||
|
||||
To use tooltips or popovers on elements within a `btn-group`, set the tooltip/popover `appendToBody` option to `true`. This is due to Bootstrap CSS styling. See [here](http://getbootstrap.com/components/#btn-groups) for more information.
|
||||
|
||||
+24
-30
@@ -5,9 +5,10 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
slides = self.slides = $scope.slides = [],
|
||||
SLIDE_DIRECTION = 'uib-slideDirection',
|
||||
currentIndex = $scope.active,
|
||||
currentInterval, isPlaying, bufferedTransitions = [];
|
||||
currentInterval, isPlaying;
|
||||
|
||||
var destroyed = false;
|
||||
$element.addClass('carousel');
|
||||
|
||||
self.addSlide = function(slide, element) {
|
||||
slides.push({
|
||||
@@ -15,7 +16,7 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
element: element
|
||||
});
|
||||
slides.sort(function(a, b) {
|
||||
return +a.slide.index > +b.slide.index;
|
||||
return +a.slide.index - +b.slide.index;
|
||||
});
|
||||
//if this is the first slide or the slide is set to active, select it
|
||||
if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) {
|
||||
@@ -66,11 +67,6 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
self.removeSlide = function(slide) {
|
||||
var index = findSlideIndex(slide);
|
||||
|
||||
var bufferedIndex = bufferedTransitions.indexOf(slides[index]);
|
||||
if (bufferedIndex !== -1) {
|
||||
bufferedTransitions.splice(bufferedIndex, 1);
|
||||
}
|
||||
|
||||
//get the index of the slide inside the carousel
|
||||
slides.splice(index, 1);
|
||||
if (slides.length > 0 && currentIndex === index) {
|
||||
@@ -94,7 +90,6 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
if (slides.length === 0) {
|
||||
currentIndex = null;
|
||||
$scope.active = null;
|
||||
clearBufferedTransitions();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -109,8 +104,6 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
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]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,6 +116,14 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
return $scope.active === slide.slide.index;
|
||||
};
|
||||
|
||||
$scope.isPrevDisabled = function() {
|
||||
return $scope.active === 0 && $scope.noWrap();
|
||||
};
|
||||
|
||||
$scope.isNextDisabled = function() {
|
||||
return $scope.active === slides.length - 1 && $scope.noWrap();
|
||||
};
|
||||
|
||||
$scope.pause = function() {
|
||||
if (!$scope.noPause) {
|
||||
isPlaying = false;
|
||||
@@ -137,6 +138,9 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
}
|
||||
};
|
||||
|
||||
$element.on('mouseenter', $scope.pause);
|
||||
$element.on('mouseleave', $scope.play);
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
destroyed = true;
|
||||
resetTimer();
|
||||
@@ -161,19 +165,13 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
|
||||
var slide = slides[index];
|
||||
if (slide) {
|
||||
currentIndex = index;
|
||||
setActive(index);
|
||||
self.select(slides[index]);
|
||||
currentIndex = index;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function clearBufferedTransitions() {
|
||||
while (bufferedTransitions.length) {
|
||||
bufferedTransitions.shift();
|
||||
}
|
||||
}
|
||||
|
||||
function getSlideByIndex(index) {
|
||||
for (var i = 0, l = slides.length; i < l; ++i) {
|
||||
if (slides[i].index === index) {
|
||||
@@ -209,14 +207,6 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
if (phase === 'close') {
|
||||
$scope.$currentTransition = null;
|
||||
$animate.off('addClass', element);
|
||||
if (bufferedTransitions.length) {
|
||||
var nextSlide = bufferedTransitions.pop().slide;
|
||||
var nextIndex = nextSlide.index;
|
||||
var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
|
||||
clearBufferedTransitions();
|
||||
|
||||
goNext(nextSlide, nextIndex, nextDirection);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -247,7 +237,6 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
function resetTransition(slides) {
|
||||
if (!slides.length) {
|
||||
$scope.$currentTransition = null;
|
||||
clearBufferedTransitions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,9 +261,9 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
.directive('uibCarousel', function() {
|
||||
return {
|
||||
transclude: true,
|
||||
replace: true,
|
||||
controller: 'UibCarouselController',
|
||||
controllerAs: 'carousel',
|
||||
restrict: 'A',
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/carousel/carousel.html';
|
||||
},
|
||||
@@ -288,11 +277,11 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
};
|
||||
})
|
||||
|
||||
.directive('uibSlide', function() {
|
||||
.directive('uibSlide', ['$animate', function($animate) {
|
||||
return {
|
||||
require: '^uibCarousel',
|
||||
restrict: 'A',
|
||||
transclude: true,
|
||||
replace: true,
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/carousel/slide.html';
|
||||
},
|
||||
@@ -301,14 +290,19 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
index: '=?'
|
||||
},
|
||||
link: function (scope, element, attrs, carouselCtrl) {
|
||||
element.addClass('item');
|
||||
carouselCtrl.addSlide(scope, element);
|
||||
//when the scope is destroyed then remove the slide from the current slides array
|
||||
scope.$on('$destroy', function() {
|
||||
carouselCtrl.removeSlide(scope);
|
||||
});
|
||||
|
||||
scope.$watch('active', function(active) {
|
||||
$animate[active ? 'addClass' : 'removeClass'](element, 'active');
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
}])
|
||||
|
||||
.animation('.item', ['$animateCss',
|
||||
function($animateCss) {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<div ng-controller="CarouselDemoCtrl">
|
||||
<div style="height: 305px">
|
||||
<uib-carousel active="active" interval="myInterval" no-wrap="noWrapSlides">
|
||||
<uib-slide ng-repeat="slide in slides track by slide.id" index="slide.id">
|
||||
<div uib-carousel active="active" interval="myInterval" no-wrap="noWrapSlides">
|
||||
<div uib-slide ng-repeat="slide in slides track by slide.id" index="slide.id">
|
||||
<img ng-src="{{slide.image}}" style="margin:auto;">
|
||||
<div class="carousel-caption">
|
||||
<h4>Slide {{slide.id}}</h4>
|
||||
<p>{{slide.text}}</p>
|
||||
</div>
|
||||
</uib-slide>
|
||||
</uib-carousel>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
|
||||
@@ -8,7 +8,7 @@ angular.module('ui.bootstrap.demo').controller('CarouselDemoCtrl', function ($sc
|
||||
$scope.addSlide = function() {
|
||||
var newWidth = 600 + slides.length + 1;
|
||||
slides.push({
|
||||
image: 'http://lorempixel.com/' + newWidth + '/300',
|
||||
image: '//unsplash.it/' + newWidth + '/300',
|
||||
text: ['Nice image','Awesome photograph','That is so cool','I love that'][slides.length % 4],
|
||||
id: currIndex++
|
||||
});
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
require('../../template/carousel/carousel.html.js');
|
||||
require('../../template/carousel/slide.html.js');
|
||||
require('./carousel');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.carousel';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.carousel', 'uib/template/carousel/carousel.html', 'uib/template/carousel/slide.html']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
+1
-10
@@ -1,11 +1,2 @@
|
||||
require('../../template/carousel/carousel.html.js');
|
||||
require('../../template/carousel/slide.html.js');
|
||||
require('./carousel');
|
||||
|
||||
require('./carousel.css');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.carousel';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.carousel', 'uib/template/carousel/carousel.html', 'uib/template/carousel/slide.html']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
module.exports = require('./index-nocss.js');
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
describe('carousel', function() {
|
||||
beforeEach(module('ui.bootstrap.carousel', function($compileProvider, $provide) {
|
||||
angular.forEach(['ngSwipeLeft', 'ngSwipeRight'], makeMock);
|
||||
function makeMock(name) {
|
||||
$provide.value(name + 'Directive', []); //remove existing directive if it exists
|
||||
$compileProvider.directive(name, function() {
|
||||
return function(scope, element, attr) {
|
||||
element.on(name, function() {
|
||||
scope.$apply(attr[name]);
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
}));
|
||||
beforeEach(module('ui.bootstrap.carousel'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
beforeEach(module('uib/template/carousel/carousel.html', 'uib/template/carousel/slide.html'));
|
||||
|
||||
@@ -36,11 +24,11 @@ describe('carousel', function() {
|
||||
{content: 'three', index: 2}
|
||||
];
|
||||
elm = $compile(
|
||||
'<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">' +
|
||||
'<div uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<div uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
|
||||
'{{slide.content}}' +
|
||||
'</uib-slide>' +
|
||||
'</uib-carousel>'
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
scope.interval = 5000;
|
||||
scope.nopause = undefined;
|
||||
@@ -60,19 +48,19 @@ describe('carousel', function() {
|
||||
it('should allow overriding of the carousel template', function() {
|
||||
$templateCache.put('foo/bar.html', '<div>foo</div>');
|
||||
|
||||
elm = $compile('<uib-carousel template-url="foo/bar.html"></uib-carousel>')(scope);
|
||||
elm = $compile('<div uib-carousel template-url="foo/bar.html"></div>')(scope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elm.html()).toBe('foo');
|
||||
expect(elm.html()).toBe('<div>foo</div>');
|
||||
});
|
||||
|
||||
it('should allow overriding of the slide template', function() {
|
||||
$templateCache.put('foo/bar.html', '<div class="slide">bar</div>');
|
||||
|
||||
elm = $compile(
|
||||
'<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<uib-slide template-url="foo/bar.html"></uib-slide>' +
|
||||
'</uib-carousel>'
|
||||
'<div uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<div uib-slide template-url="foo/bar.html"></div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
$rootScope.$digest();
|
||||
|
||||
@@ -101,11 +89,11 @@ describe('carousel', function() {
|
||||
|
||||
it('should stop cycling slides forward when noWrap is truthy', function () {
|
||||
elm = $compile(
|
||||
'<uib-carousel active="active" interval="interval" no-wrap="noWrap">' +
|
||||
'<uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
|
||||
'<div uib-carousel active="active" interval="interval" no-wrap="noWrap">' +
|
||||
'<div uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
|
||||
'{{slide.content}}' +
|
||||
'</uib-slide>' +
|
||||
'</uib-carousel>'
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
|
||||
scope.noWrap = true;
|
||||
@@ -124,11 +112,11 @@ describe('carousel', function() {
|
||||
|
||||
it('should stop cycling slides backward when noWrap is truthy', function () {
|
||||
elm = $compile(
|
||||
'<uib-carousel active="active" interval="interval" no-wrap="noWrap">' +
|
||||
'<uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
|
||||
'<div uib-carousel active="active" interval="interval" no-wrap="noWrap">' +
|
||||
'<div uib-slide ng-repeat="slide in slides track by slide.index" index="slide.index">' +
|
||||
'{{slide.content}}' +
|
||||
'</uib-slide>' +
|
||||
'</uib-carousel>'
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
|
||||
scope.noWrap = true;
|
||||
@@ -147,11 +135,11 @@ describe('carousel', function() {
|
||||
scope.slides = [{active:false,content:'one'}];
|
||||
scope.$apply();
|
||||
elm = $compile(
|
||||
'<uib-carousel active="active" interval="interval" no-transition="true">' +
|
||||
'<uib-slide ng-repeat="slide in slides" index="$index">' +
|
||||
'<div uib-carousel active="active" interval="interval" no-transition="true">' +
|
||||
'<div uib-slide ng-repeat="slide in slides" index="$index">' +
|
||||
'{{slide.content}}' +
|
||||
'</uib-slide>' +
|
||||
'</uib-carousel>'
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
var indicators = elm.find('ol.carousel-indicators > li');
|
||||
expect(indicators.length).toBe(0);
|
||||
@@ -163,6 +151,38 @@ describe('carousel', function() {
|
||||
expect(navPrev.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should disable prev button when slide index is 0 and noWrap is truthy', function() {
|
||||
scope.$apply();
|
||||
|
||||
var $scope = elm.isolateScope();
|
||||
$scope.noWrap = function() {return true;};
|
||||
|
||||
$scope.isPrevDisabled();
|
||||
scope.$apply();
|
||||
|
||||
var navPrev = elm.find('a.left');
|
||||
expect(navPrev.hasClass('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('should disable next button when last slide is active and noWrap is truthy', function() {
|
||||
scope.slides = [
|
||||
{content: 'one', index: 0},
|
||||
{content: 'two', index: 1}
|
||||
];
|
||||
|
||||
scope.$apply();
|
||||
|
||||
var $scope = elm.isolateScope();
|
||||
$scope.noWrap = function() {return true;};
|
||||
$scope.next();
|
||||
|
||||
$scope.isNextDisabled();
|
||||
scope.$apply();
|
||||
|
||||
var navNext = elm.find('a.right');
|
||||
expect(navNext.hasClass('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('should show navigation when there are 3 slides', function () {
|
||||
var indicators = elm.find('ol.carousel-indicators > li');
|
||||
expect(indicators.length).not.toBe(0);
|
||||
@@ -196,20 +216,6 @@ describe('carousel', function() {
|
||||
testSlideActive(0);
|
||||
});
|
||||
|
||||
describe('swiping', function() {
|
||||
it('should go next on swipeLeft', function() {
|
||||
testSlideActive(0);
|
||||
elm.triggerHandler('ngSwipeLeft');
|
||||
testSlideActive(1);
|
||||
});
|
||||
|
||||
it('should go prev on swipeRight', function() {
|
||||
testSlideActive(0);
|
||||
elm.triggerHandler('ngSwipeRight');
|
||||
testSlideActive(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should select a slide when clicking on slide indicators', function () {
|
||||
var indicators = elm.find('ol.carousel-indicators > li');
|
||||
indicators.eq(1).click();
|
||||
@@ -237,7 +243,7 @@ describe('carousel', function() {
|
||||
});
|
||||
|
||||
it('should bind the content to slides', function() {
|
||||
var contents = elm.find('div.item');
|
||||
var contents = elm.find('div.item [ng-transclude]');
|
||||
|
||||
expect(contents.length).toBe(3);
|
||||
expect(contents.eq(0).text()).toBe('one');
|
||||
@@ -311,7 +317,7 @@ describe('carousel', function() {
|
||||
{content:'new3', index: 6}
|
||||
];
|
||||
scope.$apply();
|
||||
var contents = elm.find('div.item');
|
||||
var contents = elm.find('div.item [ng-transclude]');
|
||||
expect(contents.length).toBe(3);
|
||||
expect(contents.eq(0).text()).toBe('new1');
|
||||
expect(contents.eq(1).text()).toBe('new2');
|
||||
@@ -409,11 +415,11 @@ describe('carousel', function() {
|
||||
{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">' +
|
||||
'<div uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<div uib-slide ng-repeat="slide in slides | orderBy: \'id\' track by slide.id" index="slide.id">' +
|
||||
'{{slide.content}}' +
|
||||
'</uib-slide>' +
|
||||
'</uib-carousel>'
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
scope.$apply();
|
||||
});
|
||||
@@ -433,7 +439,7 @@ describe('carousel', function() {
|
||||
scope.slides[1].id = 2;
|
||||
scope.slides[2].id = 1;
|
||||
scope.$apply();
|
||||
var contents = elm.find('div.item');
|
||||
var contents = elm.find('div.item [ng-transclude]');
|
||||
expect(contents.length).toBe(3);
|
||||
expect(contents.eq(0).text()).toBe('three');
|
||||
expect(contents.eq(1).text()).toBe('two');
|
||||
@@ -459,7 +465,7 @@ describe('carousel', function() {
|
||||
scope.slides[2].id = 4;
|
||||
scope.slides.push({content:'four', id: 5});
|
||||
scope.$apply();
|
||||
var contents = elm.find('div.item');
|
||||
var contents = elm.find('div.item [ng-transclude]');
|
||||
expect(contents.length).toBe(4);
|
||||
expect(contents.eq(0).text()).toBe('two');
|
||||
expect(contents.eq(1).text()).toBe('one');
|
||||
@@ -471,7 +477,7 @@ describe('carousel', function() {
|
||||
testSlideActive(1);
|
||||
scope.slides.splice(1, 1);
|
||||
scope.$apply();
|
||||
var contents = elm.find('div.item');
|
||||
var contents = elm.find('div.item [ng-transclude]');
|
||||
expect(contents.length).toBe(2);
|
||||
expect(contents.eq(0).text()).toBe('three');
|
||||
expect(contents.eq(1).text()).toBe('one');
|
||||
@@ -551,7 +557,7 @@ describe('carousel', function() {
|
||||
$templateCache.put('uib/template/carousel/carousel.html', '<div>{{carousel.text}}</div>');
|
||||
|
||||
var scope = $rootScope.$new();
|
||||
var elm = $compile('<uib-carousel interval="bar" no-transition="false" no-pause="true"></uib-carousel>')(scope);
|
||||
var elm = $compile('<div uib-carousel interval="bar" no-transition="false" no-pause="true"></div>')(scope);
|
||||
$rootScope.$digest();
|
||||
|
||||
var ctrl = elm.controller('uibCarousel');
|
||||
@@ -561,7 +567,7 @@ describe('carousel', function() {
|
||||
ctrl.text = 'foo';
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elm.html()).toBe('foo');
|
||||
expect(elm.html()).toBe('<div class="ng-binding">foo</div>');
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -573,11 +579,11 @@ describe('carousel', function() {
|
||||
{active:false,content:'three'}
|
||||
];
|
||||
var elm = $compile(
|
||||
'<uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<uib-slide ng-repeat="slide in slides" index="$index" actual="slide">' +
|
||||
'<div uib-carousel active="active" interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<div uib-slide ng-repeat="slide in slides" index="$index" actual="slide">' +
|
||||
'{{slide.content}}' +
|
||||
'</uib-slide>' +
|
||||
'</uib-carousel>'
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
$rootScope.$digest();
|
||||
|
||||
|
||||
+53
-21
@@ -5,16 +5,42 @@ angular.module('ui.bootstrap.collapse', [])
|
||||
return {
|
||||
link: function(scope, element, attrs) {
|
||||
var expandingExpr = $parse(attrs.expanding),
|
||||
expandedExpr = $parse(attrs.expanded),
|
||||
collapsingExpr = $parse(attrs.collapsing),
|
||||
collapsedExpr = $parse(attrs.collapsed);
|
||||
expandedExpr = $parse(attrs.expanded),
|
||||
collapsingExpr = $parse(attrs.collapsing),
|
||||
collapsedExpr = $parse(attrs.collapsed),
|
||||
horizontal = false,
|
||||
css = {},
|
||||
cssTo = {};
|
||||
|
||||
if (!scope.$eval(attrs.uibCollapse)) {
|
||||
element.addClass('in')
|
||||
.addClass('collapse')
|
||||
.attr('aria-expanded', true)
|
||||
.attr('aria-hidden', false)
|
||||
.css({height: 'auto'});
|
||||
init();
|
||||
|
||||
function init() {
|
||||
horizontal = !!('horizontal' in attrs);
|
||||
if (horizontal) {
|
||||
css = {
|
||||
width: ''
|
||||
};
|
||||
cssTo = {width: '0'};
|
||||
} else {
|
||||
css = {
|
||||
height: ''
|
||||
};
|
||||
cssTo = {height: '0'};
|
||||
}
|
||||
if (!scope.$eval(attrs.uibCollapse)) {
|
||||
element.addClass('in')
|
||||
.addClass('collapse')
|
||||
.attr('aria-expanded', true)
|
||||
.attr('aria-hidden', false)
|
||||
.css(css);
|
||||
}
|
||||
}
|
||||
|
||||
function getScrollFromElement(element) {
|
||||
if (horizontal) {
|
||||
return {width: element.scrollWidth + 'px'};
|
||||
}
|
||||
return {height: element.scrollHeight + 'px'};
|
||||
}
|
||||
|
||||
function expand() {
|
||||
@@ -33,20 +59,26 @@ angular.module('ui.bootstrap.collapse', [])
|
||||
$animateCss(element, {
|
||||
addClass: 'in',
|
||||
easing: 'ease',
|
||||
to: { height: element[0].scrollHeight + 'px' }
|
||||
css: {
|
||||
overflow: 'hidden'
|
||||
},
|
||||
to: getScrollFromElement(element[0])
|
||||
}).start()['finally'](expandDone);
|
||||
} else {
|
||||
$animate.addClass(element, 'in', {
|
||||
to: { height: element[0].scrollHeight + 'px' }
|
||||
css: {
|
||||
overflow: 'hidden'
|
||||
},
|
||||
to: getScrollFromElement(element[0])
|
||||
}).then(expandDone);
|
||||
}
|
||||
});
|
||||
}, angular.noop);
|
||||
}
|
||||
|
||||
function expandDone() {
|
||||
element.removeClass('collapsing')
|
||||
.addClass('collapse')
|
||||
.css({height: 'auto'});
|
||||
.css(css);
|
||||
expandedExpr(scope);
|
||||
}
|
||||
|
||||
@@ -58,10 +90,10 @@ angular.module('ui.bootstrap.collapse', [])
|
||||
$q.resolve(collapsingExpr(scope))
|
||||
.then(function() {
|
||||
element
|
||||
// IMPORTANT: The height must be set before adding "collapsing" class.
|
||||
// Otherwise, the browser attempts to animate from height 0 (in
|
||||
// collapsing class) to the given height here.
|
||||
.css({height: element[0].scrollHeight + 'px'})
|
||||
// IMPORTANT: The width must be set before adding "collapsing" class.
|
||||
// Otherwise, the browser attempts to animate from width 0 (in
|
||||
// collapsing class) to the given width here.
|
||||
.css(getScrollFromElement(element[0]))
|
||||
// initially all panel collapse have the collapse class, this removal
|
||||
// prevents the animation from jumping to collapsed state
|
||||
.removeClass('collapse')
|
||||
@@ -72,18 +104,18 @@ angular.module('ui.bootstrap.collapse', [])
|
||||
if ($animateCss) {
|
||||
$animateCss(element, {
|
||||
removeClass: 'in',
|
||||
to: {height: '0'}
|
||||
to: cssTo
|
||||
}).start()['finally'](collapseDone);
|
||||
} else {
|
||||
$animate.removeClass(element, 'in', {
|
||||
to: {height: '0'}
|
||||
to: cssTo
|
||||
}).then(collapseDone);
|
||||
}
|
||||
});
|
||||
}, angular.noop);
|
||||
}
|
||||
|
||||
function collapseDone() {
|
||||
element.css({height: '0'}); // Required so that collapse works when animation is disabled
|
||||
element.css(cssTo); // Required so that collapse works when animation is disabled
|
||||
element.removeClass('collapsing')
|
||||
.addClass('collapse');
|
||||
collapsedExpr(scope);
|
||||
|
||||
@@ -1,7 +1,40 @@
|
||||
<style>
|
||||
.horizontal-collapse {
|
||||
height: 70px;
|
||||
}
|
||||
.navbar-collapse.in {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
</style>
|
||||
<div ng-controller="CollapseDemoCtrl">
|
||||
<button type="button" class="btn btn-default" ng-click="isCollapsed = !isCollapsed">Toggle collapse</button>
|
||||
<p>Resize window to less than 768 pixels to display mobile menu toggle button.</p>
|
||||
<nav class="navbar navbar-default" role="navigation">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" ng-click="isNavCollapsed = !isNavCollapsed">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#">A menu</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" uib-collapse="isNavCollapsed">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="#">Link 1</a></li>
|
||||
<li><a href="#">Link 2</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<hr>
|
||||
<button type="button" class="btn btn-default" ng-click="isCollapsed = !isCollapsed">Toggle collapse Vertically</button>
|
||||
<hr>
|
||||
<div uib-collapse="isCollapsed">
|
||||
<div class="well well-lg">Some content</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-default" ng-click="isCollapsedHorizontal = !isCollapsedHorizontal">Toggle collapse Horizontally</button>
|
||||
<hr>
|
||||
<div class="horizontal-collapse" uib-collapse="isCollapsedHorizontal" horizontal>
|
||||
<div class="well well-lg">Some content</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
angular.module('ui.bootstrap.demo').controller('CollapseDemoCtrl', function ($scope) {
|
||||
$scope.isNavCollapsed = true;
|
||||
$scope.isCollapsed = false;
|
||||
$scope.isCollapsedHorizontal = false;
|
||||
});
|
||||
|
||||
@@ -28,3 +28,10 @@
|
||||
_(Default: `false`)_ -
|
||||
Whether the element should be collapsed or not.
|
||||
|
||||
* `horizontal`
|
||||
<small class="badge">$</small> -
|
||||
An optional attribute that permit to collapse horizontally.
|
||||
|
||||
### Known Issues
|
||||
|
||||
When using the `horizontal` attribute with this directive, CSS can reflow as the collapse element goes from `0px` to its desired end width, which can result in height changes. This can cause animations to not appear to run. The best way around this is to set a fixed height via CSS on the horizontal collapse element so that this situation does not occur, and so the animation can run as expected.
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
describe('collapse directive', function() {
|
||||
var elementH, compileFnH, scope, $compile, $animate, $q;
|
||||
|
||||
beforeEach(module('ui.bootstrap.collapse'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
beforeEach(inject(function(_$rootScope_, _$compile_, _$animate_, _$q_) {
|
||||
scope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
$animate = _$animate_;
|
||||
$q = _$q_;
|
||||
}));
|
||||
|
||||
beforeEach(function() {
|
||||
elementH = angular.element(
|
||||
'<div uib-collapse="isCollapsed" '
|
||||
+ 'expanding="expanding()" '
|
||||
+ 'expanded="expanded()" '
|
||||
+ 'collapsing="collapsing()" '
|
||||
+ 'collapsed="collapsed()" '
|
||||
+ 'horizontal>'
|
||||
+ 'Some Content</div>');
|
||||
compileFnH = $compile(elementH);
|
||||
angular.element(document.body).append(elementH);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
elementH.remove();
|
||||
});
|
||||
|
||||
function initCallbacks() {
|
||||
scope.collapsing = jasmine.createSpy('scope.collapsing');
|
||||
scope.collapsed = jasmine.createSpy('scope.collapsed');
|
||||
scope.expanding = jasmine.createSpy('scope.expanding');
|
||||
scope.expanded = jasmine.createSpy('scope.expanded');
|
||||
}
|
||||
|
||||
function assertCallbacks(expected) {
|
||||
['collapsing', 'collapsed', 'expanding', 'expanded'].forEach(function(cbName) {
|
||||
if (expected[cbName]) {
|
||||
expect(scope[cbName]).toHaveBeenCalled();
|
||||
} else {
|
||||
expect(scope[cbName]).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it('should be hidden on initialization if isCollapsed = true', function() {
|
||||
initCallbacks();
|
||||
scope.isCollapsed = true;
|
||||
compileFnH(scope);
|
||||
scope.$digest();
|
||||
expect(elementH.width()).toBe(0);
|
||||
assertCallbacks({ collapsed: true });
|
||||
});
|
||||
|
||||
it('should not trigger any animation on initialization if isCollapsed = true', function() {
|
||||
var wrapperFn = function() {
|
||||
$animate.flush();
|
||||
};
|
||||
|
||||
scope.isCollapsed = true;
|
||||
compileFnH(scope);
|
||||
scope.$digest();
|
||||
|
||||
expect(wrapperFn).toThrowError(/No pending animations ready to be closed or flushed/);
|
||||
});
|
||||
|
||||
it('should collapse if isCollapsed = true on subsequent use', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFnH(scope);
|
||||
scope.$digest();
|
||||
initCallbacks();
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(elementH.width()).toBe(0);
|
||||
assertCallbacks({ collapsing: true, collapsed: true });
|
||||
});
|
||||
|
||||
it('should show after toggled from collapsed', function() {
|
||||
initCallbacks();
|
||||
scope.isCollapsed = true;
|
||||
compileFnH(scope);
|
||||
scope.$digest();
|
||||
expect(elementH.width()).toBe(0);
|
||||
assertCallbacks({ collapsed: true });
|
||||
scope.collapsed.calls.reset();
|
||||
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(elementH.width()).not.toBe(0);
|
||||
assertCallbacks({ expanding: true, expanded: true });
|
||||
});
|
||||
|
||||
it('should not trigger any animation on initialization if isCollapsed = false', function() {
|
||||
var wrapperFn = function() {
|
||||
$animate.flush();
|
||||
};
|
||||
|
||||
scope.isCollapsed = false;
|
||||
compileFnH(scope);
|
||||
scope.$digest();
|
||||
|
||||
expect(wrapperFn).toThrowError(/No pending animations ready to be closed or flushed/);
|
||||
});
|
||||
|
||||
it('should expand if isCollapsed = false on subsequent use', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFnH(scope);
|
||||
scope.$digest();
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
initCallbacks();
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(elementH.width()).not.toBe(0);
|
||||
assertCallbacks({ expanding: true, expanded: true });
|
||||
});
|
||||
|
||||
it('should collapse if isCollapsed = true on subsequent uses', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFnH(scope);
|
||||
scope.$digest();
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
initCallbacks();
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(elementH.width()).toBe(0);
|
||||
assertCallbacks({ collapsing: true, collapsed: true });
|
||||
});
|
||||
|
||||
it('should change aria-expanded attribute', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFnH(scope);
|
||||
scope.$digest();
|
||||
expect(elementH.attr('aria-expanded')).toBe('true');
|
||||
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(elementH.attr('aria-expanded')).toBe('false');
|
||||
});
|
||||
|
||||
it('should change aria-hidden attribute', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFnH(scope);
|
||||
scope.$digest();
|
||||
expect(elementH.attr('aria-hidden')).toBe('false');
|
||||
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(elementH.attr('aria-hidden')).toBe('true');
|
||||
});
|
||||
|
||||
describe('expanding callback returning a promise', function() {
|
||||
var defer, collapsedWidth;
|
||||
|
||||
beforeEach(function() {
|
||||
defer = $q.defer();
|
||||
|
||||
scope.isCollapsed = true;
|
||||
scope.expanding = function() {
|
||||
return defer.promise;
|
||||
};
|
||||
compileFnH(scope);
|
||||
scope.$digest();
|
||||
collapsedWidth = elementH.width();
|
||||
|
||||
// set flag to expand ...
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
|
||||
// ... shouldn't expand yet ...
|
||||
expect(elementH.attr('aria-expanded')).not.toBe('true');
|
||||
expect(elementH.width()).toBe(collapsedWidth);
|
||||
});
|
||||
|
||||
it('should wait for it to resolve before animating', function() {
|
||||
defer.resolve();
|
||||
|
||||
// should now expand
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
|
||||
expect(elementH.attr('aria-expanded')).toBe('true');
|
||||
expect(elementH.width()).toBeGreaterThan(collapsedWidth);
|
||||
});
|
||||
|
||||
it('should not animate if it rejects', function() {
|
||||
defer.reject();
|
||||
|
||||
// should NOT expand
|
||||
scope.$digest();
|
||||
|
||||
expect(elementH.attr('aria-expanded')).not.toBe('true');
|
||||
expect(elementH.width()).toBe(collapsedWidth);
|
||||
});
|
||||
});
|
||||
|
||||
describe('collapsing callback returning a promise', function() {
|
||||
var defer, expandedWidth;
|
||||
|
||||
beforeEach(function() {
|
||||
defer = $q.defer();
|
||||
scope.isCollapsed = false;
|
||||
scope.collapsing = function() {
|
||||
return defer.promise;
|
||||
};
|
||||
compileFnH(scope);
|
||||
scope.$digest();
|
||||
|
||||
expandedWidth = elementH.width();
|
||||
|
||||
// set flag to collapse ...
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
|
||||
// ... but it shouldn't collapse yet ...
|
||||
expect(elementH.attr('aria-expanded')).not.toBe('false');
|
||||
expect(elementH.width()).toBe(expandedWidth);
|
||||
});
|
||||
|
||||
it('should wait for it to resolve before animating', function() {
|
||||
defer.resolve();
|
||||
|
||||
// should now collapse
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
|
||||
expect(elementH.attr('aria-expanded')).toBe('false');
|
||||
expect(elementH.width()).toBeLessThan(expandedWidth);
|
||||
});
|
||||
|
||||
it('should not animate if it rejects', function() {
|
||||
defer.reject();
|
||||
|
||||
// should NOT collapse
|
||||
scope.$digest();
|
||||
|
||||
expect(elementH.attr('aria-expanded')).not.toBe('false');
|
||||
expect(elementH.width()).toBe(expandedWidth);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
+103
-26
@@ -1,6 +1,6 @@
|
||||
angular.module('ui.bootstrap.dateparser', [])
|
||||
|
||||
.service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', function($log, $locale, dateFilter, orderByFilter) {
|
||||
.service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', 'filterFilter', function($log, $locale, dateFilter, orderByFilter, filterFilter) {
|
||||
// Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
|
||||
var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
|
||||
|
||||
@@ -27,7 +27,7 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
{
|
||||
key: 'yy',
|
||||
regex: '\\d{2}',
|
||||
apply: function(value) { this.year = +value + 2000; },
|
||||
apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; },
|
||||
formatter: function(date) {
|
||||
var _date = new Date();
|
||||
_date.setFullYear(Math.abs(date.getFullYear()));
|
||||
@@ -230,11 +230,37 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
formatter: function(date) { return dateFilter(date, 'G'); }
|
||||
}
|
||||
];
|
||||
|
||||
if (angular.version.major >= 1 && angular.version.minor > 4) {
|
||||
formatCodeToRegex.push({
|
||||
key: 'LLLL',
|
||||
regex: $locale.DATETIME_FORMATS.STANDALONEMONTH.join('|'),
|
||||
apply: function(value) { this.month = $locale.DATETIME_FORMATS.STANDALONEMONTH.indexOf(value); },
|
||||
formatter: function(date) { return dateFilter(date, 'LLLL'); }
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.init();
|
||||
|
||||
function createParser(format, func) {
|
||||
function getFormatCodeToRegex(key) {
|
||||
return filterFilter(formatCodeToRegex, {key: key}, true)[0];
|
||||
}
|
||||
|
||||
this.getParser = function (key) {
|
||||
var f = getFormatCodeToRegex(key);
|
||||
return f && f.apply || null;
|
||||
};
|
||||
|
||||
this.overrideParser = function (key, parser) {
|
||||
var f = getFormatCodeToRegex(key);
|
||||
if (f && angular.isFunction(parser)) {
|
||||
this.parsers = {};
|
||||
f.apply = parser;
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
function createParser(format) {
|
||||
var map = [], regex = format.split('');
|
||||
|
||||
// check for literal values
|
||||
@@ -283,7 +309,7 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
map.push({
|
||||
index: index,
|
||||
key: data.key,
|
||||
apply: data[func],
|
||||
apply: data.apply,
|
||||
matcher: data.regex
|
||||
});
|
||||
}
|
||||
@@ -295,6 +321,70 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
};
|
||||
}
|
||||
|
||||
function createFormatter(format) {
|
||||
var formatters = [];
|
||||
var i = 0;
|
||||
var formatter, literalIdx;
|
||||
while (i < format.length) {
|
||||
if (angular.isNumber(literalIdx)) {
|
||||
if (format.charAt(i) === '\'') {
|
||||
if (i + 1 >= format.length || format.charAt(i + 1) !== '\'') {
|
||||
formatters.push(constructLiteralFormatter(format, literalIdx, i));
|
||||
literalIdx = null;
|
||||
}
|
||||
} else if (i === format.length) {
|
||||
while (literalIdx < format.length) {
|
||||
formatter = constructFormatterFromIdx(format, literalIdx);
|
||||
formatters.push(formatter);
|
||||
literalIdx = formatter.endIdx;
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (format.charAt(i) === '\'') {
|
||||
literalIdx = i;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
formatter = constructFormatterFromIdx(format, i);
|
||||
|
||||
formatters.push(formatter.parser);
|
||||
i = formatter.endIdx;
|
||||
}
|
||||
|
||||
return formatters;
|
||||
}
|
||||
|
||||
function constructLiteralFormatter(format, literalIdx, endIdx) {
|
||||
return function() {
|
||||
return format.substr(literalIdx + 1, endIdx - literalIdx - 1);
|
||||
};
|
||||
}
|
||||
|
||||
function constructFormatterFromIdx(format, i) {
|
||||
var currentPosStr = format.substr(i);
|
||||
for (var j = 0; j < formatCodeToRegex.length; j++) {
|
||||
if (new RegExp('^' + formatCodeToRegex[j].key).test(currentPosStr)) {
|
||||
var data = formatCodeToRegex[j];
|
||||
return {
|
||||
endIdx: i + data.key.length,
|
||||
parser: data.formatter
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
endIdx: i + 1,
|
||||
parser: function() {
|
||||
return currentPosStr.charAt(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.filter = function(date, format) {
|
||||
if (!angular.isDate(date) || isNaN(date) || !format) {
|
||||
return '';
|
||||
@@ -307,28 +397,13 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
}
|
||||
|
||||
if (!this.formatters[format]) {
|
||||
this.formatters[format] = createParser(format, 'formatter');
|
||||
this.formatters[format] = createFormatter(format);
|
||||
}
|
||||
|
||||
var parser = this.formatters[format],
|
||||
map = parser.map;
|
||||
var formatters = this.formatters[format];
|
||||
|
||||
var _format = format;
|
||||
|
||||
return map.reduce(function(str, mapper, i) {
|
||||
var match = _format.match(new RegExp('(.*)' + mapper.key));
|
||||
if (match && angular.isString(match[1])) {
|
||||
str += match[1];
|
||||
_format = _format.replace(match[1] + mapper.key, '');
|
||||
}
|
||||
|
||||
var endStr = i === map.length - 1 ? _format : '';
|
||||
|
||||
if (mapper.apply) {
|
||||
return str + mapper.apply.call(null, date) + endStr;
|
||||
}
|
||||
|
||||
return str + endStr;
|
||||
return formatters.reduce(function(str, formatter) {
|
||||
return str + formatter(date);
|
||||
}, '');
|
||||
};
|
||||
|
||||
@@ -442,8 +517,9 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
|
||||
}
|
||||
|
||||
//https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207
|
||||
//https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207
|
||||
function timezoneToOffset(timezone, fallback) {
|
||||
timezone = timezone.replace(/:/g, '');
|
||||
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
|
||||
return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
|
||||
}
|
||||
@@ -456,7 +532,8 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
|
||||
function convertTimezoneToLocal(date, timezone, reverse) {
|
||||
reverse = reverse ? -1 : 1;
|
||||
var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
|
||||
return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
|
||||
var dateTimezoneOffset = date.getTimezoneOffset();
|
||||
var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
|
||||
return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
|
||||
}
|
||||
}]);
|
||||
|
||||
@@ -58,6 +58,9 @@ Certain format codes support i18n. Check this [guide](https://docs.angularjs.org
|
||||
_(Example: `3` or `03`)_ -
|
||||
Parses a numeric month, but allowing an optional leading zero
|
||||
|
||||
* `LLLL`
|
||||
_(Example: `February`, i18n support)_ - Stand-alone month in year (January-December). Requires Angular version 1.5.1 or higher.
|
||||
|
||||
* `dd`
|
||||
_(Example: `05`, Leading 0)_ -
|
||||
Parses a numeric day.
|
||||
|
||||
@@ -84,6 +84,16 @@ describe('date parser', function() {
|
||||
expectFilter(new Date(2011, 4, 2, 0), 'dd-M!-yy', '02-05-11');
|
||||
expectFilter(oldDate, 'yyyy/M!/dd', '0001/03/06');
|
||||
});
|
||||
|
||||
it('should work correctly for `LLLL`', function() {
|
||||
expectFilter(new Date(2013, 7, 24, 0), 'LLLL/dd/yyyy', 'August/24/2013');
|
||||
expectFilter(new Date(2004, 10, 7, 0), 'dd.LLLL.yy', '07.November.04');
|
||||
expectFilter(new Date(2011, 4, 18, 0), 'dd-LLLL-yy', '18-May-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'LLLL/dd/yyyy', 'February/05/1980');
|
||||
expectFilter(new Date(1955, 2, 5, 0), 'yyyy/LLLL/dd', '1955/March/05');
|
||||
expectFilter(new Date(2011, 5, 2, 0), 'dd-LLLL-yy', '02-June-11');
|
||||
expectFilter(oldDate, 'yyyy/LLLL/dd', '0001/March/06');
|
||||
});
|
||||
|
||||
it('should work correctly for `d`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy', '17.November.13');
|
||||
@@ -282,11 +292,16 @@ describe('date parser', function() {
|
||||
it('should work correctly for `yy`', function() {
|
||||
expectParse('17.11.13', 'dd.MM.yy', new Date(2013, 10, 17, 0));
|
||||
expectParse('02-05-11', 'dd-MM-yy', new Date(2011, 4, 2, 0));
|
||||
expectParse('02/05/80', 'MM/dd/yy', new Date(2080, 1, 5, 0));
|
||||
expectParse('02/05/80', 'MM/dd/yy', new Date(1980, 1, 5, 0));
|
||||
expectParse('55/02/05', 'yy/MM/dd', new Date(2055, 1, 5, 0));
|
||||
expectParse('11-08-13', 'dd-MM-yy', new Date(2013, 7, 11, 0));
|
||||
});
|
||||
|
||||
it('should use `68` as the pivot year for `yy`', function() {
|
||||
expectParse('17.11.68', 'dd.MM.yy', new Date(2068, 10, 17, 0));
|
||||
expectParse('17.11.69', 'dd.MM.yy', new Date(1969, 10, 17, 0));
|
||||
});
|
||||
|
||||
it('should work correctly for `y`', function() {
|
||||
expectParse('17.11.2013', 'dd.MM.y', new Date(2013, 10, 17, 0));
|
||||
expectParse('31.12.2013', 'dd.MM.y', new Date(2013, 11, 31, 0));
|
||||
@@ -560,24 +575,48 @@ describe('date parser', function() {
|
||||
});
|
||||
|
||||
describe('with value literals', function() {
|
||||
it('should work with multiple literals', function() {
|
||||
expect(dateParser.parse('29 de January de 2013', 'd \'de\' MMMM \'de\' y')).toEqual(new Date(2013, 0, 29));
|
||||
describe('filter', function() {
|
||||
it('should work with multiple literals', function() {
|
||||
expect(dateParser.filter(new Date(2013, 0, 29), 'd \'de\' MMMM \'de\' y')).toEqual('29 de January de 2013');
|
||||
});
|
||||
|
||||
it('should work with escaped single quote', function() {
|
||||
expect(dateParser.filter(new Date(2015, 2, 22, 12), 'd.MMMM.yy h \'o\'\'clock\'')).toEqual('22.March.15 12 o\'clock');
|
||||
});
|
||||
|
||||
it('should work with only a single quote', function() {
|
||||
expect(dateParser.filter(new Date(2015, 2, 22), 'd.MMMM.yy \'\'\'')).toEqual('22.March.15 \'');
|
||||
});
|
||||
|
||||
it('should work with trailing literal', function() {
|
||||
expect(dateParser.filter(new Date(2013, 0, 1), '\'year\' y')).toEqual('year 2013');
|
||||
});
|
||||
|
||||
it('should work without whitespace', function() {
|
||||
expect(dateParser.filter(new Date(2013, 0, 1), '\'year:\'y')).toEqual('year:2013');
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with escaped single quote', function() {
|
||||
expect(dateParser.parse('22.March.15 12 o\'clock', 'd.MMMM.yy h \'o\'\'clock\'')).toEqual(new Date(2015, 2, 22, 12));
|
||||
});
|
||||
describe('parse', function() {
|
||||
it('should work with multiple literals', function() {
|
||||
expect(dateParser.parse('29 de January de 2013', 'd \'de\' MMMM \'de\' y')).toEqual(new Date(2013, 0, 29));
|
||||
});
|
||||
|
||||
it('should work with only a single quote', function() {
|
||||
expect(dateParser.parse('22.March.15 \'', 'd.MMMM.yy \'\'\'')).toEqual(new Date(2015, 2, 22));
|
||||
});
|
||||
it('should work with escaped single quote', function() {
|
||||
expect(dateParser.parse('22.March.15 12 o\'clock', 'd.MMMM.yy h \'o\'\'clock\'')).toEqual(new Date(2015, 2, 22, 12));
|
||||
});
|
||||
|
||||
it('should work with trailing literal', function() {
|
||||
expect(dateParser.parse('year 2013', '\'year\' y')).toEqual(new Date(2013, 0, 1));
|
||||
});
|
||||
it('should work with only a single quote', function() {
|
||||
expect(dateParser.parse('22.March.15 \'', 'd.MMMM.yy \'\'\'')).toEqual(new Date(2015, 2, 22));
|
||||
});
|
||||
|
||||
it('should work without whitespace', function() {
|
||||
expect(dateParser.parse('year:2013', '\'year:\'y')).toEqual(new Date(2013, 0, 1));
|
||||
it('should work with trailing literal', function() {
|
||||
expect(dateParser.parse('year 2013', '\'year\' y')).toEqual(new Date(2013, 0, 1));
|
||||
});
|
||||
|
||||
it('should work without whitespace', function() {
|
||||
expect(dateParser.parse('year:2013', '\'year:\'y')).toEqual(new Date(2013, 0, 1));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -752,4 +791,28 @@ describe('date parser', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('overrideParser', function() {
|
||||
var twoDigitYearParser = function (value) {
|
||||
this.year = +value + (+value > 30 ? 1900 : 2000);
|
||||
};
|
||||
|
||||
it('should get the current parser', function() {
|
||||
expect(dateParser.getParser('yy')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should override the parser', function() {
|
||||
dateParser.overrideParser('yy', twoDigitYearParser);
|
||||
expect(dateParser.parse('68', 'yy').getFullYear()).toEqual(1968);
|
||||
expect(dateParser.parse('67', 'yy').getFullYear()).toEqual(1967);
|
||||
expect(dateParser.parse('31', 'yy').getFullYear()).toEqual(1931);
|
||||
expect(dateParser.parse('30', 'yy').getFullYear()).toEqual(2030);
|
||||
});
|
||||
|
||||
it('should clear cached parsers', function() {
|
||||
expect(dateParser.parse('68', 'yy').getFullYear()).toEqual(2068);
|
||||
dateParser.overrideParser('yy', twoDigitYearParser);
|
||||
expect(dateParser.parse('68', 'yy').getFullYear()).toEqual(1968);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,16 +6,6 @@
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.uib-datepicker-popup.dropdown-menu {
|
||||
display: block;
|
||||
float: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.uib-button-bar {
|
||||
padding: 10px 9px 2px;
|
||||
}
|
||||
|
||||
.uib-left, .uib-right {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
+170
-747
File diff suppressed because it is too large
Load Diff
@@ -15,33 +15,7 @@
|
||||
|
||||
<h4>Inline</h4>
|
||||
<div style="display:inline-block; min-height:290px;">
|
||||
<uib-datepicker ng-model="dt" class="well well-sm" datepicker-options="inlineOptions"></uib-datepicker>
|
||||
</div>
|
||||
|
||||
<h4>Popup</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="input-group">
|
||||
<input type="text" class="form-control" uib-datepicker-popup="{{format}}" ng-model="dt" is-open="popup1.opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" alt-input-formats="altInputFormats" />
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="open1()"><i class="glyphicon glyphicon-calendar"></i></button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<p class="input-group">
|
||||
<input type="text" class="form-control" uib-datepicker-popup ng-model="dt" is-open="popup2.opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="open2()"><i class="glyphicon glyphicon-calendar"></i></button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label>Format: <span class="muted-text">(manual alternate <em>{{altInputFormats[0]}}</em>)</span></label> <select class="form-control" ng-model="format" ng-options="f for f in formats"><option></option></select>
|
||||
</div>
|
||||
<div uib-datepicker ng-model="dt" class="well well-sm" datepicker-options="options"></div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
@@ -8,20 +8,12 @@ angular.module('ui.bootstrap.demo').controller('DatepickerDemoCtrl', function ($
|
||||
$scope.dt = null;
|
||||
};
|
||||
|
||||
$scope.inlineOptions = {
|
||||
$scope.options = {
|
||||
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
|
||||
function disabled(data) {
|
||||
var date = data.date,
|
||||
@@ -30,39 +22,18 @@ angular.module('ui.bootstrap.demo').controller('DatepickerDemoCtrl', function ($
|
||||
}
|
||||
|
||||
$scope.toggleMin = function() {
|
||||
$scope.inlineOptions.minDate = $scope.inlineOptions.minDate ? null : new Date();
|
||||
$scope.dateOptions.minDate = $scope.inlineOptions.minDate;
|
||||
$scope.options.minDate = $scope.options.minDate ? null : new Date();
|
||||
};
|
||||
|
||||
$scope.toggleMin();
|
||||
|
||||
$scope.open1 = function() {
|
||||
$scope.popup1.opened = true;
|
||||
};
|
||||
|
||||
$scope.open2 = function() {
|
||||
$scope.popup2.opened = true;
|
||||
};
|
||||
|
||||
$scope.setDate = function(year, month, day) {
|
||||
$scope.dt = new Date(year, month, day);
|
||||
};
|
||||
|
||||
$scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
|
||||
$scope.format = $scope.formats[0];
|
||||
$scope.altInputFormats = ['M!/d!/yyyy'];
|
||||
|
||||
$scope.popup1 = {
|
||||
opened: false
|
||||
};
|
||||
|
||||
$scope.popup2 = {
|
||||
opened: false
|
||||
};
|
||||
|
||||
var tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
var afterTomorrow = new Date();
|
||||
var afterTomorrow = new Date(tomorrow);
|
||||
afterTomorrow.setDate(tomorrow.getDate() + 1);
|
||||
$scope.events = [
|
||||
{
|
||||
|
||||
+37
-128
@@ -2,8 +2,6 @@ Our datepicker is flexible and fully customizable.
|
||||
|
||||
You can navigate through days, months and years.
|
||||
|
||||
It comes in two formats, an inline `uib-datepicker` and an `uib-datepicker-popup` to be embedded in an input.
|
||||
|
||||
The datepicker has 3 modes:
|
||||
|
||||
* `day` - In this mode you're presented with a 6-week calendar for a specified month.
|
||||
@@ -15,7 +13,7 @@ The datepicker has 3 modes:
|
||||
* `ng-model`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i> -
|
||||
The date object. Needs to be a Javascript Date object.
|
||||
The date object. Must be a Javascript `Date` object. You may use the `uibDateParser` service to assist in string-to-object conversion.
|
||||
|
||||
* `ng-model-options`
|
||||
<small class="badge">$</small>
|
||||
@@ -24,210 +22,123 @@ The datepicker has 3 modes:
|
||||
Supported [angular ngModelOptions](https://docs.angularjs.org/api/ng/directive/ngModelOptions):
|
||||
* allowInvalid
|
||||
* timezone
|
||||
|
||||
|
||||
* `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:
|
||||
|
||||
* `datepicker-options`
|
||||
<small class="badge">$</small> -
|
||||
An object to configure the datepicker in one place.
|
||||
|
||||
* `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.
|
||||
|
||||
* `customClass ({date: date, mode: mode})` -
|
||||
An optional expression to add classes based on passing an object with date and current mode properties.
|
||||
|
||||
* `dateDisabled ({date: date, mode: mode})` -
|
||||
An optional expression to disable visible options based on passing an object with date and current mode properties.
|
||||
|
||||
* `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.
|
||||
|
||||
Defines the maximum available date. Requires a Javascript Date object.
|
||||
|
||||
* `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.
|
||||
|
||||
Defines the minimum available date. Requires a Javascript Date object.
|
||||
|
||||
* `minMode`
|
||||
<small class="badge">C</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `day`)_ -
|
||||
Sets a lower limit for mode.
|
||||
|
||||
|
||||
* `monthColumns`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `3`)_ -
|
||||
Number of columns displayed in month selection.
|
||||
|
||||
* `ngModelOptions`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `null`)_ -
|
||||
Sets `ngModelOptions` for datepicker. This can be overridden through attribute usage
|
||||
|
||||
* `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.
|
||||
|
||||
### uib-datepicker-popup settings
|
||||
|
||||
The popup is a wrapper that you can use in an input to toggle a datepicker. To configure the datepicker, use `datepicker-options`.
|
||||
|
||||
* `alt-input-formats`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
_(Default: `[]`)_ -
|
||||
A list of alternate formats acceptable for manual entry.
|
||||
|
||||
* `clear-text`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `Clear`)_ -
|
||||
The text to display for the clear button.
|
||||
|
||||
* `close-on-date-selection`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
_(Default: `true`)_ -
|
||||
Whether to close calendar when a date is chosen.
|
||||
|
||||
* `close-text`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `Done`)_ -
|
||||
The text to display for the close button.
|
||||
|
||||
* `current-text`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `Today`)_ -
|
||||
The text to display for the current day button.
|
||||
|
||||
* `datepicker-append-to-body`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
_(Default: `false`, Config: `appendToBody`)_ -
|
||||
Append the datepicker popup element to `body`, rather than inserting after `datepicker-popup`.
|
||||
|
||||
* `datepicker-options`
|
||||
<small class="badge">$</small> -
|
||||
An object with any combination of the datepicker settings (in camelCase) used to configure the wrapped datepicker.
|
||||
|
||||
* `datepicker-popup-template-url`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `uib/template/datepicker/popup.html`)_ -
|
||||
Add the ability to override the template used on the component.
|
||||
|
||||
* `datepicker-template-url`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `uib/template/datepicker/datepicker.html`)_ -
|
||||
Add the ability to override the template used on the component (inner uib-datepicker).
|
||||
|
||||
* `is-open`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `false`)_ -
|
||||
Whether or not to show the datepicker.
|
||||
|
||||
* `on-open-focus`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
_(Default: `true`)_ -
|
||||
Whether or not to focus the datepicker popup upon opening.
|
||||
|
||||
* `show-button-bar`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
_(Default: `true`)_ -
|
||||
Whether or not to display a button bar underneath the uib-datepicker.
|
||||
|
||||
* `type`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `text`, Config: `html5Types`)_ -
|
||||
You can override the input type to be _(date|datetime-local|month)_. That will change the date format of the popup.
|
||||
|
||||
* `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`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `yyyy-MM-dd`, Config: `datepickerConfig`)_ -
|
||||
The format for displayed dates. This string can take string literals by surrounding the value with single quotes, i.e. `yyyy-MM-dd h 'o\'clock'`.
|
||||
|
||||
### Keyboard support
|
||||
|
||||
Depending on datepicker's current mode, the date may refer either to day, month or year. Accordingly, the term view refers either to a month, year or year range.
|
||||
@@ -248,5 +159,3 @@ Depending on datepicker's current mode, the date may refer either to day, month
|
||||
**Notes**
|
||||
|
||||
If the date a user enters falls outside of the min-/max-date range, a `dateDisabled` validation error will show on the form.
|
||||
|
||||
If using this directive on input type date, a native browser datepicker could also appear.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
require('../dateparser');
|
||||
require('../isClass');
|
||||
require('../../template/datepicker/datepicker.html.js');
|
||||
require('../../template/datepicker/day.html.js');
|
||||
require('../../template/datepicker/month.html.js');
|
||||
require('../../template/datepicker/year.html.js');
|
||||
require('./datepicker');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.datepicker';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.datepicker', 'uib/template/datepicker/datepicker.html', 'uib/template/datepicker/day.html', 'uib/template/datepicker/month.html', 'uib/template/datepicker/year.html']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
+1
-16
@@ -1,17 +1,2 @@
|
||||
require('../dateparser');
|
||||
require('../isClass');
|
||||
require('../position');
|
||||
require('../../template/datepicker/datepicker.html.js');
|
||||
require('../../template/datepicker/day.html.js');
|
||||
require('../../template/datepicker/month.html.js');
|
||||
require('../../template/datepicker/year.html.js');
|
||||
require('../../template/datepicker/popup.html.js');
|
||||
require('./datepicker');
|
||||
|
||||
require('./datepicker.css');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.datepicker';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.datepicker', 'uib/template/datepicker/datepicker.html', 'uib/template/datepicker/day.html', 'uib/template/datepicker/month.html', 'uib/template/datepicker/year.html', 'uib/template/datepicker/popup.html']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
module.exports = require('./index-nocss.js');
|
||||
|
||||
+266
-3464
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
||||
<style>
|
||||
.full button span {
|
||||
background-color: limegreen;
|
||||
border-radius: 32px;
|
||||
color: black;
|
||||
}
|
||||
.partially button span {
|
||||
background-color: orange;
|
||||
border-radius: 32px;
|
||||
color: black;
|
||||
}
|
||||
</style>
|
||||
<div ng-controller="DatepickerPopupDemoCtrl">
|
||||
<pre>Selected date is: <em>{{dt | date:'fullDate' }}</em></pre>
|
||||
|
||||
<h4>Popup</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="input-group">
|
||||
<input type="text" class="form-control" uib-datepicker-popup="{{format}}" ng-model="dt" is-open="popup1.opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" alt-input-formats="altInputFormats" />
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="open1()"><i class="glyphicon glyphicon-calendar"></i></button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<p class="input-group">
|
||||
<input type="text" class="form-control" uib-datepicker-popup ng-model="dt" is-open="popup2.opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="open2()"><i class="glyphicon glyphicon-calendar"></i></button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label>Format: <span class="muted-text">(manual alternate <em>{{altInputFormats[0]}}</em>)</span></label> <select class="form-control" ng-model="format" ng-options="f for f in formats"><option></option></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<button type="button" class="btn btn-sm btn-info" ng-click="today()">Today</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="setDate(2009, 7, 24)">2009-08-24</button>
|
||||
<button type="button" class="btn btn-sm btn-danger" ng-click="clear()">Clear</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="toggleMin()" uib-tooltip="After today restriction">Min date</button>
|
||||
</div>
|
||||
@@ -0,0 +1,95 @@
|
||||
angular.module('ui.bootstrap.demo').controller('DatepickerPopupDemoCtrl', function ($scope) {
|
||||
$scope.today = function() {
|
||||
$scope.dt = new Date();
|
||||
};
|
||||
$scope.today();
|
||||
|
||||
$scope.clear = function() {
|
||||
$scope.dt = null;
|
||||
};
|
||||
|
||||
$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
|
||||
function disabled(data) {
|
||||
var date = data.date,
|
||||
mode = data.mode;
|
||||
return mode === 'day' && (date.getDay() === 0 || date.getDay() === 6);
|
||||
}
|
||||
|
||||
$scope.toggleMin = function() {
|
||||
$scope.inlineOptions.minDate = $scope.inlineOptions.minDate ? null : new Date();
|
||||
$scope.dateOptions.minDate = $scope.inlineOptions.minDate;
|
||||
};
|
||||
|
||||
$scope.toggleMin();
|
||||
|
||||
$scope.open1 = function() {
|
||||
$scope.popup1.opened = true;
|
||||
};
|
||||
|
||||
$scope.open2 = function() {
|
||||
$scope.popup2.opened = true;
|
||||
};
|
||||
|
||||
$scope.setDate = function(year, month, day) {
|
||||
$scope.dt = new Date(year, month, day);
|
||||
};
|
||||
|
||||
$scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
|
||||
$scope.format = $scope.formats[0];
|
||||
$scope.altInputFormats = ['M!/d!/yyyy'];
|
||||
|
||||
$scope.popup1 = {
|
||||
opened: false
|
||||
};
|
||||
|
||||
$scope.popup2 = {
|
||||
opened: false
|
||||
};
|
||||
|
||||
var tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
var afterTomorrow = new Date();
|
||||
afterTomorrow.setDate(tomorrow.getDate() + 1);
|
||||
$scope.events = [
|
||||
{
|
||||
date: tomorrow,
|
||||
status: 'full'
|
||||
},
|
||||
{
|
||||
date: afterTomorrow,
|
||||
status: 'partially'
|
||||
}
|
||||
];
|
||||
|
||||
function getDayClass(data) {
|
||||
var date = data.date,
|
||||
mode = data.mode;
|
||||
if (mode === 'day') {
|
||||
var dayToCheck = new Date(date).setHours(0,0,0,0);
|
||||
|
||||
for (var i = 0; i < $scope.events.length; i++) {
|
||||
var currentDay = new Date($scope.events[i].date).setHours(0,0,0,0);
|
||||
|
||||
if (dayToCheck === currentDay) {
|
||||
return $scope.events[i].status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
The datepicker popup is meant to be used with an input element. To understand usage of the datepicker, please refer to its documentation [here](https://angular-ui.github.io/bootstrap/#/datepicker).
|
||||
|
||||
### uib-datepicker-popup settings
|
||||
|
||||
The popup is a wrapper that you can use in an input to toggle a datepicker. To configure the datepicker, use `datepicker-options` as documented in the [inline datepicker](https://angular-ui.github.io/bootstrap/#/datepicker).
|
||||
|
||||
* `alt-input-formats`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
_(Default: `[]`)_ -
|
||||
A list of alternate formats acceptable for manual entry.
|
||||
|
||||
* `clear-text`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `Clear`)_ -
|
||||
The text to display for the clear button.
|
||||
|
||||
* `close-on-date-selection`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
_(Default: `true`)_ -
|
||||
Whether to close calendar when a date is chosen.
|
||||
|
||||
* `close-text`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `Done`)_ -
|
||||
The text to display for the close button.
|
||||
|
||||
* `current-text`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `Today`)_ -
|
||||
The text to display for the current day button.
|
||||
|
||||
* `datepicker-append-to-body`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
_(Default: `false`, Config: `appendToBody`)_ -
|
||||
Append the datepicker popup element to `body`, rather than inserting after `datepicker-popup`.
|
||||
|
||||
* `datepicker-options`
|
||||
<small class="badge">$</small> -
|
||||
An object with any combination of the datepicker settings (in camelCase) used to configure the wrapped datepicker.
|
||||
|
||||
* `datepicker-popup-template-url`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `uib/template/datepickerPopup/popup.html`)_ -
|
||||
Add the ability to override the template used on the component.
|
||||
|
||||
* `datepicker-template-url`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `uib/template/datepicker/datepicker.html`)_ -
|
||||
Add the ability to override the template used on the component (inner uib-datepicker).
|
||||
|
||||
* `is-open`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `false`)_ -
|
||||
Whether or not to show the datepicker.
|
||||
|
||||
* `ng-model`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i> -
|
||||
The date object. Must be a Javascript `Date` object. You may use the `uibDateParser` service to assist in string-to-object conversion.
|
||||
|
||||
* `on-open-focus`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
_(Default: `true`)_ -
|
||||
Whether or not to focus the datepicker popup upon opening.
|
||||
|
||||
* `show-button-bar`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
_(Default: `true`)_ -
|
||||
Whether or not to display a button bar underneath the uib-datepicker.
|
||||
|
||||
* `type`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `text`, Config: `html5Types`)_ -
|
||||
You can override the input type to be _(date|datetime-local|month)_. That will change the date format of the popup.
|
||||
|
||||
* `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`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `yyyy-MM-dd`, Config: `datepickerConfig`)_ -
|
||||
The format for displayed dates. This string can take string literals by surrounding the value with single quotes, i.e. `yyyy-MM-dd h 'o\'clock'`.
|
||||
|
||||
**Notes**
|
||||
|
||||
If using this directive on input type date, a native browser datepicker could also appear.
|
||||
@@ -0,0 +1,10 @@
|
||||
require('../datepicker/index-nocss.js');
|
||||
require('../position/index-nocss.js');
|
||||
require('../../template/datepickerPopup/popup.html.js');
|
||||
require('./popup.js');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.datepickerPopup';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.datepickerPopup', 'uib/template/datepickerPopup/popup.html', 'ui.bootstrap.module.datepicker']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
@@ -0,0 +1,4 @@
|
||||
require('../datepicker/datepicker.css');
|
||||
require('../position/position.css');
|
||||
require('./popup.css');
|
||||
module.exports = require('./index-nocss.js');
|
||||
@@ -0,0 +1,9 @@
|
||||
.uib-datepicker-popup.dropdown-menu {
|
||||
display: block;
|
||||
float: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.uib-button-bar {
|
||||
padding: 10px 9px 2px;
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position'])
|
||||
|
||||
.value('$datepickerPopupLiteralWarning', true)
|
||||
|
||||
.constant('uibDatepickerPopupConfig', {
|
||||
altInputFormats: [],
|
||||
appendToBody: false,
|
||||
clearText: 'Clear',
|
||||
closeOnDateSelection: true,
|
||||
closeText: 'Done',
|
||||
currentText: 'Today',
|
||||
datepickerPopup: 'yyyy-MM-dd',
|
||||
datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html',
|
||||
datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
|
||||
html5Types: {
|
||||
date: 'yyyy-MM-dd',
|
||||
'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
|
||||
'month': 'yyyy-MM'
|
||||
},
|
||||
onOpenFocus: true,
|
||||
showButtonBar: true,
|
||||
placement: 'auto bottom-left'
|
||||
})
|
||||
|
||||
.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning',
|
||||
function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) {
|
||||
var cache = {},
|
||||
isHtml5DateInput = false;
|
||||
var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
|
||||
datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl,
|
||||
ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [];
|
||||
|
||||
this.init = function(_ngModel_) {
|
||||
ngModel = _ngModel_;
|
||||
ngModelOptions = extractOptions(ngModel);
|
||||
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;
|
||||
|
||||
if (datepickerPopupConfig.html5Types[$attrs.type]) {
|
||||
dateFormat = datepickerPopupConfig.html5Types[$attrs.type];
|
||||
isHtml5DateInput = true;
|
||||
} else {
|
||||
dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
|
||||
$attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
|
||||
var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
|
||||
// Invalidate the $modelValue to ensure that formatters re-run
|
||||
// FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
|
||||
if (newDateFormat !== dateFormat) {
|
||||
dateFormat = newDateFormat;
|
||||
ngModel.$modelValue = null;
|
||||
|
||||
if (!dateFormat) {
|
||||
throw new Error('uibDatepickerPopup must have a date format specified.');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!dateFormat) {
|
||||
throw new Error('uibDatepickerPopup must have a date format specified.');
|
||||
}
|
||||
|
||||
if (isHtml5DateInput && $attrs.uibDatepickerPopup) {
|
||||
throw new Error('HTML5 date input types do not support custom formats.');
|
||||
}
|
||||
|
||||
// popup element used to display calendar
|
||||
popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
|
||||
|
||||
popupEl.attr({
|
||||
'ng-model': 'date',
|
||||
'ng-change': 'dateSelection(date)',
|
||||
'template-url': datepickerPopupTemplateUrl
|
||||
});
|
||||
|
||||
// datepicker element
|
||||
datepickerEl = angular.element(popupEl.children()[0]);
|
||||
datepickerEl.attr('template-url', datepickerTemplateUrl);
|
||||
|
||||
if (!$scope.datepickerOptions) {
|
||||
$scope.datepickerOptions = {};
|
||||
}
|
||||
|
||||
if (isHtml5DateInput) {
|
||||
if ($attrs.type === 'month') {
|
||||
$scope.datepickerOptions.datepickerMode = 'month';
|
||||
$scope.datepickerOptions.minMode = 'month';
|
||||
}
|
||||
}
|
||||
|
||||
datepickerEl.attr('datepicker-options', 'datepickerOptions');
|
||||
|
||||
if (!isHtml5DateInput) {
|
||||
// Internal API to maintain the correct ng-invalid-[key] class
|
||||
ngModel.$$parserName = 'date';
|
||||
ngModel.$validators.date = validator;
|
||||
ngModel.$parsers.unshift(parseDate);
|
||||
ngModel.$formatters.push(function(value) {
|
||||
if (ngModel.$isEmpty(value)) {
|
||||
$scope.date = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
if (angular.isNumber(value)) {
|
||||
value = new Date(value);
|
||||
}
|
||||
|
||||
$scope.date = dateParser.fromTimezone(value, ngModelOptions.getOption('timezone'));
|
||||
|
||||
return dateParser.filter($scope.date, dateFormat);
|
||||
});
|
||||
} else {
|
||||
ngModel.$formatters.push(function(value) {
|
||||
$scope.date = dateParser.fromTimezone(value, ngModelOptions.getOption('timezone'));
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
// Detect changes in the view from the text box
|
||||
ngModel.$viewChangeListeners.push(function() {
|
||||
$scope.date = parseDateString(ngModel.$viewValue);
|
||||
});
|
||||
|
||||
$element.on('keydown', inputKeydownBind);
|
||||
|
||||
$popup = $compile(popupEl)($scope);
|
||||
// Prevent jQuery cache memory leak (template is now redundant after linking)
|
||||
popupEl.remove();
|
||||
|
||||
if (appendToBody) {
|
||||
$document.find('body').append($popup);
|
||||
} else {
|
||||
$element.after($popup);
|
||||
}
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
if ($scope.isOpen === true) {
|
||||
if (!$rootScope.$$phase) {
|
||||
$scope.$apply(function() {
|
||||
$scope.isOpen = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$popup.remove();
|
||||
$element.off('keydown', inputKeydownBind);
|
||||
$document.off('click', documentClickBind);
|
||||
if (scrollParentEl) {
|
||||
scrollParentEl.off('scroll', positionPopup);
|
||||
}
|
||||
angular.element($window).off('resize', positionPopup);
|
||||
|
||||
//Clear all watch listeners on destroy
|
||||
while (watchListeners.length) {
|
||||
watchListeners.shift()();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.getText = function(key) {
|
||||
return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
|
||||
};
|
||||
|
||||
$scope.isDisabled = function(date) {
|
||||
if (date === 'today') {
|
||||
date = dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone'));
|
||||
}
|
||||
|
||||
var dates = {};
|
||||
angular.forEach(['minDate', 'maxDate'], function(key) {
|
||||
if (!$scope.datepickerOptions[key]) {
|
||||
dates[key] = null;
|
||||
} else if (angular.isDate($scope.datepickerOptions[key])) {
|
||||
dates[key] = new Date($scope.datepickerOptions[key]);
|
||||
} else {
|
||||
if ($datepickerPopupLiteralWarning) {
|
||||
$log.warn('Literal date support has been deprecated, please switch to date object usage');
|
||||
}
|
||||
|
||||
dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium'));
|
||||
}
|
||||
});
|
||||
|
||||
return $scope.datepickerOptions &&
|
||||
dates.minDate && $scope.compare(date, dates.minDate) < 0 ||
|
||||
dates.maxDate && $scope.compare(date, dates.maxDate) > 0;
|
||||
};
|
||||
|
||||
$scope.compare = function(date1, date2) {
|
||||
return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
|
||||
};
|
||||
|
||||
// Inner change
|
||||
$scope.dateSelection = function(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);
|
||||
ngModel.$setViewValue(date);
|
||||
|
||||
if (closeOnDateSelection) {
|
||||
$scope.isOpen = false;
|
||||
$element[0].focus();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.keydown = function(evt) {
|
||||
if (evt.which === 27) {
|
||||
evt.stopPropagation();
|
||||
$scope.isOpen = false;
|
||||
$element[0].focus();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.select = function(date, evt) {
|
||||
evt.stopPropagation();
|
||||
|
||||
if (date === 'today') {
|
||||
var today = new Date();
|
||||
if (angular.isDate($scope.date)) {
|
||||
date = new Date($scope.date);
|
||||
date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
|
||||
} else {
|
||||
date = dateParser.fromTimezone(today, ngModelOptions.getOption('timezone'));
|
||||
date.setHours(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
$scope.dateSelection(date);
|
||||
};
|
||||
|
||||
$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.$watch('isOpen', function(value) {
|
||||
if (value) {
|
||||
if (!$scope.disabled) {
|
||||
$timeout(function() {
|
||||
positionPopup();
|
||||
|
||||
if (onOpenFocus) {
|
||||
$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;
|
||||
}
|
||||
} else {
|
||||
$document.off('click', documentClickBind);
|
||||
if (scrollParentEl) {
|
||||
scrollParentEl.off('scroll', positionPopup);
|
||||
}
|
||||
angular.element($window).off('resize', positionPopup);
|
||||
}
|
||||
});
|
||||
|
||||
function cameltoDash(string) {
|
||||
return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
|
||||
}
|
||||
|
||||
function parseDateString(viewValue) {
|
||||
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);
|
||||
if (!isNaN(date)) {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
function parseDate(viewValue) {
|
||||
if (angular.isNumber(viewValue)) {
|
||||
// presumably timestamp to date object
|
||||
viewValue = new Date(viewValue);
|
||||
}
|
||||
|
||||
if (!viewValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (angular.isDate(viewValue) && !isNaN(viewValue)) {
|
||||
return viewValue;
|
||||
}
|
||||
|
||||
if (angular.isString(viewValue)) {
|
||||
var date = parseDateString(viewValue);
|
||||
if (!isNaN(date)) {
|
||||
return dateParser.toTimezone(date, ngModelOptions.getOption('timezone'));
|
||||
}
|
||||
}
|
||||
|
||||
return ngModelOptions.getOption('allowInvalid') ? viewValue : undefined;
|
||||
}
|
||||
|
||||
function validator(modelValue, viewValue) {
|
||||
var value = modelValue || viewValue;
|
||||
|
||||
if (!$attrs.ngRequired && !value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (angular.isNumber(value)) {
|
||||
value = new Date(value);
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (angular.isDate(value) && !isNaN(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (angular.isString(value)) {
|
||||
return !isNaN(parseDateString(value));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function documentClickBind(event) {
|
||||
if (!$scope.isOpen && $scope.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var popup = $popup[0];
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function inputKeydownBind(evt) {
|
||||
if (evt.which === 27 && $scope.isOpen) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
$scope.$apply(function() {
|
||||
$scope.isOpen = false;
|
||||
});
|
||||
$element[0].focus();
|
||||
} else if (evt.which === 40 && !$scope.isOpen) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
$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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function extractOptions(ngModelCtrl) {
|
||||
var ngModelOptions;
|
||||
|
||||
if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing
|
||||
// guarantee a value
|
||||
ngModelOptions = angular.isObject(ngModelCtrl.$options) ?
|
||||
ngModelCtrl.$options :
|
||||
{
|
||||
timezone: null
|
||||
};
|
||||
|
||||
// mimic 1.6+ api
|
||||
ngModelOptions.getOption = function (key) {
|
||||
return ngModelOptions[key];
|
||||
};
|
||||
} else { // in angular >=1.6 $options is always present
|
||||
ngModelOptions = ngModelCtrl.$options;
|
||||
}
|
||||
|
||||
return ngModelOptions;
|
||||
}
|
||||
|
||||
$scope.$on('uib:datepicker.mode', function() {
|
||||
$timeout(positionPopup, 0, false);
|
||||
});
|
||||
}])
|
||||
|
||||
.directive('uibDatepickerPopup', function() {
|
||||
return {
|
||||
require: ['ngModel', 'uibDatepickerPopup'],
|
||||
controller: 'UibDatepickerPopupController',
|
||||
scope: {
|
||||
datepickerOptions: '=?',
|
||||
isOpen: '=?',
|
||||
currentText: '@',
|
||||
clearText: '@',
|
||||
closeText: '@'
|
||||
},
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
var ngModel = ctrls[0],
|
||||
ctrl = ctrls[1];
|
||||
|
||||
ctrl.init(ngModel);
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
.directive('uibDatepickerPopupWrap', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
transclude: true,
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html';
|
||||
}
|
||||
};
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
<a href id="simple-dropdown" uib-dropdown-toggle>
|
||||
Click me for a dropdown, yo!
|
||||
</a>
|
||||
<ul uib-dropdown-menu aria-labelledby="simple-dropdown">
|
||||
<ul class="dropdown-menu" uib-dropdown-menu aria-labelledby="simple-dropdown">
|
||||
<li ng-repeat="choice in items">
|
||||
<a href>{{choice}}</a>
|
||||
</li>
|
||||
@@ -17,7 +17,7 @@
|
||||
<button id="single-button" type="button" class="btn btn-primary" uib-dropdown-toggle ng-disabled="disabled">
|
||||
Button dropdown <span class="caret"></span>
|
||||
</button>
|
||||
<ul uib-dropdown-menu role="menu" aria-labelledby="single-button">
|
||||
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button">
|
||||
<li role="menuitem"><a href="#">Action</a></li>
|
||||
<li role="menuitem"><a href="#">Another action</a></li>
|
||||
<li role="menuitem"><a href="#">Something else here</a></li>
|
||||
@@ -33,7 +33,7 @@
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">Split button!</span>
|
||||
</button>
|
||||
<ul uib-dropdown-menu role="menu" aria-labelledby="split-button">
|
||||
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="split-button">
|
||||
<li role="menuitem"><a href="#">Action</a></li>
|
||||
<li role="menuitem"><a href="#">Another action</a></li>
|
||||
<li role="menuitem"><a href="#">Something else here</a></li>
|
||||
@@ -47,7 +47,7 @@
|
||||
<button id="btn-append-to-body" type="button" class="btn btn-primary" uib-dropdown-toggle>
|
||||
Dropdown on Body <span class="caret"></span>
|
||||
</button>
|
||||
<ul uib-dropdown-menu role="menu" aria-labelledby="btn-append-to-body">
|
||||
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="btn-append-to-body">
|
||||
<li role="menuitem"><a href="#">Action</a></li>
|
||||
<li role="menuitem"><a href="#">Another action</a></li>
|
||||
<li role="menuitem"><a href="#">Something else here</a></li>
|
||||
@@ -61,7 +61,7 @@
|
||||
<button id="button-template-url" type="button" class="btn btn-primary" uib-dropdown-toggle ng-disabled="disabled">
|
||||
Dropdown using template <span class="caret"></span>
|
||||
</button>
|
||||
<ul uib-dropdown-menu template-url="dropdown.html" aria-labelledby="button-template-url">
|
||||
<ul class="dropdown-menu" uib-dropdown-menu template-url="dropdown.html" aria-labelledby="button-template-url">
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<button id="simple-btn-keyboard-nav" type="button" class="btn btn-primary" uib-dropdown-toggle>
|
||||
Dropdown with keyboard navigation <span class="caret"></span>
|
||||
</button>
|
||||
<ul uib-dropdown-menu role="menu" aria-labelledby="simple-btn-keyboard-nav">
|
||||
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="simple-btn-keyboard-nav">
|
||||
<li role="menuitem"><a href="#">Action</a></li>
|
||||
<li role="menuitem"><a href="#">Another action</a></li>
|
||||
<li role="menuitem"><a href="#">Something else here</a></li>
|
||||
|
||||
@@ -14,7 +14,7 @@ Each of these parts need to be used as attribute directives.
|
||||
_(Default: `always`)_ -
|
||||
Controls the behavior of the menu when clicked.
|
||||
* `always` - Automatically closes the dropdown when any of its elements is clicked.
|
||||
* `disabled` - Disables the auto close. You can control it manually with `is-open`. It still gets closed if the toggle is clicked, `esc` is pressed or another dropdown is open. It also won't be closed on `$locationchangeSuccess`.
|
||||
* `disabled` - Disables the auto close. You can control it manually with `is-open`. It still gets closed if the toggle is clicked, `esc` is pressed or another dropdown is open.
|
||||
* `outsideClick` - Closes the dropdown automatically only when the user clicks any element outside the dropdown.
|
||||
|
||||
* `dropdown-append-to`
|
||||
@@ -25,7 +25,7 @@ Each of these parts need to be used as attribute directives.
|
||||
* `dropdown-append-to-body`
|
||||
<small class="badge">B</small>
|
||||
_(Default: `false`)_ -
|
||||
Appends the inner dropdown-menu to the body element.
|
||||
Appends the inner dropdown-menu to the body element if the attribute is present without a value, or with a non `false` value.
|
||||
|
||||
* `is-open`
|
||||
<small class="badge">$</small>
|
||||
|
||||
+139
-45
@@ -1,17 +1,35 @@
|
||||
angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.position'])
|
||||
|
||||
.constant('uibDropdownConfig', {
|
||||
appendToOpenClass: 'uib-dropdown-open',
|
||||
openClass: 'open'
|
||||
})
|
||||
|
||||
.service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
|
||||
.service('uibDropdownService', ['$document', '$rootScope', '$$multiMap', function($document, $rootScope, $$multiMap) {
|
||||
var openScope = null;
|
||||
var openedContainers = $$multiMap.createNew();
|
||||
|
||||
this.open = function(dropdownScope) {
|
||||
this.isOnlyOpen = function(dropdownScope, appendTo) {
|
||||
var openedDropdowns = openedContainers.get(appendTo);
|
||||
if (openedDropdowns) {
|
||||
var openDropdown = openedDropdowns.reduce(function(toClose, dropdown) {
|
||||
if (dropdown.scope === dropdownScope) {
|
||||
return dropdown;
|
||||
}
|
||||
|
||||
return toClose;
|
||||
}, {});
|
||||
if (openDropdown) {
|
||||
return openedDropdowns.length === 1;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.open = function(dropdownScope, element, appendTo) {
|
||||
if (!openScope) {
|
||||
$document.on('click', closeDropdown);
|
||||
$document.on('keydown', keybindFilter);
|
||||
}
|
||||
|
||||
if (openScope && openScope !== dropdownScope) {
|
||||
@@ -19,20 +37,58 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
}
|
||||
|
||||
openScope = dropdownScope;
|
||||
|
||||
if (!appendTo) {
|
||||
return;
|
||||
}
|
||||
|
||||
var openedDropdowns = openedContainers.get(appendTo);
|
||||
if (openedDropdowns) {
|
||||
var openedScopes = openedDropdowns.map(function(dropdown) {
|
||||
return dropdown.scope;
|
||||
});
|
||||
if (openedScopes.indexOf(dropdownScope) === -1) {
|
||||
openedContainers.put(appendTo, {
|
||||
scope: dropdownScope
|
||||
});
|
||||
}
|
||||
} else {
|
||||
openedContainers.put(appendTo, {
|
||||
scope: dropdownScope
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.close = function(dropdownScope) {
|
||||
this.close = function(dropdownScope, element, appendTo) {
|
||||
if (openScope === dropdownScope) {
|
||||
openScope = null;
|
||||
$document.off('click', closeDropdown);
|
||||
$document.off('keydown', keybindFilter);
|
||||
$document.off('keydown', this.keybindFilter);
|
||||
openScope = null;
|
||||
}
|
||||
|
||||
if (!appendTo) {
|
||||
return;
|
||||
}
|
||||
|
||||
var openedDropdowns = openedContainers.get(appendTo);
|
||||
if (openedDropdowns) {
|
||||
var dropdownToClose = openedDropdowns.reduce(function(toClose, dropdown) {
|
||||
if (dropdown.scope === dropdownScope) {
|
||||
return dropdown;
|
||||
}
|
||||
|
||||
return toClose;
|
||||
}, {});
|
||||
if (dropdownToClose) {
|
||||
openedContainers.remove(appendTo, dropdownToClose);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var closeDropdown = function(evt) {
|
||||
// This method may still be called during the same mouse event that
|
||||
// unbound this event handler. So check openScope before proceeding.
|
||||
if (!openScope) { return; }
|
||||
if (!openScope || !openScope.isOpen) { return; }
|
||||
|
||||
if (evt && openScope.getAutoClose() === 'disabled') { return; }
|
||||
|
||||
@@ -49,6 +105,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
return;
|
||||
}
|
||||
|
||||
openScope.focusToggleElement();
|
||||
openScope.isOpen = false;
|
||||
|
||||
if (!$rootScope.$$phase) {
|
||||
@@ -56,11 +113,21 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
}
|
||||
};
|
||||
|
||||
var keybindFilter = function(evt) {
|
||||
this.keybindFilter = function(evt) {
|
||||
if (!openScope) {
|
||||
// see this.close as ESC could have been pressed which kills the scope so we can not proceed
|
||||
return;
|
||||
}
|
||||
|
||||
var dropdownElement = openScope.getDropdownElement();
|
||||
var toggleElement = openScope.getToggleElement();
|
||||
var dropdownElementTargeted = dropdownElement && dropdownElement[0].contains(evt.target);
|
||||
var toggleElementTargeted = toggleElement && toggleElement[0].contains(evt.target);
|
||||
if (evt.which === 27) {
|
||||
evt.stopPropagation();
|
||||
openScope.focusToggleElement();
|
||||
closeDropdown();
|
||||
} else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) {
|
||||
} else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen && (dropdownElementTargeted || toggleElementTargeted)) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
openScope.focusDropdownEntry(evt.which);
|
||||
@@ -77,8 +144,6 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
getIsOpen,
|
||||
setIsOpen = angular.noop,
|
||||
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
|
||||
appendToBody = false,
|
||||
appendTo = null,
|
||||
keynavEnabled = false,
|
||||
selectedOption = null,
|
||||
body = $document.find('body');
|
||||
@@ -95,26 +160,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
});
|
||||
}
|
||||
|
||||
if (angular.isDefined($attrs.dropdownAppendTo)) {
|
||||
var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
|
||||
if (appendToEl) {
|
||||
appendTo = angular.element(appendToEl);
|
||||
}
|
||||
}
|
||||
|
||||
appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
|
||||
keynavEnabled = angular.isDefined($attrs.keyboardNav);
|
||||
|
||||
if (appendToBody && !appendTo) {
|
||||
appendTo = body;
|
||||
}
|
||||
|
||||
if (appendTo && self.dropdownMenu) {
|
||||
appendTo.append(self.dropdownMenu);
|
||||
$element.on('$destroy', function handleDestroyEvent() {
|
||||
self.dropdownMenu.remove();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.toggle = function(open) {
|
||||
@@ -186,11 +232,48 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
}
|
||||
};
|
||||
|
||||
function removeDropdownMenu() {
|
||||
$element.append(self.dropdownMenu);
|
||||
}
|
||||
|
||||
scope.$watch('isOpen', function(isOpen, wasOpen) {
|
||||
var appendTo = null,
|
||||
appendToBody = false;
|
||||
|
||||
if (angular.isDefined($attrs.dropdownAppendTo)) {
|
||||
var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
|
||||
if (appendToEl) {
|
||||
appendTo = angular.element(appendToEl);
|
||||
}
|
||||
}
|
||||
|
||||
if (angular.isDefined($attrs.dropdownAppendToBody)) {
|
||||
var appendToBodyValue = $parse($attrs.dropdownAppendToBody)(scope);
|
||||
if (appendToBodyValue !== false) {
|
||||
appendToBody = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (appendToBody && !appendTo) {
|
||||
appendTo = body;
|
||||
}
|
||||
|
||||
if (appendTo && self.dropdownMenu) {
|
||||
if (isOpen) {
|
||||
appendTo.append(self.dropdownMenu);
|
||||
$element.on('$destroy', removeDropdownMenu);
|
||||
} else {
|
||||
$element.off('$destroy', removeDropdownMenu);
|
||||
removeDropdownMenu();
|
||||
}
|
||||
}
|
||||
|
||||
if (appendTo && self.dropdownMenu) {
|
||||
var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
|
||||
css,
|
||||
rightalign;
|
||||
rightalign,
|
||||
scrollbarPadding,
|
||||
scrollbarWidth = 0;
|
||||
|
||||
css = {
|
||||
top: pos.top + 'px',
|
||||
@@ -203,7 +286,13 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
css.right = 'auto';
|
||||
} else {
|
||||
css.left = 'auto';
|
||||
css.right = window.innerWidth -
|
||||
scrollbarPadding = $position.scrollbarPadding(appendTo);
|
||||
|
||||
if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
|
||||
scrollbarWidth = scrollbarPadding.scrollbarWidth;
|
||||
}
|
||||
|
||||
css.right = window.innerWidth - scrollbarWidth -
|
||||
(pos.left + $element.prop('offsetWidth')) + 'px';
|
||||
}
|
||||
|
||||
@@ -226,10 +315,18 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
}
|
||||
|
||||
var openContainer = appendTo ? appendTo : $element;
|
||||
var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass);
|
||||
var dropdownOpenClass = appendTo ? appendToOpenClass : openClass;
|
||||
var hasOpenClass = openContainer.hasClass(dropdownOpenClass);
|
||||
var isOnlyOpen = uibDropdownService.isOnlyOpen($scope, appendTo);
|
||||
|
||||
if (hasOpenClass === !isOpen) {
|
||||
$animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
|
||||
var toggleClass;
|
||||
if (appendTo) {
|
||||
toggleClass = !isOnlyOpen ? 'addClass' : 'removeClass';
|
||||
} else {
|
||||
toggleClass = isOpen ? 'addClass' : 'removeClass';
|
||||
}
|
||||
$animate[toggleClass](openContainer, dropdownOpenClass).then(function() {
|
||||
if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
|
||||
toggleInvoker($scope, { open: !!isOpen });
|
||||
}
|
||||
@@ -244,13 +341,17 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
var newEl = dropdownElement;
|
||||
self.dropdownMenu.replaceWith(newEl);
|
||||
self.dropdownMenu = newEl;
|
||||
$document.on('keydown', uibDropdownService.keybindFilter);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$document.on('keydown', uibDropdownService.keybindFilter);
|
||||
}
|
||||
|
||||
scope.focusToggleElement();
|
||||
uibDropdownService.open(scope);
|
||||
uibDropdownService.open(scope, $element, appendTo);
|
||||
} else {
|
||||
uibDropdownService.close(scope, $element, appendTo);
|
||||
if (self.dropdownMenuTemplateUrl) {
|
||||
if (templateScope) {
|
||||
templateScope.$destroy();
|
||||
@@ -260,7 +361,6 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
self.dropdownMenu = newEl;
|
||||
}
|
||||
|
||||
uibDropdownService.close(scope);
|
||||
self.selectedOption = null;
|
||||
}
|
||||
|
||||
@@ -268,12 +368,6 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
setIsOpen($scope, isOpen);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function() {
|
||||
if (scope.getAutoClose() !== 'disabled') {
|
||||
scope.isOpen = false;
|
||||
}
|
||||
});
|
||||
}])
|
||||
|
||||
.directive('uibDropdown', function() {
|
||||
@@ -330,7 +424,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
}
|
||||
};
|
||||
|
||||
element.bind('click', toggleDropdown);
|
||||
element.on('click', toggleDropdown);
|
||||
|
||||
// WAI-ARIA
|
||||
element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
|
||||
@@ -339,7 +433,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
element.unbind('click', toggleDropdown);
|
||||
element.off('click', toggleDropdown);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
require('../multiMap');
|
||||
require('../position/index-nocss.js');
|
||||
require('./dropdown');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.dropdown';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.dropdown']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
@@ -1,8 +1,2 @@
|
||||
require('../position');
|
||||
require('./dropdown');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.dropdown';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.dropdown']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
require('../position/position.css');
|
||||
module.exports = require('./index-nocss.js');
|
||||
|
||||
+339
-100
@@ -26,13 +26,16 @@ describe('uib-dropdown', function() {
|
||||
|
||||
var triggerKeyDown = function (element, keyCode) {
|
||||
var e = $.Event('keydown');
|
||||
spyOn(e, 'stopPropagation');
|
||||
e.stopPropagation.and.callThrough();
|
||||
e.which = keyCode;
|
||||
element.trigger(e);
|
||||
return e;
|
||||
};
|
||||
|
||||
describe('basic', function() {
|
||||
function dropdown() {
|
||||
return $compile('<li uib-dropdown><a href uib-dropdown-toggle></a><ul><li><a href>Hello</a></li></ul></li>')($rootScope);
|
||||
return $compile('<li uib-dropdown><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li><a href>Hello</a></li></ul></li>')($rootScope);
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
@@ -66,16 +69,18 @@ describe('uib-dropdown', function() {
|
||||
});
|
||||
|
||||
it('should close on escape key & focus toggle element', function() {
|
||||
var dropdownMenu = element.find('[uib-dropdown-menu]');
|
||||
$document.find('body').append(element);
|
||||
clickDropdownToggle();
|
||||
triggerKeyDown($document, 27);
|
||||
var event = triggerKeyDown(dropdownMenu, 27);
|
||||
expect(element).not.toHaveClass(dropdownConfig.openClass);
|
||||
expect(element.find('a')).toHaveFocus();
|
||||
expect(event.stopPropagation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not close on backspace key', function() {
|
||||
clickDropdownToggle();
|
||||
triggerKeyDown($document, 8);
|
||||
triggerKeyDown(element, 8);
|
||||
expect(element).toHaveClass(dropdownConfig.openClass);
|
||||
});
|
||||
|
||||
@@ -88,31 +93,23 @@ describe('uib-dropdown', function() {
|
||||
expect(element).toHaveClass(dropdownConfig.openClass);
|
||||
});
|
||||
|
||||
it('should close on $location change', function() {
|
||||
clickDropdownToggle();
|
||||
expect(element).toHaveClass(dropdownConfig.openClass);
|
||||
$rootScope.$broadcast('$locationChangeSuccess');
|
||||
$rootScope.$apply();
|
||||
expect(element).not.toHaveClass(dropdownConfig.openClass);
|
||||
});
|
||||
|
||||
it('should only allow one dropdown to be open at once', function() {
|
||||
var elm1 = dropdown();
|
||||
var elm2 = dropdown();
|
||||
expect(elm1).not.toHaveClass(dropdownConfig.openClass);
|
||||
expect(elm2).not.toHaveClass(dropdownConfig.openClass);
|
||||
|
||||
clickDropdownToggle( elm1 );
|
||||
clickDropdownToggle(elm1);
|
||||
expect(elm1).toHaveClass(dropdownConfig.openClass);
|
||||
expect(elm2).not.toHaveClass(dropdownConfig.openClass);
|
||||
|
||||
clickDropdownToggle( elm2 );
|
||||
clickDropdownToggle(elm2);
|
||||
expect(elm1).not.toHaveClass(dropdownConfig.openClass);
|
||||
expect(elm2).toHaveClass(dropdownConfig.openClass);
|
||||
});
|
||||
|
||||
it('should not toggle if the element has `disabled` class', function() {
|
||||
var elm = $compile('<li uib-dropdown><a class="disabled" uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
|
||||
var elm = $compile('<li uib-dropdown><a class="disabled" uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
|
||||
clickDropdownToggle( elm );
|
||||
expect(elm).not.toHaveClass(dropdownConfig.openClass);
|
||||
});
|
||||
@@ -125,7 +122,7 @@ describe('uib-dropdown', function() {
|
||||
|
||||
it('should not toggle if the element has `ng-disabled` as true', function() {
|
||||
$rootScope.isdisabled = true;
|
||||
var elm = $compile('<li uib-dropdown><div ng-disabled="isdisabled" uib-dropdown-toggle></div><ul><li>Hello</li></ul></li>')($rootScope);
|
||||
var elm = $compile('<li uib-dropdown><div ng-disabled="isdisabled" uib-dropdown-toggle></div><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
elm.find('div').click();
|
||||
expect(elm).not.toHaveClass(dropdownConfig.openClass);
|
||||
@@ -138,7 +135,7 @@ describe('uib-dropdown', function() {
|
||||
|
||||
it('should unbind events on scope destroy', function() {
|
||||
var $scope = $rootScope.$new();
|
||||
var elm = $compile('<li uib-dropdown><button ng-disabled="isdisabled" uib-dropdown-toggle></button><ul><li>Hello</li></ul></li>')($scope);
|
||||
var elm = $compile('<li uib-dropdown><button ng-disabled="isdisabled" uib-dropdown-toggle></button><ul uib-dropdown-menu><li>Hello</li></ul></li>')($scope);
|
||||
$scope.$digest();
|
||||
|
||||
var buttonEl = elm.find('button');
|
||||
@@ -218,27 +215,198 @@ describe('uib-dropdown', function() {
|
||||
});
|
||||
|
||||
describe('using dropdown-append-to-body', function() {
|
||||
function dropdown() {
|
||||
return $compile('<li uib-dropdown dropdown-append-to-body><a href uib-dropdown-toggle></a><ul uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);
|
||||
}
|
||||
describe('with no value', function() {
|
||||
function dropdown() {
|
||||
return $compile('<li uib-dropdown dropdown-append-to-body><a href uib-dropdown-toggle></a><ul uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
element = dropdown();
|
||||
beforeEach(function() {
|
||||
element = dropdown();
|
||||
$document.find('body').append(element);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
element.remove();
|
||||
});
|
||||
|
||||
it('does not add the menu to the body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
|
||||
describe('when toggled open', function() {
|
||||
var toggle;
|
||||
beforeEach(function() {
|
||||
toggle = element.find('[uib-dropdown-toggle]');
|
||||
toggle.trigger('click');
|
||||
});
|
||||
it('adds the menu to the body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]);
|
||||
});
|
||||
|
||||
describe('when toggled closed', function() {
|
||||
beforeEach(function() {
|
||||
toggle.trigger('click');
|
||||
});
|
||||
it('removes the menu from body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when closed by clicking on menu', function() {
|
||||
var menu;
|
||||
beforeEach(function() {
|
||||
menu = $document.find('#dropdown-menu a');
|
||||
menu.focus();
|
||||
menu.trigger('click');
|
||||
});
|
||||
it('focuses the dropdown element on close', function() {
|
||||
expect(document.activeElement).toBe(toggle[0]);
|
||||
});
|
||||
it('removes the menu from body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
});
|
||||
describe('when the dropdown is removed', function() {
|
||||
beforeEach(function() {
|
||||
element.remove();
|
||||
$rootScope.$digest();
|
||||
});
|
||||
it('removes the menu from body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('adds the menu to the body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]);
|
||||
});
|
||||
describe('with a value', function() {
|
||||
function dropdown() {
|
||||
return $compile('<li uib-dropdown dropdown-append-to-body="appendToBody"><a href uib-dropdown-toggle></a><ul uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);
|
||||
}
|
||||
describe('that is not false', function() {
|
||||
beforeEach(function() {
|
||||
$rootScope.appendToBody = 'sure';
|
||||
|
||||
it('removes the menu when the dropdown is removed', function() {
|
||||
element.remove();
|
||||
$rootScope.$digest();
|
||||
expect($document.find('#dropdown-menu').length).toEqual(0);
|
||||
element = dropdown();
|
||||
$document.find('body').append(element);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
element.remove();
|
||||
});
|
||||
it('does not add the menu to the body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
|
||||
describe('when toggled open', function() {
|
||||
var toggle;
|
||||
beforeEach(function() {
|
||||
toggle = element.find('[uib-dropdown-toggle]');
|
||||
toggle.trigger('click');
|
||||
});
|
||||
it('adds the menu to the body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).toBe($document.find('body')[0]);
|
||||
});
|
||||
|
||||
describe('when toggled closed', function() {
|
||||
beforeEach(function() {
|
||||
toggle.trigger('click');
|
||||
});
|
||||
it('removes the menu from body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when closed by clicking on menu', function() {
|
||||
var menu;
|
||||
beforeEach(function() {
|
||||
menu = $document.find('#dropdown-menu a');
|
||||
menu.focus();
|
||||
menu.trigger('click');
|
||||
});
|
||||
it('focuses the dropdown element on close', function() {
|
||||
expect(document.activeElement).toBe(toggle[0]);
|
||||
});
|
||||
it('removes the menu from body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
});
|
||||
describe('when the dropdown is removed', function() {
|
||||
beforeEach(function() {
|
||||
element.remove();
|
||||
$rootScope.$digest();
|
||||
});
|
||||
it('removes the menu from body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('that is false', function() {
|
||||
beforeEach(function() {
|
||||
$rootScope.appendToBody = false;
|
||||
|
||||
element = dropdown();
|
||||
$document.find('body').append(element);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
element.remove();
|
||||
});
|
||||
|
||||
it('does not add the menu to the body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
|
||||
describe('when toggled open', function() {
|
||||
var toggle;
|
||||
beforeEach(function() {
|
||||
toggle = element.find('[uib-dropdown-toggle]');
|
||||
toggle.trigger('click');
|
||||
});
|
||||
it('does not add the menu to the body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
|
||||
describe('when toggled closed', function() {
|
||||
beforeEach(function() {
|
||||
toggle.trigger('click');
|
||||
});
|
||||
it('does not remove the menu', function() {
|
||||
expect($document.find('#dropdown-menu').length).not.toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when closed by clicking on menu', function() {
|
||||
var menu;
|
||||
beforeEach(function() {
|
||||
menu = $document.find('#dropdown-menu a');
|
||||
menu.focus();
|
||||
menu.trigger('click');
|
||||
});
|
||||
it('focuses the dropdown element on close', function() {
|
||||
expect(document.activeElement).toBe(toggle[0]);
|
||||
});
|
||||
it('does not removes the menu from body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
});
|
||||
describe('when the dropdown is removed', function() {
|
||||
beforeEach(function() {
|
||||
element.remove();
|
||||
$rootScope.$digest();
|
||||
});
|
||||
it('removes the menu from body', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('using dropdown-append-to', function() {
|
||||
var initialPage;
|
||||
var initialPage, container;
|
||||
|
||||
function dropdown() {
|
||||
return $compile('<li uib-dropdown dropdown-append-to="appendTo"><a href uib-dropdown-toggle></a><ul class="dropdown-menu" uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Container</a></li></ul></li>')($rootScope);
|
||||
@@ -247,7 +415,7 @@ describe('uib-dropdown', function() {
|
||||
beforeEach(function() {
|
||||
$document.find('body').append(angular.element('<div id="dropdown-container"></div>'));
|
||||
|
||||
$rootScope.appendTo = $document.find('#dropdown-container');
|
||||
$rootScope.appendTo = container = $document.find('#dropdown-container');
|
||||
|
||||
element = dropdown();
|
||||
$document.find('body').append(element);
|
||||
@@ -258,50 +426,95 @@ describe('uib-dropdown', function() {
|
||||
$document.find('#dropdown-container').remove();
|
||||
});
|
||||
|
||||
it('appends to container', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0].id).toBe('dropdown-container');
|
||||
it('does not add the menu to the container', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe(container[0]);
|
||||
});
|
||||
|
||||
it('toggles open class on container', function() {
|
||||
var container = $document.find('#dropdown-container');
|
||||
|
||||
expect(container).not.toHaveClass('uib-dropdown-open');
|
||||
element.find('[uib-dropdown-toggle]').click();
|
||||
expect(container).toHaveClass('uib-dropdown-open');
|
||||
element.find('[uib-dropdown-toggle]').click();
|
||||
it('does not add open class on container', function() {
|
||||
expect(container).not.toHaveClass('uib-dropdown-open');
|
||||
});
|
||||
|
||||
it('removes the menu when the dropdown is removed', function() {
|
||||
element.remove();
|
||||
$rootScope.$digest();
|
||||
expect($document.find('#dropdown-menu').length).toEqual(0);
|
||||
describe('when toggled open', function() {
|
||||
var toggle;
|
||||
beforeEach(function() {
|
||||
toggle = element.find('[uib-dropdown-toggle]');
|
||||
toggle.trigger('click');
|
||||
});
|
||||
it('adds the menu to the container', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).toBe(container[0]);
|
||||
});
|
||||
it('adds open class on container', function() {
|
||||
expect(container).toHaveClass('uib-dropdown-open');
|
||||
});
|
||||
|
||||
describe('when toggled closed', function() {
|
||||
beforeEach(function() {
|
||||
toggle.trigger('click');
|
||||
});
|
||||
it('removes the menu from the container', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
it('removes open class from container', function() {
|
||||
expect(container).not.toHaveClass('uib-dropdown-open');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when closed by clicking on menu', function() {
|
||||
var menu;
|
||||
beforeEach(function() {
|
||||
menu = $document.find('#dropdown-menu a');
|
||||
menu.focus();
|
||||
menu.trigger('click');
|
||||
});
|
||||
it('focuses the dropdown element on close', function() {
|
||||
expect(document.activeElement).toBe(toggle[0]);
|
||||
});
|
||||
it('removes the menu from the container', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
it('removes open class from container', function() {
|
||||
expect(container).not.toHaveClass('uib-dropdown-open');
|
||||
});
|
||||
});
|
||||
describe('when the dropdown is removed', function() {
|
||||
beforeEach(function() {
|
||||
element.remove();
|
||||
$rootScope.$digest();
|
||||
});
|
||||
it('removes the menu from the container', function() {
|
||||
expect($document.find('#dropdown-menu').parent()[0]).not.toBe($document.find('body')[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration with $location URL rewriting', function() {
|
||||
describe('using dropdown-append-to with two dropdowns', function() {
|
||||
function dropdown() {
|
||||
// Simulate URL rewriting behavior
|
||||
$document.on('click', 'a[href="#something"]', function() {
|
||||
$rootScope.$broadcast('$locationChangeSuccess');
|
||||
$rootScope.$apply();
|
||||
});
|
||||
|
||||
return $compile('<li uib-dropdown><a href uib-dropdown-toggle></a>' +
|
||||
'<ul><li><a href="#something">Hello</a></li></ul></li>')($rootScope);
|
||||
return $compile('<div><div class="dropdown1" uib-dropdown dropdown-append-to="appendTo" on-toggle="log(1, open)"><a href uib-dropdown-toggle></a><ul class="dropdown-menu" uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Container</a></li></ul></div><div class="dropdown2" uib-dropdown dropdown-append-to="appendTo" on-toggle="log(2, open)"><a href uib-dropdown-toggle></a><ul class="dropdown-menu" uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Container</a></li></ul></div></div>')($rootScope);
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
$document.find('body').append(angular.element('<div id="dropdown-container"></div>'));
|
||||
|
||||
$rootScope.appendTo = $document.find('#dropdown-container');
|
||||
$rootScope.log = jasmine.createSpy('log');
|
||||
|
||||
element = dropdown();
|
||||
$document.find('body').append(element);
|
||||
});
|
||||
|
||||
it('should close without errors on $location change', function() {
|
||||
$document.find('body').append(element);
|
||||
clickDropdownToggle();
|
||||
expect(element).toHaveClass(dropdownConfig.openClass);
|
||||
var optionEl = element.find('ul > li').eq(0).find('a').eq(0);
|
||||
optionEl.click();
|
||||
expect(element).not.toHaveClass(dropdownConfig.openClass);
|
||||
afterEach(function() {
|
||||
// Cleanup the extra elements we appended
|
||||
$document.find('#dropdown-container').remove();
|
||||
});
|
||||
|
||||
it('should keep the class when toggling from one dropdown to another with the same container', function() {
|
||||
var container = $document.find('#dropdown-container');
|
||||
|
||||
expect(container).not.toHaveClass('uib-dropdown-open');
|
||||
element.find('.dropdown1 [uib-dropdown-toggle]').click();
|
||||
expect(container).toHaveClass('uib-dropdown-open');
|
||||
element.find('.dropdown2 [uib-dropdown-toggle]').click();
|
||||
expect(container).toHaveClass('uib-dropdown-open');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -309,7 +522,7 @@ describe('uib-dropdown', function() {
|
||||
describe('with uib-dropdown-toggle', function() {
|
||||
beforeEach(function() {
|
||||
$rootScope.isopen = true;
|
||||
element = $compile('<li uib-dropdown is-open="isopen"><a href uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
|
||||
element = $compile('<li uib-dropdown is-open="isopen"><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -343,7 +556,7 @@ describe('uib-dropdown', function() {
|
||||
describe('without uib-dropdown-toggle', function() {
|
||||
beforeEach(function() {
|
||||
$rootScope.isopen = true;
|
||||
element = $compile('<li uib-dropdown is-open="isopen"><ul><li>Hello</li></ul></li>')($rootScope);
|
||||
element = $compile('<li uib-dropdown is-open="isopen"><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -364,7 +577,7 @@ describe('uib-dropdown', function() {
|
||||
beforeEach(function() {
|
||||
$rootScope.toggleHandler = jasmine.createSpy('toggleHandler');
|
||||
$rootScope.isopen = false;
|
||||
element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)" is-open="isopen"><a uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
|
||||
element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)" is-open="isopen"><a uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -391,7 +604,7 @@ describe('uib-dropdown', function() {
|
||||
beforeEach(function() {
|
||||
$rootScope.toggleHandler = jasmine.createSpy('toggleHandler');
|
||||
$rootScope.isopen = true;
|
||||
element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)" is-open="isopen"><a uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
|
||||
element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)" is-open="isopen"><a uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -419,7 +632,7 @@ describe('uib-dropdown', function() {
|
||||
describe('without is-open', function() {
|
||||
beforeEach(function() {
|
||||
$rootScope.toggleHandler = jasmine.createSpy('toggleHandler');
|
||||
element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)"><a uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
|
||||
element = $compile('<li uib-dropdown on-toggle="toggleHandler(open)"><a uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -447,7 +660,7 @@ describe('uib-dropdown', function() {
|
||||
function dropdown(autoClose) {
|
||||
return $compile('<li uib-dropdown ' +
|
||||
(autoClose === undefined ? '' : 'auto-close="' + autoClose + '"') +
|
||||
'><a href uib-dropdown-toggle></a><ul><li><a href>Hello</a></li></ul></li>')($rootScope);
|
||||
'><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li><a href>Hello</a></li></ul></li>')($rootScope);
|
||||
}
|
||||
|
||||
describe('always', function() {
|
||||
@@ -479,7 +692,7 @@ describe('uib-dropdown', function() {
|
||||
|
||||
it('control with is-open', function() {
|
||||
$rootScope.isopen = true;
|
||||
element = $compile('<li uib-dropdown is-open="isopen" auto-close="disabled"><a href uib-dropdown-toggle></a><ul><li>Hello</li></ul></li>')($rootScope);
|
||||
element = $compile('<li uib-dropdown is-open="isopen" auto-close="disabled"><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li>Hello</li></ul></li>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(element).toHaveClass(dropdownConfig.openClass);
|
||||
@@ -502,9 +715,10 @@ describe('uib-dropdown', function() {
|
||||
|
||||
it('should close anyway if esc is pressed', function() {
|
||||
element = dropdown('disabled');
|
||||
var dropdownMenu = element.find('[uib-dropdown-menu]');
|
||||
$document.find('body').append(element);
|
||||
clickDropdownToggle();
|
||||
triggerKeyDown($document, 27);
|
||||
triggerKeyDown(dropdownMenu, 27);
|
||||
expect(element).not.toHaveClass(dropdownConfig.openClass);
|
||||
expect(element.find('a')).toHaveFocus();
|
||||
});
|
||||
@@ -521,16 +735,6 @@ describe('uib-dropdown', function() {
|
||||
expect(elm1).not.toHaveClass(dropdownConfig.openClass);
|
||||
expect(elm2).toHaveClass(dropdownConfig.openClass);
|
||||
});
|
||||
|
||||
it('should not close on $locationChangeSuccess if auto-close="disabled"', function() {
|
||||
var elm1 = dropdown('disabled');
|
||||
expect(elm1).not.toHaveClass(dropdownConfig.openClass);
|
||||
clickDropdownToggle(elm1);
|
||||
expect(elm1).toHaveClass(dropdownConfig.openClass);
|
||||
$rootScope.$broadcast('$locationChangeSuccess');
|
||||
$rootScope.$digest();
|
||||
expect(elm1).toHaveClass(dropdownConfig.openClass);
|
||||
});
|
||||
});
|
||||
|
||||
describe('outsideClick', function() {
|
||||
@@ -559,7 +763,10 @@ describe('uib-dropdown', function() {
|
||||
|
||||
describe('using keyboard-nav', function() {
|
||||
function dropdown() {
|
||||
return $compile('<li uib-dropdown keyboard-nav><a href uib-dropdown-toggle></a><ul><li><a href>Hello</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);
|
||||
return $compile('<li uib-dropdown keyboard-nav><a href uib-dropdown-toggle></a><ul uib-dropdown-menu><li><a href>Hello</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);
|
||||
}
|
||||
function getFocusedElement() {
|
||||
return angular.element(document.activeElement);
|
||||
}
|
||||
beforeEach(function() {
|
||||
element = dropdown();
|
||||
@@ -568,7 +775,7 @@ describe('uib-dropdown', function() {
|
||||
it('should focus first list element when down arrow pressed', function() {
|
||||
$document.find('body').append(element);
|
||||
clickDropdownToggle();
|
||||
triggerKeyDown($document, 40);
|
||||
triggerKeyDown(getFocusedElement(), 40);
|
||||
|
||||
expect(element).toHaveClass(dropdownConfig.openClass);
|
||||
var optionEl = element.find('ul').eq(0).find('a').eq(0);
|
||||
@@ -577,7 +784,7 @@ describe('uib-dropdown', function() {
|
||||
|
||||
it('should not focus first list element when down arrow pressed if closed', function() {
|
||||
$document.find('body').append(element);
|
||||
triggerKeyDown($document, 40);
|
||||
triggerKeyDown(getFocusedElement(), 40);
|
||||
|
||||
expect(element).not.toHaveClass(dropdownConfig.openClass);
|
||||
var focusEl = element.find('ul').eq(0).find('a').eq(0);
|
||||
@@ -587,8 +794,8 @@ describe('uib-dropdown', function() {
|
||||
it('should focus second list element when down arrow pressed twice', function() {
|
||||
$document.find('body').append(element);
|
||||
clickDropdownToggle();
|
||||
triggerKeyDown($document, 40);
|
||||
triggerKeyDown($document, 40);
|
||||
triggerKeyDown(getFocusedElement(), 40);
|
||||
triggerKeyDown(getFocusedElement(), 40);
|
||||
|
||||
expect(element).toHaveClass(dropdownConfig.openClass);
|
||||
var focusEl = element.find('ul').eq(0).find('a').eq(1);
|
||||
@@ -600,7 +807,7 @@ describe('uib-dropdown', function() {
|
||||
clickDropdownToggle();
|
||||
expect(element).toHaveClass(dropdownConfig.openClass);
|
||||
|
||||
triggerKeyDown($document, 38);
|
||||
triggerKeyDown(getFocusedElement(), 38);
|
||||
var focusEl = element.find('ul').eq(0).find('a').eq(0);
|
||||
expect(focusEl).not.toHaveFocus();
|
||||
});
|
||||
@@ -608,7 +815,7 @@ describe('uib-dropdown', function() {
|
||||
it('should focus last list element when up arrow pressed after dropdown toggled', function() {
|
||||
$document.find('body').append(element);
|
||||
clickDropdownToggle();
|
||||
triggerKeyDown($document, 38);
|
||||
triggerKeyDown(getFocusedElement(), 38);
|
||||
|
||||
expect(element).toHaveClass(dropdownConfig.openClass);
|
||||
var focusEl = element.find('ul').eq(0).find('a').eq(1);
|
||||
@@ -618,7 +825,7 @@ describe('uib-dropdown', function() {
|
||||
it('should not change focus when other keys are pressed', function() {
|
||||
$document.find('body').append(element);
|
||||
clickDropdownToggle();
|
||||
triggerKeyDown($document, 37);
|
||||
triggerKeyDown(getFocusedElement(), 37);
|
||||
|
||||
expect(element).toHaveClass(dropdownConfig.openClass);
|
||||
var focusEl = element.find('ul').eq(0).find('a');
|
||||
@@ -629,10 +836,10 @@ describe('uib-dropdown', function() {
|
||||
it('should focus first list element when down arrow pressed 2x and up pressed 1x', function() {
|
||||
$document.find('body').append(element);
|
||||
clickDropdownToggle();
|
||||
triggerKeyDown($document, 40);
|
||||
triggerKeyDown($document, 40);
|
||||
triggerKeyDown(getFocusedElement(), 40);
|
||||
triggerKeyDown(getFocusedElement(), 40);
|
||||
|
||||
triggerKeyDown($document, 38);
|
||||
triggerKeyDown(getFocusedElement(), 38);
|
||||
|
||||
expect(element).toHaveClass(dropdownConfig.openClass);
|
||||
var focusEl = element.find('ul').eq(0).find('a').eq(0);
|
||||
@@ -642,14 +849,14 @@ describe('uib-dropdown', function() {
|
||||
it('should stay focused on final list element if down pressed at list end', function() {
|
||||
$document.find('body').append(element);
|
||||
clickDropdownToggle();
|
||||
triggerKeyDown($document, 40);
|
||||
triggerKeyDown($document, 40);
|
||||
triggerKeyDown(getFocusedElement(), 40);
|
||||
triggerKeyDown(getFocusedElement(), 40);
|
||||
|
||||
expect(element).toHaveClass(dropdownConfig.openClass);
|
||||
var focusEl = element.find('ul').eq(0).find('a').eq(1);
|
||||
expect(focusEl).toHaveFocus();
|
||||
|
||||
triggerKeyDown($document, 40);
|
||||
triggerKeyDown(element, 40);
|
||||
expect(focusEl).toHaveFocus();
|
||||
});
|
||||
|
||||
@@ -658,19 +865,19 @@ describe('uib-dropdown', function() {
|
||||
$document.find('body').append(element);
|
||||
clickDropdownToggle();
|
||||
|
||||
triggerKeyDown($document, 40);
|
||||
triggerKeyDown(getFocusedElement(), 40);
|
||||
|
||||
expect(element).toHaveClass(dropdownConfig.openClass);
|
||||
var focusEl = element.find('ul').eq(0).find('a').eq(0);
|
||||
expect(focusEl).toHaveFocus();
|
||||
|
||||
triggerKeyDown($document, 27);
|
||||
triggerKeyDown(getFocusedElement(), 27);
|
||||
expect(element).not.toHaveClass(dropdownConfig.openClass);
|
||||
});
|
||||
|
||||
describe('with dropdown-append-to-body', function() {
|
||||
function dropdown() {
|
||||
return $compile('<li uib-dropdown dropdown-append-to-body keyboard-nav><a href uib-dropdown-toggle></a><ul uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Body</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);
|
||||
return $compile('<li uib-dropdown dropdown-append-to-body keyboard-nav><a href uib-dropdown-toggle>foo</a><ul uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Body</a></li><li><a href>Hello Again</a></li></ul></li>')($rootScope);
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
@@ -678,23 +885,25 @@ describe('uib-dropdown', function() {
|
||||
});
|
||||
|
||||
it('should focus first list element when down arrow pressed', function() {
|
||||
$document.find('body').append(element);
|
||||
clickDropdownToggle();
|
||||
|
||||
triggerKeyDown($document, 40);
|
||||
|
||||
var dropdownMenu = $document.find('#dropdown-menu');
|
||||
|
||||
triggerKeyDown(getFocusedElement(), 40);
|
||||
|
||||
expect(dropdownMenu.parent()).toHaveClass(dropdownConfig.appendToOpenClass);
|
||||
var focusEl = $document.find('ul').eq(0).find('a');
|
||||
expect(focusEl).toHaveFocus();
|
||||
});
|
||||
|
||||
it('should focus second list element when down arrow pressed twice', function() {
|
||||
$document.find('body').append(element);
|
||||
clickDropdownToggle();
|
||||
triggerKeyDown($document, 40);
|
||||
triggerKeyDown($document, 40);
|
||||
|
||||
var dropdownMenu = $document.find('#dropdown-menu');
|
||||
triggerKeyDown(getFocusedElement(), 40);
|
||||
triggerKeyDown(getFocusedElement(), 40);
|
||||
triggerKeyDown(getFocusedElement(), 40);
|
||||
|
||||
expect(dropdownMenu.parent()).toHaveClass(dropdownConfig.appendToOpenClass);
|
||||
var elem1 = $document.find('ul');
|
||||
@@ -704,4 +913,34 @@ describe('uib-dropdown', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// issue #5942
|
||||
describe('using dropdown-append-to-body with dropdown-menu-right class', function() {
|
||||
function dropdown() {
|
||||
return $compile('<li style="float: right;" uib-dropdown dropdown-append-to-body><a href uib-dropdown-toggle>Toggle menu</a><ul uib-dropdown-menu class="dropdown-menu-right" id="dropdown-menu"><li><a href>Hello On Body</a></li></ul></li>')($rootScope);
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
element = dropdown();
|
||||
$document.find('body').append(element);
|
||||
|
||||
var menu = $document.find('#dropdown-menu');
|
||||
menu.css('position', 'absolute');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
element.remove();
|
||||
});
|
||||
|
||||
it('should align the menu correctly when the body has no vertical scrollbar', function() {
|
||||
var toggle = element.find('[uib-dropdown-toggle]');
|
||||
var menu = $document.find('#dropdown-menu');
|
||||
toggle.trigger('click');
|
||||
|
||||
// Get the offsets of the rightmost position of both the toggle and the menu (offset from the left of the window)
|
||||
var toggleRight = Math.round(toggle.offset().left + toggle.outerWidth());
|
||||
var menuRight = Math.round(menu.offset().left + menu.outerWidth());
|
||||
expect(menuRight).toBe(toggleRight);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ function ($animate) {
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function (tElement, tAttrs) {
|
||||
compile: function(tElement, tAttrs) {
|
||||
var linkedScopes = [];
|
||||
var instances = [];
|
||||
var expToData = {};
|
||||
@@ -32,7 +32,7 @@ function ($animate) {
|
||||
element: element
|
||||
});
|
||||
|
||||
exps.forEach(function (exp, k) {
|
||||
exps.forEach(function(exp, k) {
|
||||
addForExp(exp, scope);
|
||||
});
|
||||
|
||||
@@ -45,9 +45,9 @@ function ($animate) {
|
||||
var compareWithExp = matches[2];
|
||||
var data = expToData[exp];
|
||||
if (!data) {
|
||||
var watchFn = function (compareWithVal) {
|
||||
var watchFn = function(compareWithVal) {
|
||||
var newActivated = null;
|
||||
instances.some(function (instance) {
|
||||
instances.some(function(instance) {
|
||||
var thisVal = instance.scope.$eval(onExp);
|
||||
if (thisVal === compareWithVal) {
|
||||
newActivated = instance;
|
||||
@@ -82,14 +82,13 @@ function ($animate) {
|
||||
instances.splice(index, 1);
|
||||
if (linkedScopes.length) {
|
||||
var newWatchScope = linkedScopes[0];
|
||||
angular.forEach(expToData, function (data) {
|
||||
angular.forEach(expToData, function(data) {
|
||||
if (data.scope === removedScope) {
|
||||
data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
|
||||
data.scope = newWatchScope;
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
expToData = {};
|
||||
}
|
||||
}
|
||||
|
||||
+33
-14
@@ -1,25 +1,44 @@
|
||||
<div ng-controller="ModalDemoCtrl">
|
||||
<div ng-controller="ModalDemoCtrl as $ctrl" class="modal-demo">
|
||||
<script type="text/ng-template" id="myModalContent.html">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">I'm a modal!</h3>
|
||||
<h3 class="modal-title" id="modal-title">I'm a modal!</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body" id="modal-body">
|
||||
<ul>
|
||||
<li ng-repeat="item in items">
|
||||
<a href="#" ng-click="$event.preventDefault(); selected.item = item">{{ item }}</a>
|
||||
<li ng-repeat="item in $ctrl.items">
|
||||
<a href="#" ng-click="$event.preventDefault(); $ctrl.selected.item = item">{{ item }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
Selected: <b>{{ selected.item }}</b>
|
||||
Selected: <b>{{ $ctrl.selected.item }}</b>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" type="button" ng-click="ok()">OK</button>
|
||||
<button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>
|
||||
<button class="btn btn-primary" type="button" ng-click="$ctrl.ok()">OK</button>
|
||||
<button class="btn btn-warning" type="button" ng-click="$ctrl.cancel()">Cancel</button>
|
||||
</div>
|
||||
</script>
|
||||
<script type="text/ng-template" id="stackedModal.html">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" id="modal-title-{{name}}">The {{name}} modal!</h3>
|
||||
</div>
|
||||
<div class="modal-body" id="modal-body-{{name}}">
|
||||
Having multiple modals open at once is probably bad UX but it's technically possible.
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<button type="button" class="btn btn-default" ng-click="open()">Open me!</button>
|
||||
<button type="button" class="btn btn-default" ng-click="open('lg')">Large modal</button>
|
||||
<button type="button" class="btn btn-default" ng-click="open('sm')">Small modal</button>
|
||||
<button type="button" class="btn btn-default" ng-click="toggleAnimation()">Toggle Animation ({{ animationsEnabled }})</button>
|
||||
<div ng-show="selected">Selection from a modal: {{ selected }}</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-default" ng-click="$ctrl.open()">Open me!</button>
|
||||
<button type="button" class="btn btn-default" ng-click="$ctrl.open('lg')">Large modal</button>
|
||||
<button type="button" class="btn btn-default" ng-click="$ctrl.open('sm')">Small modal</button>
|
||||
<button type="button"
|
||||
class="btn btn-default"
|
||||
ng-click="$ctrl.open('sm', '.modal-parent')">
|
||||
Modal appended to a custom parent
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" ng-click="$ctrl.toggleAnimation()">Toggle Animation ({{ $ctrl.animationsEnabled }})</button>
|
||||
<button type="button" class="btn btn-default" ng-click="$ctrl.openComponentModal()">Open a component modal!</button>
|
||||
<button type="button" class="btn btn-default" ng-click="$ctrl.openMultipleModals()">
|
||||
Open multiple modals at once
|
||||
</button>
|
||||
<div ng-show="$ctrl.selected">Selection from a modal: {{ $ctrl.selected }}</div>
|
||||
<div class="modal-parent">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+94
-19
@@ -1,51 +1,126 @@
|
||||
angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope, $uibModal, $log) {
|
||||
angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($uibModal, $log, $document) {
|
||||
var $ctrl = this;
|
||||
$ctrl.items = ['item1', 'item2', 'item3'];
|
||||
|
||||
$scope.items = ['item1', 'item2', 'item3'];
|
||||
|
||||
$scope.animationsEnabled = true;
|
||||
|
||||
$scope.open = function (size) {
|
||||
$ctrl.animationsEnabled = true;
|
||||
|
||||
$ctrl.open = function (size, parentSelector) {
|
||||
var parentElem = parentSelector ?
|
||||
angular.element($document[0].querySelector('.modal-demo ' + parentSelector)) : undefined;
|
||||
var modalInstance = $uibModal.open({
|
||||
animation: $scope.animationsEnabled,
|
||||
animation: $ctrl.animationsEnabled,
|
||||
ariaLabelledBy: 'modal-title',
|
||||
ariaDescribedBy: 'modal-body',
|
||||
templateUrl: 'myModalContent.html',
|
||||
controller: 'ModalInstanceCtrl',
|
||||
controllerAs: '$ctrl',
|
||||
size: size,
|
||||
appendTo: parentElem,
|
||||
resolve: {
|
||||
items: function () {
|
||||
return $scope.items;
|
||||
return $ctrl.items;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
modalInstance.result.then(function (selectedItem) {
|
||||
$scope.selected = selectedItem;
|
||||
$ctrl.selected = selectedItem;
|
||||
}, function () {
|
||||
$log.info('Modal dismissed at: ' + new Date());
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggleAnimation = function () {
|
||||
$scope.animationsEnabled = !$scope.animationsEnabled;
|
||||
$ctrl.openComponentModal = function () {
|
||||
var modalInstance = $uibModal.open({
|
||||
animation: $ctrl.animationsEnabled,
|
||||
component: 'modalComponent',
|
||||
resolve: {
|
||||
items: function () {
|
||||
return $ctrl.items;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
modalInstance.result.then(function (selectedItem) {
|
||||
$ctrl.selected = selectedItem;
|
||||
}, function () {
|
||||
$log.info('modal-component dismissed at: ' + new Date());
|
||||
});
|
||||
};
|
||||
|
||||
$ctrl.openMultipleModals = function () {
|
||||
$uibModal.open({
|
||||
animation: $ctrl.animationsEnabled,
|
||||
ariaLabelledBy: 'modal-title-bottom',
|
||||
ariaDescribedBy: 'modal-body-bottom',
|
||||
templateUrl: 'stackedModal.html',
|
||||
size: 'sm',
|
||||
controller: function($scope) {
|
||||
$scope.name = 'bottom';
|
||||
}
|
||||
});
|
||||
|
||||
$uibModal.open({
|
||||
animation: $ctrl.animationsEnabled,
|
||||
ariaLabelledBy: 'modal-title-top',
|
||||
ariaDescribedBy: 'modal-body-top',
|
||||
templateUrl: 'stackedModal.html',
|
||||
size: 'sm',
|
||||
controller: function($scope) {
|
||||
$scope.name = 'top';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$ctrl.toggleAnimation = function () {
|
||||
$ctrl.animationsEnabled = !$ctrl.animationsEnabled;
|
||||
};
|
||||
});
|
||||
|
||||
// Please note that $uibModalInstance represents a modal window (instance) dependency.
|
||||
// It is not the same as the $uibModal service used above.
|
||||
|
||||
angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($scope, $uibModalInstance, items) {
|
||||
|
||||
$scope.items = items;
|
||||
$scope.selected = {
|
||||
item: $scope.items[0]
|
||||
angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($uibModalInstance, items) {
|
||||
var $ctrl = this;
|
||||
$ctrl.items = items;
|
||||
$ctrl.selected = {
|
||||
item: $ctrl.items[0]
|
||||
};
|
||||
|
||||
$scope.ok = function () {
|
||||
$uibModalInstance.close($scope.selected.item);
|
||||
$ctrl.ok = function () {
|
||||
$uibModalInstance.close($ctrl.selected.item);
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$ctrl.cancel = function () {
|
||||
$uibModalInstance.dismiss('cancel');
|
||||
};
|
||||
});
|
||||
|
||||
// Please note that the close and dismiss bindings are from $uibModalInstance.
|
||||
|
||||
angular.module('ui.bootstrap.demo').component('modalComponent', {
|
||||
templateUrl: 'myModalContent.html',
|
||||
bindings: {
|
||||
resolve: '<',
|
||||
close: '&',
|
||||
dismiss: '&'
|
||||
},
|
||||
controller: function () {
|
||||
var $ctrl = this;
|
||||
|
||||
$ctrl.$onInit = function () {
|
||||
$ctrl.items = $ctrl.resolve.items;
|
||||
$ctrl.selected = {
|
||||
item: $ctrl.items[0]
|
||||
};
|
||||
};
|
||||
|
||||
$ctrl.ok = function () {
|
||||
$ctrl.close({$value: $ctrl.selected.item});
|
||||
};
|
||||
|
||||
$ctrl.cancel = function () {
|
||||
$ctrl.dismiss({$value: 'cancel'});
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
+36
-12
@@ -1,5 +1,5 @@
|
||||
`$uibModal` is a service to create modal windows.
|
||||
Creating modals is straightforward: create a template, a controller and reference them when using `$uibModal`.
|
||||
Creating modals is straightforward: create a template and controller, and reference them when using `$uibModal`.
|
||||
|
||||
The `$uibModal` service has only one method: `open(options)`.
|
||||
|
||||
@@ -10,15 +10,23 @@ The `$uibModal` service has only one method: `open(options)`.
|
||||
* `animation`
|
||||
_(Type: `boolean`, Default: `true`)_ -
|
||||
Set to false to disable animations on new modal/backdrop. Does not toggle animations for modals/backdrops that are already displayed.
|
||||
|
||||
* `appendTo`
|
||||
|
||||
* `appendTo`
|
||||
_(Type: `angular.element`, Default: `body`: Example: `$document.find('aside').eq(0)`)_ -
|
||||
Appends the modal to a specific element.
|
||||
|
||||
|
||||
* `ariaDescribedBy`
|
||||
_(Type: `string`, `my-modal-description`)_ -
|
||||
Sets the [`aria-describedby`](https://www.w3.org/TR/wai-aria/states_and_properties#aria-describedby) property on the modal. The value should be an id (without the leading `#`) pointing to the element that describes your modal. Typically, this will be the text on your modal, but does not include something the user would interact with, like buttons or a form. Omitting this option will not impact sighted users but will weaken your accessibility support.
|
||||
|
||||
* `ariaLabelledBy`
|
||||
_(Type: `string`, `my-modal-title`)_ -
|
||||
Sets the [`aria-labelledby`](https://www.w3.org/TR/wai-aria/states_and_properties#aria-labelledby) property on the modal. The value should be an id (without the leading `#`) pointing to the element that labels your modal. Typically, this will be a header element. Omitting this option will not impact sighted users but will weaken your accessibility support.
|
||||
|
||||
* `backdrop`
|
||||
_(Type: `boolean|string`, Default: `true`)_ -
|
||||
Controls presence of a backdrop. Allowed values: `true` (default), `false` (no backdrop), `'static'` (disables modal closing by click on the backdrop).
|
||||
|
||||
|
||||
* `backdropClass`
|
||||
_(Type: `string`)_ -
|
||||
Additional CSS class(es) to be added to a modal backdrop template.
|
||||
@@ -27,15 +35,29 @@ The `$uibModal` service has only one method: `open(options)`.
|
||||
_(Type: `boolean`, Default: `false`)_ -
|
||||
When used with `controllerAs` & set to `true`, it will bind the $scope properties onto the controller.
|
||||
|
||||
* `component`
|
||||
_(Type: `string`, Example: `myComponent`)_ -
|
||||
A string reference to the component to be rendered that is registered with Angular's compiler. If using a directive, the directive must have `restrict: 'E'` and a template or templateUrl set.
|
||||
|
||||
It supports these bindings:
|
||||
|
||||
* `close` - A method that can be used to close a modal, passing a result. The result must be passed in this format: `{$value: myResult}`
|
||||
|
||||
* `dismiss` - A method that can be used to dismiss a modal, passing a result. The result must be passed in this format: `{$value: myRejectedResult}`
|
||||
|
||||
* `modalInstance` - The modal instance. This is the same `$uibModalInstance` injectable found when using `controller`.
|
||||
|
||||
* `resolve` - An object of the modal resolve values. See [UI Router resolves](#ui-router-resolves) for details.
|
||||
|
||||
* `controller`
|
||||
_(Type: `function|string|array`, Example: `MyModalController`)_ -
|
||||
A controller for the modal instance, either a controller name as a string, or an inline controller function, optionally wrapped in array notation for dependency injection. Allows the controller-as syntax. Has a special `$uibModalInstance` injectable to access the modal instance.
|
||||
|
||||
* `controllerAs`
|
||||
_(Type: `string`, Example: `ctrl`)_ -
|
||||
_(Type: `string`, Example: `ctrl`)_ -
|
||||
An alternative to the controller-as syntax. Requires the `controller` option to be provided as well.
|
||||
|
||||
* `keyboard` -
|
||||
* `keyboard` -
|
||||
_(Type: `boolean`, Default: `true`)_ -
|
||||
Indicates whether the dialog should be closable by hitting the ESC key.
|
||||
|
||||
@@ -76,7 +98,7 @@ The `$uibModal` service has only one method: `open(options)`.
|
||||
CSS class(es) to be added to the top modal window.
|
||||
|
||||
Global defaults may be set for `$uibModal` via `$uibModalProvider.options`.
|
||||
|
||||
|
||||
#### return
|
||||
|
||||
The `open` method returns a modal instance, an object with the following properties:
|
||||
@@ -103,8 +125,8 @@ The `open` method returns a modal instance, an object with the following propert
|
||||
|
||||
* `rendered`
|
||||
_(Type: `promise`)_ -
|
||||
Is resolved when a modal is rendered.
|
||||
|
||||
Is resolved when a modal is rendered.
|
||||
|
||||
---
|
||||
|
||||
The scope associated with modal's content is augmented with:
|
||||
@@ -125,9 +147,9 @@ Also, when using `bindToController`, you can define an `$onInit` method in the c
|
||||
|
||||
Events fired:
|
||||
|
||||
* `$uibUnscheduledDestruction` -
|
||||
* `$uibUnscheduledDestruction` -
|
||||
This event is fired if the $scope is destroyed via unexpected mechanism, such as it being passed in the modal options and a $route/$state transition occurs. The modal will also be dismissed.
|
||||
|
||||
|
||||
* `modal.closing` -
|
||||
This event is broadcast to the modal scope before the modal closes. If the listener calls preventDefault() on the event, then the modal will remain open.
|
||||
Also, the `$close` and `$dismiss` methods returns true if the event was executed. This event also includes a parameter for the result/reason and a boolean that indicates whether the modal is being closed (true) or dismissed.
|
||||
@@ -135,3 +157,5 @@ Events fired:
|
||||
##### UI Router resolves
|
||||
|
||||
If one wants to have the modal resolve using [UI Router's](https://github.com/angular-ui/ui-router) pre-1.0 resolve mechanism, one can call `$uibResolve.setResolver('$resolve')` in the configuration phase of the application. One can also provide a custom resolver as well, as long as the signature conforms to UI Router's [$resolve](http://angular-ui.github.io/ui-router/site/#/api/ui.router.util.$resolve).
|
||||
|
||||
When the modal is opened with a controller, a `$resolve` object is exposed on the template with the resolved values from the resolve object. If using the component option, see details on how to access this object in component section of the modal documentation.
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
require('../multiMap');
|
||||
require('../position/index-nocss.js');
|
||||
require('../stackedMap');
|
||||
require('../../template/modal/window.html.js');
|
||||
require('./modal');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.modal';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.modal', 'uib/template/modal/window.html']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
+2
-10
@@ -1,10 +1,2 @@
|
||||
require('../stackedMap');
|
||||
require('../../template/modal/backdrop.html.js');
|
||||
require('../../template/modal/window.html.js');
|
||||
require('./modal');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.modal';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.modal', 'uib/template/modal/backdrop.html', 'uib/template/modal/window.html']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
require('../position/position.css');
|
||||
module.exports = require('./index-nocss.js');
|
||||
|
||||
+263
-168
@@ -1,59 +1,4 @@
|
||||
angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
/**
|
||||
* A helper, internal data structure that stores all references attached to key
|
||||
*/
|
||||
.factory('$$multiMap', function() {
|
||||
return {
|
||||
createNew: function() {
|
||||
var map = {};
|
||||
|
||||
return {
|
||||
entries: function() {
|
||||
return Object.keys(map).map(function(key) {
|
||||
return {
|
||||
key: key,
|
||||
value: map[key]
|
||||
};
|
||||
});
|
||||
},
|
||||
get: function(key) {
|
||||
return map[key];
|
||||
},
|
||||
hasKey: function(key) {
|
||||
return !!map[key];
|
||||
},
|
||||
keys: function() {
|
||||
return Object.keys(map);
|
||||
},
|
||||
put: function(key, value) {
|
||||
if (!map[key]) {
|
||||
map[key] = [];
|
||||
}
|
||||
|
||||
map[key].push(value);
|
||||
},
|
||||
remove: function(key, value) {
|
||||
var values = map[key];
|
||||
|
||||
if (!values) {
|
||||
return;
|
||||
}
|
||||
|
||||
var idx = values.indexOf(value);
|
||||
|
||||
if (idx !== -1) {
|
||||
values.splice(idx, 1);
|
||||
}
|
||||
|
||||
if (!values.length) {
|
||||
delete map[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
angular.module('ui.bootstrap.modal', ['ui.bootstrap.multiMap', 'ui.bootstrap.stackedMap', 'ui.bootstrap.position'])
|
||||
/**
|
||||
* Pluggable resolve mechanism for the modal resolve resolution
|
||||
* Supports UI Router's $resolve service
|
||||
@@ -106,8 +51,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
.directive('uibModalBackdrop', ['$animate', '$injector', '$uibModalStack',
|
||||
function($animate, $injector, $modalStack) {
|
||||
return {
|
||||
replace: true,
|
||||
templateUrl: 'uib/template/modal/backdrop.html',
|
||||
restrict: 'A',
|
||||
compile: function(tElement, tAttrs) {
|
||||
tElement.addClass(tAttrs.backdropClass);
|
||||
return linkFn;
|
||||
@@ -136,13 +80,12 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
scope: {
|
||||
index: '@'
|
||||
},
|
||||
replace: true,
|
||||
restrict: 'A',
|
||||
transclude: true,
|
||||
templateUrl: function(tElement, tAttrs) {
|
||||
return tAttrs.templateUrl || 'uib/template/modal/window.html';
|
||||
},
|
||||
link: function(scope, element, attrs) {
|
||||
element.addClass(attrs.windowClass || '');
|
||||
element.addClass(attrs.windowTopClass || '');
|
||||
scope.size = attrs.size;
|
||||
|
||||
@@ -165,14 +108,11 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
// {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
|
||||
scope.$isRendered = true;
|
||||
|
||||
// Deferred object that will be resolved when this modal is render.
|
||||
// Deferred object that will be resolved when this modal is rendered.
|
||||
var modalRenderDeferObj = $q.defer();
|
||||
// Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
|
||||
// In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
|
||||
attrs.$observe('modalRender', function(value) {
|
||||
if (value === 'true') {
|
||||
modalRenderDeferObj.resolve();
|
||||
}
|
||||
// Resolve render promise post-digest
|
||||
scope.$$postDigest(function() {
|
||||
modalRenderDeferObj.resolve();
|
||||
});
|
||||
|
||||
modalRenderDeferObj.promise.then(function() {
|
||||
@@ -201,7 +141,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
|
||||
/**
|
||||
* If something within the freshly-opened modal already has focus (perhaps via a
|
||||
* directive that causes focus). then no need to try and focus anything.
|
||||
* directive that causes focus) then there's no need to try to focus anything.
|
||||
*/
|
||||
if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
|
||||
var inputWithAutofocus = element[0].querySelector('[autofocus]');
|
||||
@@ -235,20 +175,20 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
};
|
||||
})
|
||||
|
||||
.directive('uibModalTransclude', function() {
|
||||
.directive('uibModalTransclude', ['$animate', function($animate) {
|
||||
return {
|
||||
link: function(scope, element, attrs, controller, transclude) {
|
||||
transclude(scope.$parent, function(clone) {
|
||||
element.empty();
|
||||
element.append(clone);
|
||||
$animate.enter(clone, element);
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
}])
|
||||
|
||||
.factory('$uibModalStack', ['$animate', '$animateCss', '$document',
|
||||
'$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap',
|
||||
function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap) {
|
||||
'$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition',
|
||||
function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) {
|
||||
var OPENED_MODAL_CLASS = 'modal-open';
|
||||
|
||||
var backdropDomEl, backdropScope;
|
||||
@@ -257,13 +197,30 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
var $modalStack = {
|
||||
NOW_CLOSING_EVENT: 'modal.stack.now-closing'
|
||||
};
|
||||
var topModalIndex = 0;
|
||||
var previousTopOpenedModal = null;
|
||||
var ARIA_HIDDEN_ATTRIBUTE_NAME = 'data-bootstrap-modal-aria-hidden-count';
|
||||
|
||||
//Modal focus behavior
|
||||
var focusableElementList;
|
||||
var focusIndex = 0;
|
||||
var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
|
||||
'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
|
||||
'iframe, object, embed, *[tabindex], *[contenteditable=true]';
|
||||
var tabbableSelector = 'a[href], area[href], input:not([disabled]):not([tabindex=\'-1\']), ' +
|
||||
'button:not([disabled]):not([tabindex=\'-1\']),select:not([disabled]):not([tabindex=\'-1\']), textarea:not([disabled]):not([tabindex=\'-1\']), ' +
|
||||
'iframe, object, embed, *[tabindex]:not([tabindex=\'-1\']), *[contenteditable=true]';
|
||||
var scrollbarPadding;
|
||||
var SNAKE_CASE_REGEXP = /[A-Z]/g;
|
||||
|
||||
// TODO: extract into common dependency with tooltip
|
||||
function snake_case(name) {
|
||||
var separator = '-';
|
||||
return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
|
||||
return (pos ? separator : '') + letter.toLowerCase();
|
||||
});
|
||||
}
|
||||
|
||||
function isVisible(element) {
|
||||
return !!(element.offsetWidth ||
|
||||
element.offsetHeight ||
|
||||
element.getClientRects().length);
|
||||
}
|
||||
|
||||
function backdropIndex() {
|
||||
var topBackdropIndex = -1;
|
||||
@@ -273,6 +230,12 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
topBackdropIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// If any backdrop exist, ensure that it's index is always
|
||||
// right below the top modal
|
||||
if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) {
|
||||
topBackdropIndex = topModalIndex;
|
||||
}
|
||||
return topBackdropIndex;
|
||||
}
|
||||
|
||||
@@ -288,11 +251,24 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
|
||||
//clean up the stack
|
||||
openedWindows.remove(modalInstance);
|
||||
previousTopOpenedModal = openedWindows.top();
|
||||
if (previousTopOpenedModal) {
|
||||
topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10);
|
||||
}
|
||||
|
||||
removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
|
||||
var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
|
||||
openedClasses.remove(modalBodyClass, modalInstance);
|
||||
appendToElement.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
|
||||
var areAnyOpen = openedClasses.hasKey(modalBodyClass);
|
||||
appendToElement.toggleClass(modalBodyClass, areAnyOpen);
|
||||
if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
|
||||
if (scrollbarPadding.originalRight) {
|
||||
appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'});
|
||||
} else {
|
||||
appendToElement.css({paddingRight: ''});
|
||||
}
|
||||
scrollbarPadding = null;
|
||||
}
|
||||
toggleTopWindowClass(true);
|
||||
}, modalWindow.closedDeferred);
|
||||
checkRemoveBackdrop();
|
||||
@@ -354,6 +330,10 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
afterAnimating.done = true;
|
||||
|
||||
$animate.leave(domEl).then(function() {
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
|
||||
domEl.remove();
|
||||
if (closedDeferred) {
|
||||
closedDeferred.resolve();
|
||||
@@ -361,9 +341,6 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
});
|
||||
|
||||
scope.$destroy();
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,15 +368,15 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
break;
|
||||
}
|
||||
case 9: {
|
||||
$modalStack.loadFocusElementList(modal);
|
||||
var list = $modalStack.loadFocusElementList(modal);
|
||||
var focusChanged = false;
|
||||
if (evt.shiftKey) {
|
||||
if ($modalStack.isFocusInFirstItem(evt) || $modalStack.isModalFocused(evt, modal)) {
|
||||
focusChanged = $modalStack.focusLastFocusableElement();
|
||||
if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) {
|
||||
focusChanged = $modalStack.focusLastFocusableElement(list);
|
||||
}
|
||||
} else {
|
||||
if ($modalStack.isFocusInLastItem(evt)) {
|
||||
focusChanged = $modalStack.focusFirstFocusableElement();
|
||||
if ($modalStack.isFocusInLastItem(evt, list)) {
|
||||
focusChanged = $modalStack.focusFirstFocusableElement(list);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,6 +384,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -419,6 +397,10 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
|
||||
toggleTopWindowClass(false);
|
||||
|
||||
// Store the current top first, to determine what index we ought to use
|
||||
// for the current top modal
|
||||
previousTopOpenedModal = openedWindows.top();
|
||||
|
||||
openedWindows.add(modalInstance, {
|
||||
deferred: modal.deferred,
|
||||
renderDeferred: modal.renderDeferred,
|
||||
@@ -437,66 +419,152 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
var appendToElement = modal.appendTo,
|
||||
currBackdropIndex = backdropIndex();
|
||||
|
||||
if (!appendToElement.length) {
|
||||
throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
|
||||
}
|
||||
|
||||
if (currBackdropIndex >= 0 && !backdropDomEl) {
|
||||
backdropScope = $rootScope.$new(true);
|
||||
backdropScope.modalOptions = modal;
|
||||
backdropScope.index = currBackdropIndex;
|
||||
backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
|
||||
backdropDomEl.attr('backdrop-class', modal.backdropClass);
|
||||
backdropDomEl.attr({
|
||||
'class': 'modal-backdrop',
|
||||
'ng-style': '{\'z-index\': 1040 + (index && 1 || 0) + index*10}',
|
||||
'uib-modal-animation-class': 'fade',
|
||||
'modal-in-class': 'in'
|
||||
});
|
||||
if (modal.backdropClass) {
|
||||
backdropDomEl.addClass(modal.backdropClass);
|
||||
}
|
||||
|
||||
if (modal.animation) {
|
||||
backdropDomEl.attr('modal-animation', 'true');
|
||||
}
|
||||
$compile(backdropDomEl)(backdropScope);
|
||||
$animate.enter(backdropDomEl, appendToElement);
|
||||
if ($uibPosition.isScrollable(appendToElement)) {
|
||||
scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement);
|
||||
if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
|
||||
appendToElement.css({paddingRight: scrollbarPadding.right + 'px'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var content;
|
||||
if (modal.component) {
|
||||
content = document.createElement(snake_case(modal.component.name));
|
||||
content = angular.element(content);
|
||||
content.attr({
|
||||
resolve: '$resolve',
|
||||
'modal-instance': '$uibModalInstance',
|
||||
close: '$close($value)',
|
||||
dismiss: '$dismiss($value)'
|
||||
});
|
||||
} else {
|
||||
content = modal.content;
|
||||
}
|
||||
|
||||
// Set the top modal index based on the index of the previous top modal
|
||||
topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0;
|
||||
var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
|
||||
angularDomEl.attr({
|
||||
'class': 'modal',
|
||||
'template-url': modal.windowTemplateUrl,
|
||||
'window-class': modal.windowClass,
|
||||
'window-top-class': modal.windowTopClass,
|
||||
'role': 'dialog',
|
||||
'aria-labelledby': modal.ariaLabelledBy,
|
||||
'aria-describedby': modal.ariaDescribedBy,
|
||||
'size': modal.size,
|
||||
'index': openedWindows.length() - 1,
|
||||
'animate': 'animate'
|
||||
}).html(modal.content);
|
||||
'index': topModalIndex,
|
||||
'animate': 'animate',
|
||||
'ng-style': '{\'z-index\': 1050 + $$topModalIndex*10, display: \'block\'}',
|
||||
'tabindex': -1,
|
||||
'uib-modal-animation-class': 'fade',
|
||||
'modal-in-class': 'in'
|
||||
}).append(content);
|
||||
if (modal.windowClass) {
|
||||
angularDomEl.addClass(modal.windowClass);
|
||||
}
|
||||
|
||||
if (modal.animation) {
|
||||
angularDomEl.attr('modal-animation', 'true');
|
||||
}
|
||||
|
||||
$animate.enter($compile(angularDomEl)(modal.scope), appendToElement)
|
||||
.then(function() {
|
||||
if (!modal.scope.$$uibDestructionScheduled) {
|
||||
$animate.addClass(appendToElement, modalBodyClass);
|
||||
}
|
||||
});
|
||||
appendToElement.addClass(modalBodyClass);
|
||||
if (modal.scope) {
|
||||
// we need to explicitly add the modal index to the modal scope
|
||||
// because it is needed by ngStyle to compute the zIndex property.
|
||||
modal.scope.$$topModalIndex = topModalIndex;
|
||||
}
|
||||
$animate.enter($compile(angularDomEl)(modal.scope), appendToElement);
|
||||
|
||||
openedWindows.top().value.modalDomEl = angularDomEl;
|
||||
openedWindows.top().value.modalOpener = modalOpener;
|
||||
|
||||
$modalStack.clearFocusListCache();
|
||||
applyAriaHidden(angularDomEl);
|
||||
|
||||
function applyAriaHidden(el) {
|
||||
if (!el || el[0].tagName === 'BODY') {
|
||||
return;
|
||||
}
|
||||
|
||||
getSiblings(el).forEach(function(sibling) {
|
||||
var elemIsAlreadyHidden = sibling.getAttribute('aria-hidden') === 'true',
|
||||
ariaHiddenCount = parseInt(sibling.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10);
|
||||
|
||||
if (!ariaHiddenCount) {
|
||||
ariaHiddenCount = elemIsAlreadyHidden ? 1 : 0;
|
||||
}
|
||||
|
||||
sibling.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, ariaHiddenCount + 1);
|
||||
sibling.setAttribute('aria-hidden', 'true');
|
||||
});
|
||||
|
||||
return applyAriaHidden(el.parent());
|
||||
|
||||
function getSiblings(el) {
|
||||
var children = el.parent() ? el.parent().children() : [];
|
||||
|
||||
return Array.prototype.filter.call(children, function(child) {
|
||||
return child !== el[0];
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function broadcastClosing(modalWindow, resultOrReason, closing) {
|
||||
return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
|
||||
}
|
||||
|
||||
function unhideBackgroundElements() {
|
||||
Array.prototype.forEach.call(
|
||||
document.querySelectorAll('[' + ARIA_HIDDEN_ATTRIBUTE_NAME + ']'),
|
||||
function(hiddenEl) {
|
||||
var ariaHiddenCount = parseInt(hiddenEl.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10),
|
||||
newHiddenCount = ariaHiddenCount - 1;
|
||||
hiddenEl.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, newHiddenCount);
|
||||
|
||||
if (!newHiddenCount) {
|
||||
hiddenEl.removeAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME);
|
||||
hiddenEl.removeAttribute('aria-hidden');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$modalStack.close = function(modalInstance, result) {
|
||||
var modalWindow = openedWindows.get(modalInstance);
|
||||
unhideBackgroundElements();
|
||||
if (modalWindow && broadcastClosing(modalWindow, result, true)) {
|
||||
modalWindow.value.modalScope.$$uibDestructionScheduled = true;
|
||||
modalWindow.value.deferred.resolve(result);
|
||||
removeModalWindow(modalInstance, modalWindow.value.modalOpener);
|
||||
return true;
|
||||
}
|
||||
|
||||
return !modalWindow;
|
||||
};
|
||||
|
||||
$modalStack.dismiss = function(modalInstance, reason) {
|
||||
var modalWindow = openedWindows.get(modalInstance);
|
||||
unhideBackgroundElements();
|
||||
if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
|
||||
modalWindow.value.modalScope.$$uibDestructionScheduled = true;
|
||||
modalWindow.value.deferred.reject(reason);
|
||||
@@ -524,16 +592,17 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
}
|
||||
};
|
||||
|
||||
$modalStack.focusFirstFocusableElement = function() {
|
||||
if (focusableElementList.length > 0) {
|
||||
focusableElementList[0].focus();
|
||||
$modalStack.focusFirstFocusableElement = function(list) {
|
||||
if (list.length > 0) {
|
||||
list[0].focus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
$modalStack.focusLastFocusableElement = function() {
|
||||
if (focusableElementList.length > 0) {
|
||||
focusableElementList[focusableElementList.length - 1].focus();
|
||||
|
||||
$modalStack.focusLastFocusableElement = function(list) {
|
||||
if (list.length > 0) {
|
||||
list[list.length - 1].focus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -549,32 +618,29 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
return false;
|
||||
};
|
||||
|
||||
$modalStack.isFocusInFirstItem = function(evt) {
|
||||
if (focusableElementList.length > 0) {
|
||||
return (evt.target || evt.srcElement) === focusableElementList[0];
|
||||
$modalStack.isFocusInFirstItem = function(evt, list) {
|
||||
if (list.length > 0) {
|
||||
return (evt.target || evt.srcElement) === list[0];
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
$modalStack.isFocusInLastItem = function(evt) {
|
||||
if (focusableElementList.length > 0) {
|
||||
return (evt.target || evt.srcElement) === focusableElementList[focusableElementList.length - 1];
|
||||
$modalStack.isFocusInLastItem = function(evt, list) {
|
||||
if (list.length > 0) {
|
||||
return (evt.target || evt.srcElement) === list[list.length - 1];
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
$modalStack.clearFocusListCache = function() {
|
||||
focusableElementList = [];
|
||||
focusIndex = 0;
|
||||
};
|
||||
|
||||
$modalStack.loadFocusElementList = function(modalWindow) {
|
||||
if (focusableElementList === undefined || !focusableElementList.length) {
|
||||
if (modalWindow) {
|
||||
var modalDomE1 = modalWindow.value.modalDomEl;
|
||||
if (modalDomE1 && modalDomE1.length) {
|
||||
focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
|
||||
}
|
||||
if (modalWindow) {
|
||||
var modalDomE1 = modalWindow.value.modalDomEl;
|
||||
if (modalDomE1 && modalDomE1.length) {
|
||||
var elements = modalDomE1[0].querySelectorAll(tabbableSelector);
|
||||
return elements ?
|
||||
Array.prototype.filter.call(elements, function(element) {
|
||||
return isVisible(element);
|
||||
}) : elements;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -629,13 +695,22 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
modalOptions.resolve = modalOptions.resolve || {};
|
||||
modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
|
||||
|
||||
//verify options
|
||||
if (!modalOptions.template && !modalOptions.templateUrl) {
|
||||
throw new Error('One of template or templateUrl options is required.');
|
||||
if (!modalOptions.appendTo.length) {
|
||||
throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
|
||||
}
|
||||
|
||||
var templateAndResolvePromise =
|
||||
$q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
|
||||
//verify options
|
||||
if (!modalOptions.component && !modalOptions.template && !modalOptions.templateUrl) {
|
||||
throw new Error('One of component or template or templateUrl options is required.');
|
||||
}
|
||||
|
||||
var templateAndResolvePromise;
|
||||
if (modalOptions.component) {
|
||||
templateAndResolvePromise = $q.when($uibResolve.resolve(modalOptions.resolve, {}, null, null));
|
||||
} else {
|
||||
templateAndResolvePromise =
|
||||
$q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
|
||||
}
|
||||
|
||||
function resolveWithTemplate() {
|
||||
return templateAndResolvePromise;
|
||||
@@ -661,46 +736,11 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
}
|
||||
});
|
||||
|
||||
var ctrlInstance, ctrlInstantiate, ctrlLocals = {};
|
||||
|
||||
//controllers
|
||||
if (modalOptions.controller) {
|
||||
ctrlLocals.$scope = modalScope;
|
||||
ctrlLocals.$uibModalInstance = modalInstance;
|
||||
angular.forEach(tplAndVars[1], function(value, key) {
|
||||
ctrlLocals[key] = value;
|
||||
});
|
||||
|
||||
// the third param will make the controller instantiate later,private api
|
||||
// @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126
|
||||
ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true);
|
||||
if (modalOptions.controllerAs) {
|
||||
ctrlInstance = ctrlInstantiate.instance;
|
||||
|
||||
if (modalOptions.bindToController) {
|
||||
ctrlInstance.$close = modalScope.$close;
|
||||
ctrlInstance.$dismiss = modalScope.$dismiss;
|
||||
angular.extend(ctrlInstance, providedScope);
|
||||
}
|
||||
|
||||
ctrlInstance = ctrlInstantiate();
|
||||
|
||||
modalScope[modalOptions.controllerAs] = ctrlInstance;
|
||||
} else {
|
||||
ctrlInstance = ctrlInstantiate();
|
||||
}
|
||||
|
||||
if (angular.isFunction(ctrlInstance.$onInit)) {
|
||||
ctrlInstance.$onInit();
|
||||
}
|
||||
}
|
||||
|
||||
$modalStack.open(modalInstance, {
|
||||
var modal = {
|
||||
scope: modalScope,
|
||||
deferred: modalResultDeferred,
|
||||
renderDeferred: modalRenderDeferred,
|
||||
closedDeferred: modalClosedDeferred,
|
||||
content: tplAndVars[0],
|
||||
animation: modalOptions.animation,
|
||||
backdrop: modalOptions.backdrop,
|
||||
keyboard: modalOptions.keyboard,
|
||||
@@ -708,12 +748,67 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
|
||||
windowTopClass: modalOptions.windowTopClass,
|
||||
windowClass: modalOptions.windowClass,
|
||||
windowTemplateUrl: modalOptions.windowTemplateUrl,
|
||||
ariaLabelledBy: modalOptions.ariaLabelledBy,
|
||||
ariaDescribedBy: modalOptions.ariaDescribedBy,
|
||||
size: modalOptions.size,
|
||||
openedClass: modalOptions.openedClass,
|
||||
appendTo: modalOptions.appendTo
|
||||
});
|
||||
};
|
||||
|
||||
var component = {};
|
||||
var ctrlInstance, ctrlInstantiate, ctrlLocals = {};
|
||||
|
||||
if (modalOptions.component) {
|
||||
constructLocals(component, false, true, false);
|
||||
component.name = modalOptions.component;
|
||||
modal.component = component;
|
||||
} else if (modalOptions.controller) {
|
||||
constructLocals(ctrlLocals, true, false, true);
|
||||
|
||||
// the third param will make the controller instantiate later,private api
|
||||
// @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126
|
||||
ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true, modalOptions.controllerAs);
|
||||
if (modalOptions.controllerAs && modalOptions.bindToController) {
|
||||
ctrlInstance = ctrlInstantiate.instance;
|
||||
ctrlInstance.$close = modalScope.$close;
|
||||
ctrlInstance.$dismiss = modalScope.$dismiss;
|
||||
angular.extend(ctrlInstance, {
|
||||
$resolve: ctrlLocals.$scope.$resolve
|
||||
}, providedScope);
|
||||
}
|
||||
|
||||
ctrlInstance = ctrlInstantiate();
|
||||
|
||||
if (angular.isFunction(ctrlInstance.$onInit)) {
|
||||
ctrlInstance.$onInit();
|
||||
}
|
||||
}
|
||||
|
||||
if (!modalOptions.component) {
|
||||
modal.content = tplAndVars[0];
|
||||
}
|
||||
|
||||
$modalStack.open(modalInstance, modal);
|
||||
modalOpenedDeferred.resolve(true);
|
||||
|
||||
function constructLocals(obj, template, instanceOnScope, injectable) {
|
||||
obj.$scope = modalScope;
|
||||
obj.$scope.$resolve = {};
|
||||
if (instanceOnScope) {
|
||||
obj.$scope.$uibModalInstance = modalInstance;
|
||||
} else {
|
||||
obj.$uibModalInstance = modalInstance;
|
||||
}
|
||||
|
||||
var resolves = template ? tplAndVars[1] : tplAndVars;
|
||||
angular.forEach(resolves, function(value, key) {
|
||||
if (injectable) {
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
obj.$scope.$resolve[key] = value;
|
||||
});
|
||||
}
|
||||
}, function resolveError(reason) {
|
||||
modalOpenedDeferred.reject(reason);
|
||||
modalResultDeferred.reject(reason);
|
||||
|
||||
+553
-28
@@ -46,13 +46,68 @@ describe('$uibResolve', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('uibModalTransclude', function() {
|
||||
var uibModalTranscludeDDO,
|
||||
$animate;
|
||||
|
||||
beforeEach(module('ui.bootstrap.modal'));
|
||||
beforeEach(module(function($provide) {
|
||||
$animate = jasmine.createSpyObj('$animate', ['enter']);
|
||||
$provide.value('$animate', $animate);
|
||||
}));
|
||||
|
||||
beforeEach(inject(function(uibModalTranscludeDirective) {
|
||||
uibModalTranscludeDDO = uibModalTranscludeDirective[0];
|
||||
}));
|
||||
|
||||
describe('when initialised', function() {
|
||||
var scope,
|
||||
element,
|
||||
transcludeSpy,
|
||||
transcludeFn;
|
||||
|
||||
beforeEach(function() {
|
||||
scope = {
|
||||
$parent: 'parentScope'
|
||||
};
|
||||
|
||||
element = jasmine.createSpyObj('containerElement', ['empty']);
|
||||
transcludeSpy = jasmine.createSpy('transcludeSpy').and.callFake(function(scope, fn) {
|
||||
transcludeFn = fn;
|
||||
});
|
||||
|
||||
uibModalTranscludeDDO.link(scope, element, {}, {}, transcludeSpy);
|
||||
});
|
||||
|
||||
it('should call the transclusion function', function() {
|
||||
expect(transcludeSpy).toHaveBeenCalledWith(scope.$parent, jasmine.any(Function));
|
||||
});
|
||||
|
||||
describe('transclusion callback', function() {
|
||||
var transcludedContent;
|
||||
|
||||
beforeEach(function() {
|
||||
transcludedContent = 'my transcluded content';
|
||||
transcludeFn(transcludedContent);
|
||||
});
|
||||
|
||||
it('should empty the element', function() {
|
||||
expect(element.empty).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should append the transcluded content', function() {
|
||||
expect($animate.enter).toHaveBeenCalledWith(transcludedContent, element);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('$uibModal', function() {
|
||||
var $animate, $controllerProvider, $rootScope, $document, $compile, $templateCache, $timeout, $q;
|
||||
var $uibModal, $uibModalStack, $uibModalProvider;
|
||||
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
beforeEach(module('ui.bootstrap.modal'));
|
||||
beforeEach(module('uib/template/modal/backdrop.html'));
|
||||
beforeEach(module('uib/template/modal/window.html'));
|
||||
beforeEach(module(function(_$controllerProvider_, _$uibModalProvider_, $compileProvider) {
|
||||
$controllerProvider = _$controllerProvider_;
|
||||
@@ -76,6 +131,16 @@ describe('$uibModal', function() {
|
||||
elem.focus();
|
||||
}
|
||||
};
|
||||
}).component('fooBar', {
|
||||
bindings: {
|
||||
resolve: '<',
|
||||
modalInstance: '<',
|
||||
close: '&',
|
||||
dismiss: '&'
|
||||
},
|
||||
controller: angular.noop,
|
||||
controllerAs: 'foobar',
|
||||
template: '<div>Foo Bar</div>'
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -96,6 +161,7 @@ describe('$uibModal', function() {
|
||||
toBeResolvedWith: function(util, customEqualityTesters) {
|
||||
return {
|
||||
compare: function(promise, expected) {
|
||||
var called = false;
|
||||
promise.then(function(result) {
|
||||
expect(result).toEqual(expected);
|
||||
|
||||
@@ -104,10 +170,18 @@ describe('$uibModal', function() {
|
||||
} else {
|
||||
result.message = 'Expected "' + angular.mock.dump(result) + '" to be resolved with "' + expected + '".';
|
||||
}
|
||||
}, function(result) {
|
||||
fail('Expected "' + angular.mock.dump(result) + '" to be resolved with "' + expected + '".');
|
||||
})['finally'](function() {
|
||||
called = true;
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
if (!called) {
|
||||
fail('Expected "' + angular.mock.dump(result) + '" to be resolved with "' + expected + '".');
|
||||
}
|
||||
|
||||
return {pass: true};
|
||||
}
|
||||
};
|
||||
@@ -116,9 +190,10 @@ describe('$uibModal', function() {
|
||||
return {
|
||||
compare: function(promise, expected) {
|
||||
var result = {};
|
||||
var called = false;
|
||||
|
||||
promise.then(function() {
|
||||
|
||||
promise.then(function(result) {
|
||||
fail('Expected "' + angular.mock.dump(result) + '" to be rejected with "' + expected + '".');
|
||||
}, function(result) {
|
||||
expect(result).toEqual(expected);
|
||||
|
||||
@@ -127,10 +202,16 @@ describe('$uibModal', function() {
|
||||
} else {
|
||||
result.message = 'Expected "' + angular.mock.dump(result) + '" to be rejected with "' + expected + '".';
|
||||
}
|
||||
})['finally'](function() {
|
||||
called = true;
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
if (!called) {
|
||||
fail('Expected "' + angular.mock.dump(result) + '" to be rejected with "' + expected + '".');
|
||||
}
|
||||
|
||||
return {pass: true};
|
||||
}
|
||||
};
|
||||
@@ -215,6 +296,8 @@ describe('$uibModal', function() {
|
||||
|
||||
function open(modalOptions, noFlush, noDigest) {
|
||||
var modal = $uibModal.open(modalOptions);
|
||||
modal.opened['catch'](angular.noop);
|
||||
modal.result['catch'](angular.noop);
|
||||
|
||||
if (!noDigest) {
|
||||
$rootScope.$digest();
|
||||
@@ -445,7 +528,7 @@ describe('$uibModal', function() {
|
||||
|
||||
var modal = open({template: '<div>Content<button>inside modal</button></div>'});
|
||||
$rootScope.$digest();
|
||||
expect(document.activeElement.tagName).toBe('DIV');
|
||||
expect(document.activeElement.className.split(' ')).toContain('modal');
|
||||
expect($document).toHaveModalsOpen(1);
|
||||
|
||||
triggerKeyDown($document, 27);
|
||||
@@ -575,7 +658,7 @@ describe('$uibModal', function() {
|
||||
it('should not focus on the element that has autofocus attribute when the modal is opened and something in the modal already has focus and the animations have finished', function() {
|
||||
function openAndCloseModalWithAutofocusElement() {
|
||||
|
||||
var modal = open({template: '<div><input type="text" id="auto-focus-element" autofocus><input type="text" id="pre-focus-element" focus-me></div>'});
|
||||
var modal = open({template: '<div><input type="text" id="pre-focus-element" focus-me><input type="text" id="auto-focus-element" autofocus></div>'});
|
||||
$rootScope.$digest();
|
||||
expect(angular.element('#auto-focus-element')).not.toHaveFocus();
|
||||
expect(angular.element('#pre-focus-element')).toHaveFocus();
|
||||
@@ -617,7 +700,7 @@ describe('$uibModal', function() {
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
|
||||
expect(document.activeElement.tagName).toBe('DIV');
|
||||
expect(document.activeElement.className.split(' ')).toContain('modal');
|
||||
|
||||
close(modal, 'closed ok');
|
||||
|
||||
@@ -715,6 +798,144 @@ describe('$uibModal', function() {
|
||||
|
||||
initialPage.remove();
|
||||
});
|
||||
|
||||
it('should change focus to next proper element when DOM changes and tab is pressed', function() {
|
||||
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
|
||||
angular.element(document.body).append(initialPage);
|
||||
initialPage.focus();
|
||||
|
||||
open({
|
||||
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
|
||||
'<button id="tab-focus-button">Open me!</button>',
|
||||
keyboard: false
|
||||
});
|
||||
$rootScope.$digest();
|
||||
expect($document).toHaveModalsOpen(1);
|
||||
|
||||
$('#tab-focus-link3').focus();
|
||||
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
|
||||
|
||||
$('#tab-focus-button').remove();
|
||||
triggerKeyDown(angular.element(document.activeElement), 9, false);
|
||||
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
|
||||
|
||||
initialPage.remove();
|
||||
});
|
||||
|
||||
it('should change focus to next proper element when DOM changes and shift+tab is pressed', function() {
|
||||
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
|
||||
angular.element(document.body).append(initialPage);
|
||||
initialPage.focus();
|
||||
|
||||
open({
|
||||
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
|
||||
'<button id="tab-focus-button">Open me!</button>',
|
||||
keyboard: false
|
||||
});
|
||||
$rootScope.$digest();
|
||||
expect($document).toHaveModalsOpen(1);
|
||||
|
||||
$('#tab-focus-link1').focus();
|
||||
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
|
||||
|
||||
$('#tab-focus-button').remove();
|
||||
triggerKeyDown(angular.element(document.activeElement), 9, true);
|
||||
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
|
||||
|
||||
initialPage.remove();
|
||||
});
|
||||
|
||||
it('should change focus to next non-hidden element when tab is pressed', function() {
|
||||
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
|
||||
angular.element(document.body).append(initialPage);
|
||||
initialPage.focus();
|
||||
|
||||
open({
|
||||
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
|
||||
'<button id="tab-focus-button">Open me!</button>',
|
||||
keyboard: false
|
||||
});
|
||||
$rootScope.$digest();
|
||||
expect($document).toHaveModalsOpen(1);
|
||||
|
||||
$('#tab-focus-link3').focus();
|
||||
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
|
||||
|
||||
$('#tab-focus-button').css('display', 'none');
|
||||
triggerKeyDown(angular.element(document.activeElement), 9, false);
|
||||
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
|
||||
|
||||
initialPage.remove();
|
||||
});
|
||||
|
||||
it('should change focus to previous non-hidden element when shift+tab is pressed', function() {
|
||||
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
|
||||
angular.element(document.body).append(initialPage);
|
||||
initialPage.focus();
|
||||
|
||||
open({
|
||||
template:'<a href="#" id="tab-focus-link1">a</a><a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
|
||||
'<button id="tab-focus-button">Open me!</button>',
|
||||
keyboard: false
|
||||
});
|
||||
$rootScope.$digest();
|
||||
expect($document).toHaveModalsOpen(1);
|
||||
|
||||
$('#tab-focus-link1').focus();
|
||||
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
|
||||
|
||||
$('#tab-focus-button').css('display', 'none');
|
||||
triggerKeyDown(angular.element(document.activeElement), 9, true);
|
||||
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
|
||||
|
||||
initialPage.remove();
|
||||
});
|
||||
|
||||
it('should change focus to next tabbable element when tab is pressed', function() {
|
||||
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
|
||||
angular.element(document.body).append(initialPage);
|
||||
initialPage.focus();
|
||||
|
||||
open({
|
||||
template:'<button id="tab-focus-button1" tabindex="-1">Skip me!</button><a href="#" id="tab-focus-link1">a</a>' +
|
||||
'<a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
|
||||
'<button id="tab-focus-button2" tabindex="-1">Skip me!</button>',
|
||||
keyboard: false
|
||||
});
|
||||
$rootScope.$digest();
|
||||
expect($document).toHaveModalsOpen(1);
|
||||
|
||||
$('#tab-focus-link3').focus();
|
||||
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
|
||||
|
||||
triggerKeyDown(angular.element(document.activeElement), 9, false);
|
||||
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
|
||||
|
||||
initialPage.remove();
|
||||
});
|
||||
|
||||
it('should change focus to previous tabbable element when shift+tab is pressed', function() {
|
||||
var initialPage = angular.element('<a href="#" id="cannot-get-focus-from-modal">Outland link</a>');
|
||||
angular.element(document.body).append(initialPage);
|
||||
initialPage.focus();
|
||||
|
||||
open({
|
||||
template:'<button id="tab-focus-button1" tabindex="-1">Skip me!</button><a href="#" id="tab-focus-link1">a</a>' +
|
||||
'<a href="#" id="tab-focus-link2">b</a><a href="#" id="tab-focus-link3">c</a>' +
|
||||
'<button id="tab-focus-button2" tabindex="-1">Skip me!</button>',
|
||||
keyboard: false
|
||||
});
|
||||
$rootScope.$digest();
|
||||
expect($document).toHaveModalsOpen(1);
|
||||
|
||||
$('#tab-focus-link1').focus();
|
||||
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link1');
|
||||
|
||||
triggerKeyDown(angular.element(document.activeElement), 9, true);
|
||||
expect(document.activeElement.getAttribute('id')).toBe('tab-focus-link3');
|
||||
|
||||
initialPage.remove();
|
||||
});
|
||||
});
|
||||
|
||||
describe('default options can be changed in a provider', function() {
|
||||
@@ -737,16 +958,89 @@ describe('$uibModal', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('option by option', function () {
|
||||
describe('template and templateUrl', function () {
|
||||
it('should throw an error if none of template and templateUrl are provided', function() {
|
||||
describe('option by option', function() {
|
||||
describe('component', function() {
|
||||
function getModalComponent($document) {
|
||||
return $document.find('body > div.modal > div.modal-dialog > div.modal-content foo-bar');
|
||||
}
|
||||
|
||||
it('should use as modal content', function() {
|
||||
open({
|
||||
component: 'fooBar'
|
||||
});
|
||||
|
||||
var component = getModalComponent($document);
|
||||
expect(component.html()).toBe('<div>Foo Bar</div>');
|
||||
});
|
||||
|
||||
it('should bind expected values', function() {
|
||||
var modal = open({
|
||||
component: 'fooBar',
|
||||
resolve: {
|
||||
foo: function() {
|
||||
return 'bar';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var component = getModalComponent($document);
|
||||
var componentScope = component.isolateScope();
|
||||
|
||||
expect(componentScope.foobar.resolve.foo).toBe('bar');
|
||||
expect(componentScope.foobar.modalInstance).toBe(modal);
|
||||
expect(componentScope.foobar.close).toEqual(jasmine.any(Function));
|
||||
expect(componentScope.foobar.dismiss).toEqual(jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('should close the modal', function() {
|
||||
var modal = open({
|
||||
component: 'fooBar',
|
||||
resolve: {
|
||||
foo: function() {
|
||||
return 'bar';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var component = getModalComponent($document);
|
||||
var componentScope = component.isolateScope();
|
||||
|
||||
componentScope.foobar.close({
|
||||
$value: 'baz'
|
||||
});
|
||||
|
||||
expect(modal.result).toBeResolvedWith('baz');
|
||||
});
|
||||
|
||||
it('should dismiss the modal', function() {
|
||||
var modal = open({
|
||||
component: 'fooBar',
|
||||
resolve: {
|
||||
foo: function() {
|
||||
return 'bar';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var component = getModalComponent($document);
|
||||
var componentScope = component.isolateScope();
|
||||
|
||||
componentScope.foobar.dismiss({
|
||||
$value: 'baz'
|
||||
});
|
||||
|
||||
expect(modal.result).toBeRejectedWith('baz');
|
||||
});
|
||||
});
|
||||
|
||||
describe('template and templateUrl', function() {
|
||||
it('should throw an error if none of component, template and templateUrl are provided', function() {
|
||||
expect(function(){
|
||||
var modal = open({});
|
||||
}).toThrow(new Error('One of template or templateUrl options is required.'));
|
||||
}).toThrow(new Error('One of component or template or templateUrl options is required.'));
|
||||
});
|
||||
|
||||
it('should not fail if a templateUrl contains leading / trailing white spaces', function() {
|
||||
|
||||
$templateCache.put('whitespace.html', ' <div>Whitespaces</div> ');
|
||||
open({templateUrl: 'whitespace.html'});
|
||||
expect($document).toHaveModalOpenWithContent('Whitespaces', 'div');
|
||||
@@ -961,6 +1255,20 @@ describe('$uibModal', function() {
|
||||
});
|
||||
expect($document).toHaveModalOpenWithContent('Content from root scope', 'div');
|
||||
});
|
||||
|
||||
it('should expose $resolve in template', function() {
|
||||
open({
|
||||
controller: function($scope) {},
|
||||
resolve: {
|
||||
$foo: function() {
|
||||
return 'Content from resolve';
|
||||
}
|
||||
},
|
||||
template: '<div>{{$resolve.$foo}}</div>'
|
||||
});
|
||||
|
||||
expect($document).toHaveModalOpenWithContent('Content from resolve', 'div');
|
||||
});
|
||||
});
|
||||
|
||||
describe('keyboard', function () {
|
||||
@@ -1183,7 +1491,7 @@ describe('$uibModal', function() {
|
||||
expect(body).not.toHaveClass('modal-open');
|
||||
});
|
||||
|
||||
it('should remove the custom class on closing of modal', function() {
|
||||
it('should remove the custom class on closing of modal after animations have completed', function() {
|
||||
var modal = open({
|
||||
template: '<div>dummy modal</div>',
|
||||
openedClass: 'foo'
|
||||
@@ -1191,7 +1499,13 @@ describe('$uibModal', function() {
|
||||
|
||||
expect(body).toHaveClass('foo');
|
||||
|
||||
close(modal);
|
||||
close(modal, null, true);
|
||||
expect(body).toHaveClass('foo');
|
||||
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(body).not.toHaveClass('foo');
|
||||
});
|
||||
@@ -1252,6 +1566,28 @@ describe('$uibModal', function() {
|
||||
expect(body).not.toHaveClass('modal-open');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ariaLabelledBy', function() {
|
||||
it('should add the aria-labelledby property to the modal', function() {
|
||||
open({
|
||||
template: '<div><h3 id="modal-label">Modal Label</h3><p id="modal-description">Modal description</p></div>',
|
||||
ariaLabelledBy: 'modal-label'
|
||||
});
|
||||
|
||||
expect($document.find('.modal').attr('aria-labelledby')).toEqual('modal-label');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ariaDescribedBy', function() {
|
||||
it('should add the aria-describedby property to the modal', function() {
|
||||
open({
|
||||
template: '<div><h3 id="modal-label">Modal Label</h3><p id="modal-description">Modal description</p></div>',
|
||||
ariaDescribedBy: 'modal-description'
|
||||
});
|
||||
|
||||
expect($document.find('.modal').attr('aria-describedby')).toEqual('modal-description');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('modal window', function() {
|
||||
@@ -1263,15 +1599,6 @@ describe('$uibModal', function() {
|
||||
expect($rootScope.foo).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support custom CSS classes as string', function() {
|
||||
$rootScope.animate = false;
|
||||
var windowEl = $compile('<div uib-modal-window animate="animate" window-class="test foo">content</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(windowEl).toHaveClass('test');
|
||||
expect(windowEl).toHaveClass('foo');
|
||||
});
|
||||
|
||||
it('should support window top class', function () {
|
||||
$rootScope.animate = false;
|
||||
var windowEl = $compile('<div uib-modal-window animate="animate" window-top-class="test foo">content</div>')($rootScope);
|
||||
@@ -1282,13 +1609,12 @@ describe('$uibModal', function() {
|
||||
});
|
||||
|
||||
it('should support custom template url', inject(function($templateCache) {
|
||||
$templateCache.put('window.html', '<div class="mywindow" ng-transclude></div>');
|
||||
$templateCache.put('window.html', '<div ng-transclude></div>');
|
||||
|
||||
var windowEl = $compile('<div uib-modal-window template-url="window.html" window-class="test">content</div>')($rootScope);
|
||||
var windowEl = $compile('<div uib-modal-window template-url="window.html">content</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(windowEl).toHaveClass('mywindow');
|
||||
expect(windowEl).toHaveClass('test');
|
||||
expect(windowEl.html()).toBe('<div ng-transclude="">content</div>');
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -1412,16 +1738,19 @@ describe('$uibModal', function() {
|
||||
ds[x] = {index: i, deferred: $q.defer(), reject: reject};
|
||||
|
||||
var scope = $rootScope.$new();
|
||||
var failed = false;
|
||||
scope.index = i;
|
||||
open({
|
||||
template: '<div>' + i + '</div>',
|
||||
scope: scope,
|
||||
resolve: {
|
||||
x: function() { return ds[x].deferred.promise; }
|
||||
x: function() { return ds[x].deferred.promise['catch'](function () {
|
||||
failed = true;
|
||||
}); }
|
||||
}
|
||||
}, true).opened.then(function() {
|
||||
expect($uibModalStack.getTop().value.modalScope.index).toEqual(i);
|
||||
actual += i;
|
||||
if (!failed) { actual += i; }
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1523,6 +1852,202 @@ describe('$uibModal', function() {
|
||||
expect($document.find('div.modal1')).toHaveClass('modal-top');
|
||||
expect($document).toHaveModalsOpen(1);
|
||||
});
|
||||
|
||||
it('should have top modal with highest index', function() {
|
||||
var modal2Index = null;
|
||||
var modal3Index = null;
|
||||
|
||||
var modal1Instance = {
|
||||
result: $q.defer(),
|
||||
opened: $q.defer(),
|
||||
closed: $q.defer(),
|
||||
rendered: $q.defer(),
|
||||
close: function(result) {
|
||||
return $uibModalStack.close(modal1Instance, result);
|
||||
},
|
||||
dismiss: function(reason) {
|
||||
return $uibModalStack.dismiss(modal1Instance, reason);
|
||||
}
|
||||
};
|
||||
var modal2Instance = {
|
||||
result: $q.defer(),
|
||||
opened: $q.defer(),
|
||||
closed: $q.defer(),
|
||||
rendered: $q.defer(),
|
||||
close: function(result) {
|
||||
return $uibModalStack.close(modal2Instance, result);
|
||||
},
|
||||
dismiss: function(reason) {
|
||||
return $uibModalStack.dismiss(modal2Instance, reason);
|
||||
}
|
||||
};
|
||||
var modal3Instance = {
|
||||
result: $q.defer(),
|
||||
opened: $q.defer(),
|
||||
closed: $q.defer(),
|
||||
rendered: $q.defer(),
|
||||
close: function(result) {
|
||||
return $uibModalStack.close(modal13nstance, result);
|
||||
},
|
||||
dismiss: function(reason) {
|
||||
return $uibModalStack.dismiss(modal3Instance, reason);
|
||||
}
|
||||
};
|
||||
|
||||
var modal1 = $uibModalStack.open(modal1Instance, {
|
||||
appendTo: angular.element(document.body),
|
||||
scope: $rootScope.$new(),
|
||||
deferred: modal1Instance.result,
|
||||
renderDeferred: modal1Instance.rendered,
|
||||
closedDeferred: modal1Instance.closed,
|
||||
content: '<div>Modal1</div>'
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
expect($document).toHaveModalsOpen(1);
|
||||
|
||||
expect(parseInt($uibModalStack.getTop().value.modalDomEl.attr('index'), 10)).toEqual(0);
|
||||
|
||||
var modal2 = $uibModalStack.open(modal2Instance, {
|
||||
appendTo: angular.element(document.body),
|
||||
scope: $rootScope.$new(),
|
||||
deferred: modal2Instance.result,
|
||||
renderDeferred: modal2Instance.rendered,
|
||||
closedDeferred: modal2Instance.closed,
|
||||
content: '<div>Modal2</div>'
|
||||
});
|
||||
|
||||
modal2Instance.rendered.promise.then(function() {
|
||||
modal2Index = parseInt($uibModalStack.getTop().value.modalDomEl.attr('index'), 10);
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
expect($document).toHaveModalsOpen(2);
|
||||
|
||||
expect(modal2Index).toEqual(1);
|
||||
close(modal1Instance);
|
||||
expect($document).toHaveModalsOpen(1);
|
||||
|
||||
var modal3 = $uibModalStack.open(modal3Instance, {
|
||||
appendTo: angular.element(document.body),
|
||||
scope: $rootScope.$new(),
|
||||
deferred: modal3Instance.result,
|
||||
renderDeferred: modal3Instance.rendered,
|
||||
closedDeferred: modal3Instance.closed,
|
||||
content: '<div>Modal3</div>'
|
||||
});
|
||||
|
||||
modal3Instance.rendered.promise.then(function() {
|
||||
modal3Index = parseInt($uibModalStack.getTop().value.modalDomEl.attr('index'), 10);
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
expect($document).toHaveModalsOpen(2);
|
||||
|
||||
expect(modal3Index).toEqual(2);
|
||||
expect(modal2Index).toBeLessThan(modal3Index);
|
||||
});
|
||||
|
||||
it('should have top modal with highest z-index', function() {
|
||||
var modal2zIndex = null;
|
||||
var modal3zIndex = null;
|
||||
|
||||
var modal1Instance = {
|
||||
result: $q.defer(),
|
||||
opened: $q.defer(),
|
||||
closed: $q.defer(),
|
||||
rendered: $q.defer(),
|
||||
close: function(result) {
|
||||
return $uibModalStack.close(modal1Instance, result);
|
||||
},
|
||||
dismiss: function(reason) {
|
||||
return $uibModalStack.dismiss(modal1Instance, reason);
|
||||
}
|
||||
};
|
||||
var modal2Instance = {
|
||||
result: $q.defer(),
|
||||
opened: $q.defer(),
|
||||
closed: $q.defer(),
|
||||
rendered: $q.defer(),
|
||||
close: function(result) {
|
||||
return $uibModalStack.close(modal2Instance, result);
|
||||
},
|
||||
dismiss: function(reason) {
|
||||
return $uibModalStack.dismiss(modal2Instance, reason);
|
||||
}
|
||||
};
|
||||
var modal3Instance = {
|
||||
result: $q.defer(),
|
||||
opened: $q.defer(),
|
||||
closed: $q.defer(),
|
||||
rendered: $q.defer(),
|
||||
close: function(result) {
|
||||
return $uibModalStack.close(modal3Instance, result);
|
||||
},
|
||||
dismiss: function(reason) {
|
||||
return $uibModalStack.dismiss(modal3Instance, reason);
|
||||
}
|
||||
};
|
||||
|
||||
var modal1 = $uibModalStack.open(modal1Instance, {
|
||||
appendTo: angular.element(document.body),
|
||||
scope: $rootScope.$new(),
|
||||
deferred: modal1Instance.result,
|
||||
renderDeferred: modal1Instance.rendered,
|
||||
closedDeferred: modal1Instance.closed,
|
||||
content: '<div>Modal1</div>'
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
expect($document).toHaveModalsOpen(1);
|
||||
|
||||
expect(+$uibModalStack.getTop().value.modalDomEl[0].style.zIndex).toBe(1050);
|
||||
|
||||
var modal2 = $uibModalStack.open(modal2Instance, {
|
||||
appendTo: angular.element(document.body),
|
||||
scope: $rootScope.$new(),
|
||||
deferred: modal2Instance.result,
|
||||
renderDeferred: modal2Instance.rendered,
|
||||
closedDeferred: modal2Instance.closed,
|
||||
content: '<div>Modal2</div>'
|
||||
});
|
||||
|
||||
modal2Instance.rendered.promise.then(function() {
|
||||
modal2zIndex = +$uibModalStack.getTop().value.modalDomEl[0].style.zIndex;
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
expect($document).toHaveModalsOpen(2);
|
||||
|
||||
expect(modal2zIndex).toBe(1060);
|
||||
close(modal1Instance);
|
||||
expect($document).toHaveModalsOpen(1);
|
||||
|
||||
var modal3 = $uibModalStack.open(modal3Instance, {
|
||||
appendTo: angular.element(document.body),
|
||||
scope: $rootScope.$new(),
|
||||
deferred: modal3Instance.result,
|
||||
renderDeferred: modal3Instance.rendered,
|
||||
closedDeferred: modal3Instance.closed,
|
||||
content: '<div>Modal3</div>'
|
||||
});
|
||||
|
||||
modal3Instance.rendered.promise.then(function() {
|
||||
modal3zIndex = +$uibModalStack.getTop().value.modalDomEl[0].style.zIndex;
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
expect($document).toHaveModalsOpen(2);
|
||||
|
||||
expect(modal3zIndex).toBe(1070);
|
||||
expect(modal2zIndex).toBeLessThan(modal3zIndex);
|
||||
});
|
||||
});
|
||||
|
||||
describe('modal.closing event', function() {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
require('./multiMap.js');
|
||||
@@ -0,0 +1,55 @@
|
||||
angular.module('ui.bootstrap.multiMap', [])
|
||||
/**
|
||||
* A helper, internal data structure that stores all references attached to key
|
||||
*/
|
||||
.factory('$$multiMap', function() {
|
||||
return {
|
||||
createNew: function() {
|
||||
var map = {};
|
||||
|
||||
return {
|
||||
entries: function() {
|
||||
return Object.keys(map).map(function(key) {
|
||||
return {
|
||||
key: key,
|
||||
value: map[key]
|
||||
};
|
||||
});
|
||||
},
|
||||
get: function(key) {
|
||||
return map[key];
|
||||
},
|
||||
hasKey: function(key) {
|
||||
return !!map[key];
|
||||
},
|
||||
keys: function() {
|
||||
return Object.keys(map);
|
||||
},
|
||||
put: function(key, value) {
|
||||
if (!map[key]) {
|
||||
map[key] = [];
|
||||
}
|
||||
|
||||
map[key].push(value);
|
||||
},
|
||||
remove: function(key, value) {
|
||||
var values = map[key];
|
||||
|
||||
if (!values) {
|
||||
return;
|
||||
}
|
||||
|
||||
var idx = values.indexOf(value);
|
||||
|
||||
if (idx !== -1) {
|
||||
values.splice(idx, 1);
|
||||
}
|
||||
|
||||
if (!values.length) {
|
||||
delete map[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
describe('multi map', function() {
|
||||
var multiMap;
|
||||
|
||||
beforeEach(module('ui.bootstrap.modal'));
|
||||
beforeEach(module('ui.bootstrap.multiMap'));
|
||||
beforeEach(inject(function($$multiMap) {
|
||||
multiMap = $$multiMap.createNew();
|
||||
}));
|
||||
@@ -1,5 +1,5 @@
|
||||
<div ng-controller="PagerDemoCtrl">
|
||||
<h4>Pager</h4>
|
||||
<pre>You are currently on page {{currentPage}}</pre>
|
||||
<uib-pager total-items="totalItems" ng-model="currentPage"></uib-pager>
|
||||
<ul uib-pager total-items="totalItems" ng-model="currentPage"></ul>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
require('../paging');
|
||||
require('../tabindex');
|
||||
require('../../template/pager/pager.html.js');
|
||||
require('./pager');
|
||||
|
||||
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging'])
|
||||
angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex'])
|
||||
|
||||
.controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {
|
||||
$scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;
|
||||
@@ -22,13 +22,14 @@ angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging'])
|
||||
ngDisabled: '='
|
||||
},
|
||||
require: ['uibPager', '?ngModel'],
|
||||
restrict: 'A',
|
||||
controller: 'UibPagerController',
|
||||
controllerAs: 'pager',
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/pager/pager.html';
|
||||
},
|
||||
replace: true,
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
element.addClass('pager');
|
||||
var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
||||
|
||||
if (!ngModelCtrl) {
|
||||
|
||||
@@ -10,7 +10,7 @@ describe('pager directive', function() {
|
||||
$document = _$document_;
|
||||
$templateCache = _$templateCache_;
|
||||
body = $document.find('body');
|
||||
element = $compile('<uib-pager total-items="total" ng-model="currentPage"></uib-pager>')($rootScope);
|
||||
element = $compile('<ul uib-pager total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
}));
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('pager directive', function() {
|
||||
it('exposes the controller on the template', function() {
|
||||
$templateCache.put('uib/template/pager/pager.html', '<div>{{pager.text}}</div>');
|
||||
|
||||
element = $compile('<uib-pager></uib-pager>')($rootScope);
|
||||
element = $compile('<ul uib-pager></ul uib-pager>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
var ctrl = element.controller('uibPager');
|
||||
@@ -65,7 +65,7 @@ describe('pager directive', function() {
|
||||
ctrl.text = 'foo';
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(element.html()).toBe('foo');
|
||||
expect(element.html()).toBe('<div class="ng-binding">foo</div>');
|
||||
});
|
||||
|
||||
it('disables the "previous" link if current page is 1', function() {
|
||||
@@ -102,7 +102,7 @@ describe('pager directive', function() {
|
||||
|
||||
it('executes the `ng-change` expression when an element is clicked', function() {
|
||||
$rootScope.selectPageHandler = jasmine.createSpy('selectPageHandler');
|
||||
element = $compile('<uib-pager total-items="total" ng-model="currentPage" ng-change="selectPageHandler()"></uib-pager>')($rootScope);
|
||||
element = $compile('<ul uib-pager total-items="total" ng-model="currentPage" ng-change="selectPageHandler()"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
clickPaginationEl(-1);
|
||||
@@ -147,16 +147,16 @@ describe('pager directive', function() {
|
||||
it('allows custom templates', function() {
|
||||
$templateCache.put('foo/bar.html', '<div>baz</div>');
|
||||
|
||||
element = $compile('<uib-pager template-url="foo/bar.html"></uib-pager>')($rootScope);
|
||||
element = $compile('<ul uib-pager template-url="foo/bar.html"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(element.html()).toBe('baz');
|
||||
expect(element.html()).toBe('<div>baz</div>');
|
||||
});
|
||||
|
||||
describe('`items-per-page`', function() {
|
||||
beforeEach(function() {
|
||||
$rootScope.perpage = 5;
|
||||
element = $compile('<uib-pager total-items="total" items-per-page="perpage" ng-model="currentPage"></uib-pager>')($rootScope);
|
||||
element = $compile('<ul uib-pager total-items="total" items-per-page="perpage" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -190,7 +190,7 @@ describe('pager directive', function() {
|
||||
describe('`num-pages`', function() {
|
||||
beforeEach(function() {
|
||||
$rootScope.numpg = null;
|
||||
element = $compile('<uib-pager total-items="total" ng-model="currentPage" num-pages="numpg"></uib-pager>')($rootScope);
|
||||
element = $compile('<ul uib-pager total-items="total" ng-model="currentPage" num-pages="numpg"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -206,7 +206,7 @@ describe('pager directive', function() {
|
||||
uibPagerConfig.previousText = 'PR';
|
||||
uibPagerConfig.nextText = 'NE';
|
||||
uibPagerConfig.align = false;
|
||||
element = $compile('<uib-pager total-items="total" ng-model="currentPage"></uib-pager>')($rootScope);
|
||||
element = $compile('<ul uib-pager total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
}));
|
||||
afterEach(inject(function(uibPagerConfig) {
|
||||
@@ -227,7 +227,7 @@ describe('pager directive', function() {
|
||||
|
||||
describe('override configuration from attributes', function() {
|
||||
beforeEach(function() {
|
||||
element = $compile('<uib-pager align="false" previous-text="<" next-text=">" total-items="total" ng-model="currentPage"></uib-pager>')($rootScope);
|
||||
element = $compile('<ul uib-pager align="false" previous-text="<" next-text=">" total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -248,7 +248,7 @@ describe('pager directive', function() {
|
||||
it('changes "previous" & "next" text from interpolated attributes', function() {
|
||||
$rootScope.previousText = '<<';
|
||||
$rootScope.nextText = '>>';
|
||||
element = $compile('<uib-pager align="false" previous-text="{{previousText}}" next-text="{{nextText}}" total-items="total" ng-model="currentPage"></uib-pager>')($rootScope);
|
||||
element = $compile('<ul uib-pager align="false" previous-text="{{previousText}}" next-text="{{nextText}}" total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(getPaginationEl(0).text()).toBe('<<');
|
||||
@@ -259,7 +259,7 @@ describe('pager directive', function() {
|
||||
it('disables the component when ng-disabled is true', function() {
|
||||
$rootScope.disable = true;
|
||||
|
||||
element = $compile('<uib-pager total-items="total" ng-disabled="disable" ng-model="currentPage"></uib-pager>')($rootScope);
|
||||
element = $compile('<ul uib-pager total-items="total" ng-disabled="disable" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
updateCurrentPage(2);
|
||||
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<div ng-controller="PaginationDemoCtrl">
|
||||
<h4>Default</h4>
|
||||
<uib-pagination total-items="totalItems" ng-model="currentPage" ng-change="pageChanged()"></uib-pagination>
|
||||
<uib-pagination boundary-links="true" total-items="totalItems" ng-model="currentPage" class="pagination-sm" previous-text="‹" next-text="›" first-text="«" last-text="»"></uib-pagination>
|
||||
<uib-pagination direction-links="false" boundary-links="true" total-items="totalItems" ng-model="currentPage"></uib-pagination>
|
||||
<uib-pagination direction-links="false" total-items="totalItems" ng-model="currentPage" num-pages="smallnumPages"></uib-pagination>
|
||||
<ul uib-pagination total-items="totalItems" ng-model="currentPage" ng-change="pageChanged()"></ul>
|
||||
<ul uib-pagination boundary-links="true" total-items="totalItems" ng-model="currentPage" class="pagination-sm" previous-text="‹" next-text="›" first-text="«" last-text="»"></ul>
|
||||
<ul uib-pagination direction-links="false" boundary-links="true" total-items="totalItems" ng-model="currentPage"></ul>
|
||||
<ul uib-pagination direction-links="false" total-items="totalItems" ng-model="currentPage" num-pages="smallnumPages"></ul>
|
||||
<pre>The selected page no: {{currentPage}}</pre>
|
||||
<button type="button" class="btn btn-info" ng-click="setPage(3)">Set current page to: 3</button>
|
||||
|
||||
<hr />
|
||||
<h4>Limit the maximum visible buttons</h4>
|
||||
<h6><code>rotate</code> defaulted to <code>true</code>:</h6>
|
||||
<uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" num-pages="numPages"></uib-pagination>
|
||||
<ul uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" num-pages="numPages"></ul>
|
||||
<h6><code>rotate</code> defaulted to <code>true</code> and <code>force-ellipses</code> set to <code>true</code>:</h6>
|
||||
<uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" force-ellipses="true"></uib-pagination>
|
||||
<ul uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" force-ellipses="true"></ul>
|
||||
<h6><code>rotate</code> set to <code>false</code>:</h6>
|
||||
<uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" rotate="false"></uib-pagination>
|
||||
<ul uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" rotate="false"></ul>
|
||||
<h6><code>boundary-link-numbers</code> set to <code>true</code> and <code>rotate</code> defaulted to <code>true</code>:</h6>
|
||||
<uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-link-numbers="true"></uib-pagination>
|
||||
<ul uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-link-numbers="true"></ul>
|
||||
<h6><code>boundary-link-numbers</code> set to <code>true</code> and <code>rotate</code> set to <code>false</code>:</h6>
|
||||
<uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-link-numbers="true" rotate="false"></uib-pagination>
|
||||
<ul uib-pagination total-items="bigTotalItems" ng-model="bigCurrentPage" max-size="maxSize" class="pagination-sm" boundary-link-numbers="true" rotate="false"></ul>
|
||||
<pre>Page: {{bigCurrentPage}} / {{numPages}}</pre>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
require('../paging');
|
||||
require('../tabindex');
|
||||
require('../../template/pagination/pagination.html.js');
|
||||
require('./pagination');
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
|
||||
angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex'])
|
||||
.controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
|
||||
var ctrl = this;
|
||||
// Setup configuration parameters
|
||||
@@ -9,6 +9,7 @@ angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
|
||||
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;
|
||||
$scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
|
||||
$attrs.$set('role', 'menu');
|
||||
|
||||
uibPaging.create(this, $scope, $attrs);
|
||||
|
||||
@@ -132,13 +133,14 @@ angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
|
||||
ngDisabled:'='
|
||||
},
|
||||
require: ['uibPagination', '?ngModel'],
|
||||
restrict: 'A',
|
||||
controller: 'UibPaginationController',
|
||||
controllerAs: 'pagination',
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/pagination/pagination.html';
|
||||
},
|
||||
replace: true,
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
element.addClass('pagination');
|
||||
var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
||||
|
||||
if (!ngModelCtrl) {
|
||||
|
||||
@@ -11,7 +11,7 @@ describe('pagination directive', function() {
|
||||
$document = _$document_;
|
||||
$templateCache = _$templateCache_;
|
||||
body = $document.find('body');
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
}));
|
||||
|
||||
@@ -54,11 +54,20 @@ describe('pagination directive', function() {
|
||||
expect(element.hasClass('pagination')).toBe(true);
|
||||
});
|
||||
|
||||
it('has accessibility attributes', function() {
|
||||
expect(element.attr('role')).toEqual('menu');
|
||||
|
||||
var li = element.find('li');
|
||||
for (var i = 0; i < li.length; i++) {
|
||||
expect(li.eq(i).attr('role')).toEqual('menuitem');
|
||||
}
|
||||
});
|
||||
|
||||
it('exposes the controller to the template', function() {
|
||||
$templateCache.put('uib/template/pagination/pagination.html', '<div>{{pagination.randomText}}</div>');
|
||||
var scope = $rootScope.$new();
|
||||
|
||||
element = $compile('<uib-pagination></uib-pagination>')(scope);
|
||||
element = $compile('<ul uib-pagination></ul>')(scope);
|
||||
$rootScope.$digest();
|
||||
|
||||
var ctrl = element.controller('uibPagination');
|
||||
@@ -68,17 +77,17 @@ describe('pagination directive', function() {
|
||||
ctrl.randomText = 'foo';
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(element.html()).toBe('foo');
|
||||
expect(element.html()).toBe('<div class="ng-binding">foo</div>');
|
||||
});
|
||||
|
||||
it('allows custom templates', function() {
|
||||
$templateCache.put('foo/bar.html', '<div>baz</div>');
|
||||
var scope = $rootScope.$new();
|
||||
|
||||
element = $compile('<uib-pagination template-url="foo/bar.html"></uib-pagination>')(scope);
|
||||
element = $compile('<ul uib-pagination template-url="foo/bar.html"></ul>')(scope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(element.html()).toBe('baz');
|
||||
expect(element.html()).toBe('<div>baz</div>');
|
||||
});
|
||||
|
||||
it('contains num-pages + 2 li elements', function() {
|
||||
@@ -213,7 +222,7 @@ describe('pagination directive', function() {
|
||||
describe('`items-per-page`', function() {
|
||||
beforeEach(function() {
|
||||
$rootScope.perpage = 5;
|
||||
element = $compile('<uib-pagination total-items="total" items-per-page="perpage" ng-model="currentPage"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" items-per-page="perpage" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -256,7 +265,7 @@ describe('pagination directive', function() {
|
||||
describe('executes `ng-change` expression', function() {
|
||||
beforeEach(function() {
|
||||
$rootScope.selectPageHandler = jasmine.createSpy('selectPageHandler');
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" ng-change="selectPageHandler()"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" ng-change="selectPageHandler()"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -286,7 +295,7 @@ describe('pagination directive', function() {
|
||||
$rootScope.total = 98; // 10 pages
|
||||
$rootScope.currentPage = 3;
|
||||
$rootScope.maxSize = 5;
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -367,7 +376,7 @@ describe('pagination directive', function() {
|
||||
$rootScope.total = 98; // 10 pages
|
||||
$rootScope.currentPage = 3;
|
||||
$rootScope.maxSize = 5;
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize" force-ellipses="true"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize" force-ellipses="true"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -429,7 +438,7 @@ describe('pagination directive', function() {
|
||||
$rootScope.total = 98; // 10 pages
|
||||
$rootScope.currentPage = 3;
|
||||
$rootScope.maxSize = 5;
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize" boundary-link-numbers="true"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize" boundary-link-numbers="true"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -522,7 +531,7 @@ describe('pagination directive', function() {
|
||||
$rootScope.currentPage = 7;
|
||||
$rootScope.maxSize = 5;
|
||||
$rootScope.rotate = false;
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize" rotate="rotate"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" max-size="maxSize" rotate="rotate"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -581,7 +590,7 @@ describe('pagination directive', function() {
|
||||
|
||||
describe('pagination directive with `boundary-links`', function() {
|
||||
beforeEach(function() {
|
||||
element = $compile('<uib-pagination boundary-links="true" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination boundary-links="true" total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -636,7 +645,7 @@ describe('pagination directive', function() {
|
||||
});
|
||||
|
||||
it('changes "first" & "last" text from attributes', function() {
|
||||
element = $compile('<uib-pagination boundary-links="true" first-text="<<<" last-text=">>>" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination boundary-links="true" first-text="<<<" last-text=">>>" total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(getPaginationEl(0).text()).toBe('<<<');
|
||||
@@ -644,7 +653,7 @@ describe('pagination directive', function() {
|
||||
});
|
||||
|
||||
it('changes "previous" & "next" text from attributes', function() {
|
||||
element = $compile('<uib-pagination boundary-links="true" previous-text="<<" next-text=">>" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination boundary-links="true" previous-text="<<" next-text=">>" total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(getPaginationEl(1).text()).toBe('<<');
|
||||
@@ -654,7 +663,7 @@ describe('pagination directive', function() {
|
||||
it('changes "first" & "last" text from interpolated attributes', function() {
|
||||
$rootScope.myfirstText = '<<<';
|
||||
$rootScope.mylastText = '>>>';
|
||||
element = $compile('<uib-pagination boundary-links="true" first-text="{{myfirstText}}" last-text="{{mylastText}}" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination boundary-links="true" first-text="{{myfirstText}}" last-text="{{mylastText}}" total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(getPaginationEl(0).text()).toBe('<<<');
|
||||
@@ -664,7 +673,7 @@ describe('pagination directive', function() {
|
||||
it('changes "previous" & "next" text from interpolated attributes', function() {
|
||||
$rootScope.previousText = '<<';
|
||||
$rootScope.nextText = '>>';
|
||||
element = $compile('<uib-pagination boundary-links="true" previous-text="{{previousText}}" next-text="{{nextText}}" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination boundary-links="true" previous-text="{{previousText}}" next-text="{{nextText}}" total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(getPaginationEl(1).text()).toBe('<<');
|
||||
@@ -700,7 +709,7 @@ describe('pagination directive', function() {
|
||||
|
||||
describe('pagination directive with just number links', function() {
|
||||
beforeEach(function() {
|
||||
element = $compile('<uib-pagination direction-links="false" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination direction-links="false" total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -752,7 +761,7 @@ describe('pagination directive', function() {
|
||||
describe('with just boundary & number links', function() {
|
||||
beforeEach(function() {
|
||||
$rootScope.directions = false;
|
||||
element = $compile('<uib-pagination boundary-links="true" direction-links="directions" total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination boundary-links="true" direction-links="directions" total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -784,7 +793,7 @@ describe('pagination directive', function() {
|
||||
describe('`num-pages`', function() {
|
||||
beforeEach(function() {
|
||||
$rootScope.numpg = null;
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" num-pages="numpg"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" num-pages="numpg"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -827,7 +836,7 @@ describe('pagination directive', function() {
|
||||
paginationConfig.previousText = 'PR';
|
||||
paginationConfig.nextText = 'NE';
|
||||
paginationConfig.lastText = 'LA';
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(getPaginationEl(0).text()).toBe('FI');
|
||||
@@ -838,7 +847,7 @@ describe('pagination directive', function() {
|
||||
|
||||
it('contains number of pages + 2 li elements', function() {
|
||||
paginationConfig.itemsPerPage = 5;
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(getPaginationBarSize()).toBe(12);
|
||||
@@ -846,7 +855,7 @@ describe('pagination directive', function() {
|
||||
|
||||
it('should take maxSize defaults into account', function() {
|
||||
paginationConfig.maxSize = 2;
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(getPaginationBarSize()).toBe(4);
|
||||
@@ -854,7 +863,7 @@ describe('pagination directive', function() {
|
||||
|
||||
it('should take forceEllipses defaults into account', function () {
|
||||
paginationConfig.forceEllipses = true;
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" max-size="2"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" max-size="2"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
// Should contain 2 nav buttons, 2 pages, and 2 ellipsis since the currentPage defaults to 3, which is in the middle
|
||||
@@ -865,7 +874,7 @@ describe('pagination directive', function() {
|
||||
paginationConfig.boundaryLinkNumbers = true;
|
||||
$rootScope.total = 88; // 9 pages
|
||||
$rootScope.currentPage = 5;
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" max-size="3"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" max-size="3"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
// Should contain 2 nav buttons, 2 pages, 2 ellipsis, and 2 extra end numbers since the currentPage is in the middle
|
||||
@@ -879,7 +888,7 @@ describe('pagination directive', function() {
|
||||
$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);
|
||||
element = $compile('<ul uib-pagination boundary-links="true" page-label="pageLabel($page)" first-text="<<" previous-text="<" next-text=">" last-text=">>" total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
|
||||
@@ -904,7 +913,7 @@ describe('pagination directive', function() {
|
||||
|
||||
describe('disabled with ngDisable', function() {
|
||||
beforeEach(function() {
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage" ng-disabled="disabled"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage" ng-disabled="disabled"></ul>')($rootScope);
|
||||
$rootScope.currentPage = 3;
|
||||
$rootScope.$digest();
|
||||
});
|
||||
@@ -941,7 +950,7 @@ describe('pagination directive', function() {
|
||||
it('should retain the model value when total-items starts as undefined', function() {
|
||||
$rootScope.currentPage = 5;
|
||||
$rootScope.total = undefined;
|
||||
element = $compile('<uib-pagination total-items="total" ng-model="currentPage"></uib-pagination>')($rootScope);
|
||||
element = $compile('<ul uib-pagination total-items="total" ng-model="currentPage"></ul>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.currentPage).toBe(5);
|
||||
|
||||
@@ -34,13 +34,15 @@
|
||||
<hr />
|
||||
<h4>Triggers</h4>
|
||||
<p>
|
||||
<button uib-popover="I appeared on mouse enter!" popover-trigger="mouseenter" type="button" class="btn btn-default">Mouseenter</button>
|
||||
<button uib-popover="I appeared on mouse enter!" popover-trigger="'mouseenter'" type="button" class="btn btn-default">Mouseenter</button>
|
||||
</p>
|
||||
<input type="text" value="Click me!" uib-popover="I appeared on focus! Click away and I'll vanish..." popover-trigger="focus" class="form-control">
|
||||
<input type="text" value="Click me!" uib-popover="I appeared on focus! Click away and I'll vanish..." popover-trigger="'focus'" class="form-control">
|
||||
|
||||
<hr />
|
||||
<h4>Other</h4>
|
||||
<button popover-animation="true" uib-popover="I fade in and out!" type="button" class="btn btn-default">fading</button>
|
||||
<button uib-popover="I have a title!" popover-title="The title." type="button" class="btn btn-default">title</button>
|
||||
<button uib-popover="I am activated manually" popover-is-open="popoverIsOpen" ng-click="popoverIsOpen = !popoverIsOpen" type="button" class="btn btn-default">Toggle popover</button>
|
||||
<button uib-popover-html="htmlPopover" class="btn btn-default">HTML Popover</button>
|
||||
<button uib-popover-html="'<b>HTML</b>, <i>inline</i>'" class="btn btn-default">HTML Popover (inline)</button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module('ui.bootstrap.demo').controller('PopoverDemoCtrl', function ($scope) {
|
||||
angular.module('ui.bootstrap.demo').controller('PopoverDemoCtrl', function ($scope, $sce) {
|
||||
$scope.dynamicPopover = {
|
||||
content: 'Hello, World!',
|
||||
templateUrl: 'myPopoverTemplate.html',
|
||||
@@ -22,4 +22,6 @@ angular.module('ui.bootstrap.demo').controller('PopoverDemoCtrl', function ($sco
|
||||
],
|
||||
selected: 'top'
|
||||
};
|
||||
|
||||
$scope.htmlPopover = $sce.trustAsHtml('<b style="color: red">I can</b> have <div class="label label-success">HTML</div> content');
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ module.
|
||||
__Note to mobile developers__: Please note that while popovers may work correctly on mobile devices (including tablets),
|
||||
we have made the decision to not officially support such a use-case because it does not make sense from a UX perspective.
|
||||
|
||||
There are three versions of the popover: `uib-popover` and `uib-popover-template`, and `uib-tooltip-html`:
|
||||
There are three versions of the popover: `uib-popover` and `uib-popover-template`, and `uib-popover-html`:
|
||||
|
||||
* `uib-popover` -
|
||||
Takes text only and will escape any HTML provided for the popover body.
|
||||
@@ -27,13 +27,21 @@ All these settings are available for the three types of popovers.
|
||||
<small class="badge">C</small>
|
||||
_(Default: `true`, Config: `animation`)_ -
|
||||
Should it fade in and out?
|
||||
|
||||
|
||||
* `popover-append-to-body`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
_(Default: `false`, Config: `appendToBody`)_ -
|
||||
Should the popover be appended to '$body' instead of the parent element?
|
||||
|
||||
|
||||
* `popover-class` -
|
||||
Custom class to be applied to the popover.
|
||||
|
||||
* `popover-enable`
|
||||
<small class="badge">$</small>
|
||||
_(Default: `true`)_ -
|
||||
Is it enabled? It will enable or disable the configured popover-trigger.
|
||||
|
||||
* `popover-is-open`
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `false`)_ -
|
||||
@@ -43,7 +51,7 @@ All these settings are available for the three types of popovers.
|
||||
<small class="badge">C</small>
|
||||
_(Default: `top`, Config: `placement`)_ -
|
||||
Passing in 'auto' separated by a space before the placement will enable auto positioning, e.g: "auto bottom-left". The popover will attempt to position where it fits in the closest scrollable ancestor. Accepts:
|
||||
|
||||
|
||||
* `top` - popover on top, horizontally centered on host element.
|
||||
* `top-left` - popover on top, left edge aligned with host element left edge.
|
||||
* `top-right` - popover on top, right edge aligned with host element right edge.
|
||||
@@ -56,7 +64,7 @@ All these settings are available for the three types of popovers.
|
||||
* `right` - popover on right, vertically centered on host element.
|
||||
* `right-top` - popover on right, top edge aligned with host element top edge.
|
||||
* `right-bottom` - popover on right, bottom edge aligned with host element bottom edge.
|
||||
|
||||
|
||||
* `popover-popup-close-delay`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `0`, Config: `popupCloseDelay`)_ -
|
||||
@@ -69,10 +77,11 @@ All these settings are available for the three types of popovers.
|
||||
|
||||
* `popover-title` -
|
||||
A string to display as a fancy title.
|
||||
|
||||
|
||||
* `popover-trigger`
|
||||
_(Default: `click`)_ -
|
||||
What should trigger a show of the popover? Supports a space separated list of event names (see below).
|
||||
<small class="badge">$</small>
|
||||
_(Default: `'click'`)_ -
|
||||
What should trigger a show of the popover? Supports a space separated list of event names, or objects (see below).
|
||||
|
||||
**Note:** To configure the tooltips, you need to do it on `$uibTooltipProvider` (also see below).
|
||||
|
||||
@@ -101,7 +110,7 @@ methods are available:
|
||||
* `setTriggers(obj)`
|
||||
_(Example: `{ 'openTrigger': 'closeTrigger' }`)_ -
|
||||
Extends the default trigger mappings mentioned above with mappings of your own.
|
||||
|
||||
|
||||
* `options(obj)` -
|
||||
Provide a set of defaults for certain tooltip and popover attributes. Currently supports the ones with the <small class="badge">C</small> badge.
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
require('../tooltip/index-nocss.js');
|
||||
require('../../template/popover/popover.html.js');
|
||||
require('../../template/popover/popover-html.html.js');
|
||||
require('../../template/popover/popover-template.html.js');
|
||||
require('./popover');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.popover';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.popover', 'uib/template/popover/popover.html', 'uib/template/popover/popover-html.html', 'uib/template/popover/popover-template.html']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
+2
-11
@@ -1,11 +1,2 @@
|
||||
require('../tooltip');
|
||||
require('../../template/popover/popover.html.js');
|
||||
require('../../template/popover/popover-html.html.js');
|
||||
require('../../template/popover/popover-template.html.js');
|
||||
require('./popover');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.popover';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.popover', 'uib/template/popover/popover.html', 'uib/template/popover/popover-html.html', 'uib/template/popover/popover-template.html']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
require('../tooltip/tooltip.css');
|
||||
module.exports = require('./index-nocss.js');
|
||||
|
||||
@@ -7,9 +7,8 @@ angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
|
||||
|
||||
.directive('uibPopoverTemplatePopup', function() {
|
||||
return {
|
||||
replace: true,
|
||||
scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
|
||||
originScope: '&' },
|
||||
restrict: 'A',
|
||||
scope: { uibTitle: '@', contentExp: '&', originScope: '&' },
|
||||
templateUrl: 'uib/template/popover/popover-template.html'
|
||||
};
|
||||
})
|
||||
@@ -22,8 +21,8 @@ angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
|
||||
|
||||
.directive('uibPopoverHtmlPopup', function() {
|
||||
return {
|
||||
replace: true,
|
||||
scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
|
||||
restrict: 'A',
|
||||
scope: { contentExp: '&', uibTitle: '@' },
|
||||
templateUrl: 'uib/template/popover/popover-html.html'
|
||||
};
|
||||
})
|
||||
@@ -36,8 +35,8 @@ angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
|
||||
|
||||
.directive('uibPopoverPopup', function() {
|
||||
return {
|
||||
replace: true,
|
||||
scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
|
||||
restrict: 'A',
|
||||
scope: { uibTitle: '@', content: '@' },
|
||||
templateUrl: 'uib/template/popover/popover.html'
|
||||
};
|
||||
})
|
||||
|
||||
@@ -21,6 +21,7 @@ describe('popover', function() {
|
||||
scope.template = $sce.trustAsHtml('<span>My template</span>');
|
||||
$compile(elmBody)(scope);
|
||||
scope.$digest();
|
||||
$document.find('body').append(elmBody);
|
||||
elm = elmBody.find('span');
|
||||
elmScope = elm.scope();
|
||||
tooltipScope = elmScope.$$childTail;
|
||||
@@ -87,6 +88,7 @@ describe('popover', function() {
|
||||
it('should hide popover when template becomes empty', inject(function($timeout) {
|
||||
elm.trigger('click');
|
||||
tooltipScope.$digest();
|
||||
$timeout.flush(0);
|
||||
expect(tooltipScope.isOpen).toBe(true);
|
||||
|
||||
scope.template = '';
|
||||
@@ -147,6 +149,22 @@ describe('popover', function() {
|
||||
expect(elmBody.children().eq(1)).not.toHaveClass('fade');
|
||||
}));
|
||||
|
||||
it ('should display the title', inject(function($compile) {
|
||||
elmBody = angular.element(
|
||||
'<div><span uib-popover-html="template" popover-title="popover title">Selector Text</span></div>'
|
||||
);
|
||||
|
||||
$compile(elmBody)(scope);
|
||||
scope.$digest();
|
||||
elm = elmBody.find('span');
|
||||
|
||||
elm.trigger('click');
|
||||
scope.$digest();
|
||||
|
||||
var titleEl = elmBody.find('.popover-title');
|
||||
expect(titleEl.text()).toBe('popover title');
|
||||
}));
|
||||
|
||||
describe('supports options', function() {
|
||||
describe('placement', function() {
|
||||
it('can specify an alternative, valid placement', inject(function($compile) {
|
||||
|
||||
@@ -25,6 +25,7 @@ describe('popover template', function() {
|
||||
|
||||
scope = $rootScope;
|
||||
$compile(elmBody)(scope);
|
||||
$document.find('body').append(elmBody);
|
||||
scope.templateUrl = 'myUrl';
|
||||
|
||||
scope.$digest();
|
||||
@@ -35,6 +36,7 @@ describe('popover template', function() {
|
||||
|
||||
afterEach(function() {
|
||||
$document.off('keypress');
|
||||
elmBody.remove();
|
||||
});
|
||||
|
||||
it('should open on click', inject(function() {
|
||||
@@ -75,6 +77,7 @@ describe('popover template', function() {
|
||||
it('should hide popover when template becomes empty', inject(function($timeout) {
|
||||
elm.trigger('click');
|
||||
tooltipScope.$digest();
|
||||
$timeout.flush(0);
|
||||
expect(tooltipScope.isOpen).toBe(true);
|
||||
|
||||
scope.templateUrl = '';
|
||||
@@ -86,6 +89,22 @@ describe('popover template', function() {
|
||||
expect(elmBody.children().length).toBe(1);
|
||||
}));
|
||||
|
||||
it ('should display the title', inject(function($compile) {
|
||||
elmBody = angular.element(
|
||||
'<div><span uib-popover-template="templateUrl" popover-title="popover title">Selector Text</span></div>'
|
||||
);
|
||||
|
||||
$compile(elmBody)(scope);
|
||||
scope.$digest();
|
||||
elm = elmBody.find('span');
|
||||
|
||||
elm.trigger('click');
|
||||
scope.$digest();
|
||||
|
||||
var titleEl = elmBody.find('.popover-title');
|
||||
expect(titleEl.text()).toBe('popover title');
|
||||
}));
|
||||
|
||||
describe('supports options', function() {
|
||||
describe('placement', function() {
|
||||
it('can specify an alternative, valid placement', inject(function($compile) {
|
||||
|
||||
@@ -103,6 +103,36 @@ describe('popover', function() {
|
||||
expect(elmBody.children().eq(1)).not.toHaveClass('fade');
|
||||
}));
|
||||
|
||||
it ('should display the title', inject(function($compile) {
|
||||
elmBody = angular.element(
|
||||
'<div><span uib-popover="popover text" popover-title="popover title">Trigger here</span></div>'
|
||||
);
|
||||
$compile(elmBody)(scope);
|
||||
scope.$digest();
|
||||
|
||||
elm = elmBody.find('span');
|
||||
elm.trigger('click');
|
||||
scope.$digest();
|
||||
|
||||
var titleEl = elmBody.find('.popover-title');
|
||||
expect(titleEl.text()).toBe('popover title');
|
||||
}));
|
||||
|
||||
it ('should display the content', inject(function($compile) {
|
||||
elmBody = angular.element(
|
||||
'<div><span uib-popover="popover text" popover-title="popover title">Trigger here</span></div>'
|
||||
);
|
||||
$compile(elmBody)(scope);
|
||||
scope.$digest();
|
||||
|
||||
elm = elmBody.find('span');
|
||||
elm.trigger('click');
|
||||
scope.$digest();
|
||||
|
||||
var contentEl = elmBody.find('.popover-content');
|
||||
expect(contentEl.text()).toBe('popover text');
|
||||
}));
|
||||
|
||||
describe('supports options', function() {
|
||||
describe('placement', function() {
|
||||
it('can specify an alternative, valid placement', inject(function($compile) {
|
||||
|
||||
@@ -45,15 +45,60 @@ Gets the closest positioned ancestor.
|
||||
* _(Type: `element`)_ -
|
||||
The closest positioned ancestor.
|
||||
|
||||
#### scrollbarWidth()
|
||||
#### scrollbarWidth(isBody)
|
||||
|
||||
Calculates the browser scrollbar width and caches the result for future calls. Concept from the TWBS measureScrollbar() function in [modal.js](https://github.com/twbs/bootstrap/blob/master/js/modal.js).
|
||||
|
||||
##### parameters
|
||||
|
||||
* `isBody`
|
||||
_(Type: `boolean`, Default: `false`, optional)_ - Is the requested scrollbar width for the body/html element. IE and Edge overlay the scrollbar on the body/html element and should be considered 0.
|
||||
|
||||
##### returns
|
||||
|
||||
* _(Type: `number`)_ -
|
||||
The width of the browser scrollbar.
|
||||
|
||||
#### scrollbarPadding(element)
|
||||
|
||||
Calculates the padding required to replace the scrollbar on an element.
|
||||
|
||||
##### parameters
|
||||
|
||||
* 'element' _(Type: `element`)_ - The element to calculate the padding on (should be a scrollable element).
|
||||
|
||||
##### returns
|
||||
|
||||
An object with the following properties:
|
||||
|
||||
* `scrollbarWidth`
|
||||
_(Type: `number`)_ -
|
||||
The width of the scrollbar.
|
||||
|
||||
* `widthOverflow`
|
||||
_(Type: `boolean`)_ -
|
||||
Whether the width is overflowing.
|
||||
|
||||
* `right`
|
||||
_(Type: `number`)_ -
|
||||
The total right padding required to replace the scrollbar.
|
||||
|
||||
* `originalRight`
|
||||
_(Type: `number`)_ -
|
||||
The oringal right padding on the element.
|
||||
|
||||
* `heightOverflow`
|
||||
_(Type: `boolean`)_ -
|
||||
Whether the height is overflowing.
|
||||
|
||||
* `bottom`
|
||||
_(Type: `number`)_ -
|
||||
The total bottom padding required to replace the scrollbar.
|
||||
|
||||
* `originalBottom`
|
||||
_(Type: `number`)_ -
|
||||
The oringal bottom padding on the element.
|
||||
|
||||
#### isScrollable(element, includeHidden)
|
||||
|
||||
Determines if an element is scrollable.
|
||||
@@ -72,7 +117,7 @@ Determines if an element is scrollable.
|
||||
* _(Type: `boolean`)_ -
|
||||
Whether the element is scrollable.
|
||||
|
||||
#### scrollParent(element, includeHidden)
|
||||
#### scrollParent(element, includeHidden, includeSelf)
|
||||
|
||||
Gets the closest scrollable ancestor. Concept from the jQueryUI [scrollParent.js](https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js).
|
||||
|
||||
@@ -85,6 +130,9 @@ Gets the closest scrollable ancestor. Concept from the jQueryUI [scrollParent.j
|
||||
* `includeHidden`
|
||||
_(Type: `boolean`, Default: `false`, optional)_ - Should scroll style of 'hidden' be considered.
|
||||
|
||||
* `includeSelf`
|
||||
_(Type: `boolean`, Default: `false`, optional)_ - Should the element passed in be included in the scrollable lookup.
|
||||
|
||||
##### returns
|
||||
|
||||
* _(Type: `element`)_ -
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
require('./position');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.position';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.position']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
@@ -1,9 +1,2 @@
|
||||
require('./position');
|
||||
|
||||
require('./position.css');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.position';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.position']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
module.exports = require('./index-nocss.js');
|
||||
|
||||
@@ -7,9 +7,13 @@
|
||||
}
|
||||
|
||||
.uib-position-scrollbar-measure {
|
||||
position: absolute;
|
||||
top: -9999px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
overflow: scroll;
|
||||
position: absolute !important;
|
||||
top: -9999px !important;
|
||||
width: 50px !important;
|
||||
height: 50px !important;
|
||||
overflow: scroll !important;
|
||||
}
|
||||
|
||||
.uib-position-body-scrollbar-measure {
|
||||
overflow: scroll !important;
|
||||
}
|
||||
@@ -12,6 +12,11 @@ angular.module('ui.bootstrap.position', [])
|
||||
* Do not access this variable directly, use scrollbarWidth() instead.
|
||||
*/
|
||||
var SCROLLBAR_WIDTH;
|
||||
/**
|
||||
* scrollbar on body and html element in IE and Edge overlay
|
||||
* content and should be considered 0 width.
|
||||
*/
|
||||
var BODY_SCROLLBAR_WIDTH;
|
||||
var OVERFLOW_REGEX = {
|
||||
normal: /(auto|scroll)/,
|
||||
hidden: /(auto|scroll|hidden)/
|
||||
@@ -22,6 +27,7 @@ angular.module('ui.bootstrap.position', [])
|
||||
secondary: /^(top|bottom|left|right|center)$/,
|
||||
vertical: /^(top|bottom)$/
|
||||
};
|
||||
var BODY_REGEX = /(HTML|BODY)/;
|
||||
|
||||
return {
|
||||
|
||||
@@ -75,10 +81,23 @@ angular.module('ui.bootstrap.position', [])
|
||||
/**
|
||||
* Provides the scrollbar width, concept from TWBS measureScrollbar()
|
||||
* function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
|
||||
* In IE and Edge, scollbar on body and html element overlay and should
|
||||
* return a width of 0.
|
||||
*
|
||||
* @returns {number} The width of the browser scollbar.
|
||||
*/
|
||||
scrollbarWidth: function() {
|
||||
scrollbarWidth: function(isBody) {
|
||||
if (isBody) {
|
||||
if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) {
|
||||
var bodyElem = $document.find('body');
|
||||
bodyElem.addClass('uib-position-body-scrollbar-measure');
|
||||
BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth;
|
||||
BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0;
|
||||
bodyElem.removeClass('uib-position-body-scrollbar-measure');
|
||||
}
|
||||
return BODY_SCROLLBAR_WIDTH;
|
||||
}
|
||||
|
||||
if (angular.isUndefined(SCROLLBAR_WIDTH)) {
|
||||
var scrollElem = angular.element('<div class="uib-position-scrollbar-measure"></div>');
|
||||
$document.find('body').append(scrollElem);
|
||||
@@ -90,6 +109,40 @@ angular.module('ui.bootstrap.position', [])
|
||||
return SCROLLBAR_WIDTH;
|
||||
},
|
||||
|
||||
/**
|
||||
* Provides the padding required on an element to replace the scrollbar.
|
||||
*
|
||||
* @returns {object} An object with the following properties:
|
||||
* <ul>
|
||||
* <li>**scrollbarWidth**: the width of the scrollbar</li>
|
||||
* <li>**widthOverflow**: whether the the width is overflowing</li>
|
||||
* <li>**right**: the amount of right padding on the element needed to replace the scrollbar</li>
|
||||
* <li>**rightOriginal**: the amount of right padding currently on the element</li>
|
||||
* <li>**heightOverflow**: whether the the height is overflowing</li>
|
||||
* <li>**bottom**: the amount of bottom padding on the element needed to replace the scrollbar</li>
|
||||
* <li>**bottomOriginal**: the amount of bottom padding currently on the element</li>
|
||||
* </ul>
|
||||
*/
|
||||
scrollbarPadding: function(elem) {
|
||||
elem = this.getRawNode(elem);
|
||||
|
||||
var elemStyle = $window.getComputedStyle(elem);
|
||||
var paddingRight = this.parseStyle(elemStyle.paddingRight);
|
||||
var paddingBottom = this.parseStyle(elemStyle.paddingBottom);
|
||||
var scrollParent = this.scrollParent(elem, false, true);
|
||||
var scrollbarWidth = this.scrollbarWidth(BODY_REGEX.test(scrollParent.tagName));
|
||||
|
||||
return {
|
||||
scrollbarWidth: scrollbarWidth,
|
||||
widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth,
|
||||
right: paddingRight + scrollbarWidth,
|
||||
originalRight: paddingRight,
|
||||
heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight,
|
||||
bottom: paddingBottom + scrollbarWidth,
|
||||
originalBottom: paddingBottom
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks to see if the element is scrollable.
|
||||
*
|
||||
@@ -115,15 +168,20 @@ angular.module('ui.bootstrap.position', [])
|
||||
* @param {element} elem - The element to find the scroll parent of.
|
||||
* @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
|
||||
* default is false.
|
||||
* @param {boolean=} [includeSelf=false] - Should the element being passed be
|
||||
* included in the scrollable llokup.
|
||||
*
|
||||
* @returns {element} A HTML element.
|
||||
*/
|
||||
scrollParent: function(elem, includeHidden) {
|
||||
scrollParent: function(elem, includeHidden, includeSelf) {
|
||||
elem = this.getRawNode(elem);
|
||||
|
||||
var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
|
||||
var documentEl = $document[0].documentElement;
|
||||
var elemStyle = $window.getComputedStyle(elem);
|
||||
if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) {
|
||||
return elem;
|
||||
}
|
||||
var excludeStatic = elemStyle.position === 'absolute';
|
||||
var scrollParent = elem.parentElement || documentEl;
|
||||
|
||||
@@ -471,13 +529,33 @@ angular.module('ui.bootstrap.position', [])
|
||||
},
|
||||
|
||||
/**
|
||||
* Provides a way for positioning tooltip & dropdown
|
||||
* arrows when using placement options beyond the standard
|
||||
* left, right, top, or bottom.
|
||||
*
|
||||
* @param {element} elem - The tooltip/dropdown element.
|
||||
* @param {string} placement - The placement for the elem.
|
||||
*/
|
||||
* Provides a way to adjust the top positioning after first
|
||||
* render to correctly align element to top after content
|
||||
* rendering causes resized element height
|
||||
*
|
||||
* @param {array} placementClasses - The array of strings of classes
|
||||
* element should have.
|
||||
* @param {object} containerPosition - The object with container
|
||||
* position information
|
||||
* @param {number} initialHeight - The initial height for the elem.
|
||||
* @param {number} currentHeight - The current height for the elem.
|
||||
*/
|
||||
adjustTop: function(placementClasses, containerPosition, initialHeight, currentHeight) {
|
||||
if (placementClasses.indexOf('top') !== -1 && initialHeight !== currentHeight) {
|
||||
return {
|
||||
top: containerPosition.top - currentHeight + 'px'
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Provides a way for positioning tooltip & dropdown
|
||||
* arrows when using placement options beyond the standard
|
||||
* left, right, top, or bottom.
|
||||
*
|
||||
* @param {element} elem - The tooltip/dropdown element.
|
||||
* @param {string} placement - The placement for the elem.
|
||||
*/
|
||||
positionArrow: function(elem, placement) {
|
||||
elem = this.getRawNode(elem);
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ It supports multiple (stacked) `<uib-bar>` into the same `<uib-progress>` elemen
|
||||
|
||||
* `type`
|
||||
_(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.
|
||||
|
||||
* `title`
|
||||
_(Default: `progressbar`)_ -
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div ng-controller="RatingDemoCtrl">
|
||||
<h4>Default</h4>
|
||||
<uib-rating ng-model="rate" max="max" read-only="isReadonly" on-hover="hoveringOver(value)" on-leave="overStar = null" titles="['one','two','three']" aria-labelledby="default-rating"></uib-rating>
|
||||
<span uib-rating ng-model="rate" max="max" read-only="isReadonly" on-hover="hoveringOver(value)" on-leave="overStar = null" titles="['one','two','three']" aria-labelledby="default-rating"></span>
|
||||
<span class="label" ng-class="{'label-warning': percent<30, 'label-info': percent>=30 && percent<70, 'label-success': percent>=70}" ng-show="overStar && !isReadonly">{{percent}}%</span>
|
||||
|
||||
<pre style="margin:15px 0;">Rate: <b>{{rate}}</b> - Readonly is: <i>{{isReadonly}}</i> - Hovering over: <b>{{overStar || "none"}}</b></pre>
|
||||
@@ -10,6 +10,6 @@
|
||||
<hr />
|
||||
|
||||
<h4>Custom icons</h4>
|
||||
<div ng-init="x = 5"><uib-rating ng-model="x" max="15" state-on="'glyphicon-ok-sign'" state-off="'glyphicon-ok-circle'" aria-labelledby="custom-icons-1"></uib-rating> <b>(<i>Rate:</i> {{x}})</b></div>
|
||||
<div ng-init="y = 2"><uib-rating ng-model="y" rating-states="ratingStates" aria-labelledby="custom-icons-2"></uib-rating> <b>(<i>Rate:</i> {{y}})</b></div>
|
||||
<div ng-init="x = 5"><span uib-rating ng-model="x" max="15" state-on="'glyphicon-ok-sign'" state-off="'glyphicon-ok-circle'" aria-labelledby="custom-icons-1"></span> <b>(<i>Rate:</i> {{x}})</b></div>
|
||||
<div ng-init="y = 2"><span uib-rating ng-model="y" rating-states="ratingStates" aria-labelledby="custom-icons-2"></span> <b>(<i>Rate:</i> {{y}})</b></div>
|
||||
</div>
|
||||
|
||||
@@ -38,6 +38,11 @@ Rating directive that will take care of visualising a star rating bar.
|
||||
_(Default: ['one', 'two', 'three', 'four', 'five']`)_ -
|
||||
An array of strings defining titles for all icons.
|
||||
|
||||
* `enable-reset`
|
||||
<small class="badge">$</small>
|
||||
_(Default: `true`)_ -
|
||||
Clicking the icon of the current rating will reset the rating to 0.
|
||||
|
||||
* `state-off`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
|
||||
@@ -4,7 +4,8 @@ angular.module('ui.bootstrap.rating', [])
|
||||
max: 5,
|
||||
stateOn: null,
|
||||
stateOff: null,
|
||||
titles : ['one', 'two', 'three', 'four', 'five']
|
||||
enableReset: true,
|
||||
titles: ['one', 'two', 'three', 'four', 'five']
|
||||
})
|
||||
|
||||
.controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
|
||||
@@ -25,7 +26,9 @@ angular.module('ui.bootstrap.rating', [])
|
||||
|
||||
this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
|
||||
this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
|
||||
var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ;
|
||||
this.enableReset = angular.isDefined($attrs.enableReset) ?
|
||||
$scope.$parent.$eval($attrs.enableReset) : ratingConfig.enableReset;
|
||||
var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles;
|
||||
this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
|
||||
tmpTitles : ratingConfig.titles;
|
||||
|
||||
@@ -52,7 +55,8 @@ angular.module('ui.bootstrap.rating', [])
|
||||
|
||||
$scope.rate = function(value) {
|
||||
if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
|
||||
ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue === value ? 0 : value);
|
||||
var newViewValue = self.enableReset && ngModelCtrl.$viewValue === value ? 0 : value;
|
||||
ngModelCtrl.$setViewValue(newViewValue);
|
||||
ngModelCtrl.$render();
|
||||
}
|
||||
};
|
||||
@@ -86,6 +90,7 @@ angular.module('ui.bootstrap.rating', [])
|
||||
.directive('uibRating', function() {
|
||||
return {
|
||||
require: ['uibRating', 'ngModel'],
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
readonly: '=?readOnly',
|
||||
onHover: '&',
|
||||
@@ -93,7 +98,6 @@ angular.module('ui.bootstrap.rating', [])
|
||||
},
|
||||
controller: 'UibRatingController',
|
||||
templateUrl: 'uib/template/rating/rating.html',
|
||||
replace: true,
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
||||
ratingCtrl.init(ngModelCtrl);
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
describe('rating directive', function() {
|
||||
var $rootScope, $compile, element;
|
||||
var $rootScope, $compile, element, innerElem;
|
||||
beforeEach(module('ui.bootstrap.rating'));
|
||||
beforeEach(module('uib/template/rating/rating.html'));
|
||||
beforeEach(inject(function(_$compile_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$rootScope.rate = 3;
|
||||
element = $compile('<uib-rating ng-model="rate"></uib-rating>')($rootScope);
|
||||
element = $compile('<span uib-rating ng-model="rate"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
innerElem = element.children().eq(0);
|
||||
}));
|
||||
|
||||
function getStars() {
|
||||
return element.find('i');
|
||||
return innerElem.find('i');
|
||||
}
|
||||
|
||||
function getStar(number) {
|
||||
@@ -38,17 +39,17 @@ describe('rating directive', function() {
|
||||
function triggerKeyDown(keyCode) {
|
||||
var e = $.Event('keydown');
|
||||
e.which = keyCode;
|
||||
element.trigger(e);
|
||||
innerElem.trigger(e);
|
||||
}
|
||||
|
||||
it('contains the default number of icons', function() {
|
||||
expect(getStars().length).toBe(5);
|
||||
expect(element.attr('aria-valuemax')).toBe('5');
|
||||
expect(innerElem.attr('aria-valuemax')).toBe('5');
|
||||
});
|
||||
|
||||
it('initializes the default star icons as selected', function() {
|
||||
expect(getState()).toEqual([true, true, true, false, false]);
|
||||
expect(element.attr('aria-valuenow')).toBe('3');
|
||||
expect(innerElem.attr('aria-valuenow')).toBe('3');
|
||||
});
|
||||
|
||||
it('handles correctly the click event', function() {
|
||||
@@ -56,19 +57,19 @@ describe('rating directive', function() {
|
||||
$rootScope.$digest();
|
||||
expect(getState()).toEqual([true, true, false, false, false]);
|
||||
expect($rootScope.rate).toBe(2);
|
||||
expect(element.attr('aria-valuenow')).toBe('2');
|
||||
expect(innerElem.attr('aria-valuenow')).toBe('2');
|
||||
|
||||
getStar(5).click();
|
||||
$rootScope.$digest();
|
||||
expect(getState()).toEqual([true, true, true, true, true]);
|
||||
expect($rootScope.rate).toBe(5);
|
||||
expect(element.attr('aria-valuenow')).toBe('5');
|
||||
expect(innerElem.attr('aria-valuenow')).toBe('5');
|
||||
|
||||
getStar(5).click();
|
||||
$rootScope.$digest();
|
||||
expect(getState()).toEqual([false, false, false, false, false]);
|
||||
expect($rootScope.rate).toBe(0);
|
||||
expect(element.attr('aria-valuenow')).toBe('0');
|
||||
expect(innerElem.attr('aria-valuenow')).toBe('0');
|
||||
});
|
||||
|
||||
it('handles correctly the hover event', function() {
|
||||
@@ -82,7 +83,7 @@ describe('rating directive', function() {
|
||||
expect(getState()).toEqual([true, true, true, true, true]);
|
||||
expect($rootScope.rate).toBe(3);
|
||||
|
||||
element.trigger('mouseout');
|
||||
innerElem.trigger('mouseout');
|
||||
expect(getState()).toEqual([true, true, true, false, false]);
|
||||
expect($rootScope.rate).toBe(3);
|
||||
});
|
||||
@@ -92,13 +93,13 @@ describe('rating directive', function() {
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(getState()).toEqual([true, true, false, false, false]);
|
||||
expect(element.attr('aria-valuenow')).toBe('2');
|
||||
expect(innerElem.attr('aria-valuenow')).toBe('2');
|
||||
|
||||
$rootScope.rate = 2.5;
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(getState()).toEqual([true, true, true, false, false]);
|
||||
expect(element.attr('aria-valuenow')).toBe('3');
|
||||
expect(innerElem.attr('aria-valuenow')).toBe('3');
|
||||
});
|
||||
|
||||
it('changes the number of selected icons when value changes', function() {
|
||||
@@ -106,30 +107,33 @@ describe('rating directive', function() {
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(getState()).toEqual([true, true, false, false, false]);
|
||||
expect(element.attr('aria-valuenow')).toBe('2');
|
||||
expect(element.attr('aria-valuetext')).toBe('two');
|
||||
expect(innerElem.attr('aria-valuenow')).toBe('2');
|
||||
expect(innerElem.attr('aria-valuetext')).toBe('two');
|
||||
});
|
||||
|
||||
it('shows different number of icons when `max` attribute is set', function() {
|
||||
element = $compile('<uib-rating ng-model="rate" max="7"></uib-rating>')($rootScope);
|
||||
element = $compile('<span uib-rating ng-model="rate" max="7"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
innerElem = element.children().eq(0);
|
||||
|
||||
expect(getStars().length).toBe(7);
|
||||
expect(element.attr('aria-valuemax')).toBe('7');
|
||||
expect(innerElem.attr('aria-valuemax')).toBe('7');
|
||||
});
|
||||
|
||||
it('shows different number of icons when `max` attribute is from scope variable', function() {
|
||||
$rootScope.max = 15;
|
||||
element = $compile('<uib-rating ng-model="rate" max="max"></uib-rating>')($rootScope);
|
||||
element = $compile('<span uib-rating ng-model="rate" max="max"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
innerElem = element.children().eq(0);
|
||||
expect(getStars().length).toBe(15);
|
||||
expect(element.attr('aria-valuemax')).toBe('15');
|
||||
expect(innerElem.attr('aria-valuemax')).toBe('15');
|
||||
});
|
||||
|
||||
it('handles read-only attribute', function() {
|
||||
$rootScope.isReadonly = true;
|
||||
element = $compile('<uib-rating ng-model="rate" read-only="isReadonly"></uib-rating>')($rootScope);
|
||||
element = $compile('<span uib-rating ng-model="rate" read-only="isReadonly"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
innerElem = element.children().eq(0);
|
||||
|
||||
expect(getState()).toEqual([true, true, true, false, false]);
|
||||
|
||||
@@ -146,10 +150,37 @@ describe('rating directive', function() {
|
||||
expect(getState()).toEqual([true, true, true, true, true]);
|
||||
});
|
||||
|
||||
it('handles enable-reset attribute', function() {
|
||||
$rootScope.canReset = false;
|
||||
element = $compile('<span uib-rating ng-model="rate" enable-reset="canReset"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
innerElem = element.children().eq(0);
|
||||
|
||||
var star = {
|
||||
states: [true, true, true, true, true],
|
||||
rating: 5
|
||||
};
|
||||
|
||||
var selectStar = getStar(star.rating);
|
||||
|
||||
selectStar.click();
|
||||
$rootScope.$digest();
|
||||
expect(getState()).toEqual(star.states);
|
||||
expect($rootScope.rate).toBe(5);
|
||||
expect(innerElem.attr('aria-valuenow')).toBe('5');
|
||||
|
||||
selectStar.click();
|
||||
$rootScope.$digest();
|
||||
expect(getState()).toEqual(star.states);
|
||||
expect($rootScope.rate).toBe(5);
|
||||
expect(innerElem.attr('aria-valuenow')).toBe('5');
|
||||
});
|
||||
|
||||
it('should fire onHover', function() {
|
||||
$rootScope.hoveringOver = jasmine.createSpy('hoveringOver');
|
||||
element = $compile('<uib-rating ng-model="rate" on-hover="hoveringOver(value)"></uib-rating>')($rootScope);
|
||||
element = $compile('<span uib-rating ng-model="rate" on-hover="hoveringOver(value)"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
innerElem = element.children().eq(0);
|
||||
|
||||
getStar(3).trigger('mouseover');
|
||||
$rootScope.$digest();
|
||||
@@ -158,10 +189,11 @@ describe('rating directive', function() {
|
||||
|
||||
it('should fire onLeave', function() {
|
||||
$rootScope.leaving = jasmine.createSpy('leaving');
|
||||
element = $compile('<uib-rating ng-model="rate" on-leave="leaving()"></uib-rating>')($rootScope);
|
||||
element = $compile('<span uib-rating ng-model="rate" on-leave="leaving()"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
innerElem = element.children().eq(0);
|
||||
|
||||
element.trigger('mouseleave');
|
||||
innerElem.trigger('mouseleave');
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.leaving).toHaveBeenCalled();
|
||||
});
|
||||
@@ -218,8 +250,9 @@ describe('rating directive', function() {
|
||||
beforeEach(inject(function() {
|
||||
$rootScope.classOn = 'icon-ok-sign';
|
||||
$rootScope.classOff = 'icon-ok-circle';
|
||||
element = $compile('<uib-rating ng-model="rate" state-on="classOn" state-off="classOff"></uib-rating>')($rootScope);
|
||||
element = $compile('<span uib-rating ng-model="rate" state-on="classOn" state-off="classOff"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
innerElem = element.children().eq(0);
|
||||
}));
|
||||
|
||||
it('changes the default icons', function() {
|
||||
@@ -235,13 +268,14 @@ describe('rating directive', function() {
|
||||
{stateOn: 'heart'},
|
||||
{stateOff: 'off'}
|
||||
];
|
||||
element = $compile('<uib-rating ng-model="rate" rating-states="states"></uib-rating>')($rootScope);
|
||||
element = $compile('<span uib-rating ng-model="rate" rating-states="states"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
innerElem = element.children().eq(0);
|
||||
}));
|
||||
|
||||
it('should define number of icon elements', function() {
|
||||
expect(getStars().length).toBe(4);
|
||||
expect(element.attr('aria-valuemax')).toBe('4');
|
||||
expect(innerElem.attr('aria-valuemax')).toBe('4');
|
||||
});
|
||||
|
||||
it('handles each icon', function() {
|
||||
@@ -266,8 +300,9 @@ describe('rating directive', function() {
|
||||
uibRatingConfig.max = 10;
|
||||
uibRatingConfig.stateOn = 'on';
|
||||
uibRatingConfig.stateOff = 'off';
|
||||
element = $compile('<uib-rating ng-model="rate"></uib-rating>')($rootScope);
|
||||
element = $compile('<span uib-rating ng-model="rate"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
innerElem = element.children().eq(0);
|
||||
}));
|
||||
afterEach(inject(function(uibRatingConfig) {
|
||||
// return it to the original state
|
||||
@@ -295,8 +330,9 @@ describe('rating directive', function() {
|
||||
$rootScope.rate = 5;
|
||||
angular.extend(originalConfig, uibRatingConfig);
|
||||
uibRatingConfig.max = 10;
|
||||
element = $compile('<uib-rating ng-model="rate"></uib-rating>')($rootScope);
|
||||
element = $compile('<span uib-rating ng-model="rate"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
innerElem = element.children().eq(0);
|
||||
}));
|
||||
afterEach(inject(function(uibRatingConfig) {
|
||||
// return it to the original state
|
||||
@@ -311,18 +347,20 @@ describe('rating directive', function() {
|
||||
describe('shows custom titles ', function() {
|
||||
it('should return the custom title for each star', function() {
|
||||
$rootScope.titles = [44,45,46];
|
||||
element = $compile('<uib-rating ng-model="rate" titles="titles"></uib-rating>')($rootScope);
|
||||
element = $compile('<span uib-rating ng-model="rate" titles="titles"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
innerElem = element.children().eq(0);
|
||||
expect(getTitles()).toEqual(['44', '45', '46', '4', '5']);
|
||||
});
|
||||
it('should return the default title if the custom title is empty', function() {
|
||||
$rootScope.titles = [];
|
||||
element = $compile('<uib-rating ng-model="rate" titles="titles"></uib-rating>')($rootScope);
|
||||
element = $compile('<span uib-rating ng-model="rate" titles="titles"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
innerElem = element.children().eq(0);
|
||||
expect(getTitles()).toEqual(['one', 'two', 'three', 'four', 'five']);
|
||||
});
|
||||
it('should return the default title if the custom title is not an array', function() {
|
||||
element = $compile('<uib-rating ng-model="rate" titles="test"></uib-rating>')($rootScope);
|
||||
element = $compile('<span uib-rating ng-model="rate" titles="test"></span>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(getTitles()).toEqual(['one', 'two', 'three', 'four', 'five']);
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ angular.module('ui.bootstrap.stackedMap', [])
|
||||
return stack.splice(idx, 1)[0];
|
||||
},
|
||||
removeTop: function() {
|
||||
return stack.splice(stack.length - 1, 1)[0];
|
||||
return stack.pop();
|
||||
},
|
||||
length: function() {
|
||||
return stack.length;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
require('./tabindex');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.tabindex';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.tabindex']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user