Compare commits
705 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 | |||
| 7cbf957a0e | |||
| 800c153ebd | |||
| 6f3800f860 | |||
| fe30e5151e | |||
| a945dd247a | |||
| aac0d2b8fe | |||
| 7949494493 | |||
| dd62c73a1a | |||
| 313ba83793 | |||
| 94c34b781f | |||
| 0beb06f24a | |||
| a516178635 | |||
| c15dcbd1d1 | |||
| 4b06b0bcda | |||
| 55d9c3f48d | |||
| 5e43870c5d | |||
| 9d74d6ce50 | |||
| 4369800682 | |||
| 09b2150eb0 | |||
| a816251c9f | |||
| 299f7517b6 | |||
| d68086f978 | |||
| dd844ad8b3 | |||
| 679a8d54a7 | |||
| cc2d1b90c3 | |||
| c8922a2f04 | |||
| 7d79cdddd2 | |||
| bd55e14de5 | |||
| a9e810d0af | |||
| 70c1c9079a | |||
| 1b709e5a5c | |||
| 43daa97561 | |||
| a69f4d29a8 | |||
| deedaea250 | |||
| 785c373241 | |||
| 8364e76bb4 | |||
| 85751551e3 | |||
| d546671717 | |||
| 19e4171dfe | |||
| 2968f4cb9e | |||
| 876974973e | |||
| 97244c7e64 | |||
| 1cc142b225 | |||
| ab5941315f | |||
| 4bb178ac5b | |||
| bbc4912d55 | |||
| d880aea9b6 | |||
| b24524280c | |||
| 47e412ec0b | |||
| 74a1d04e27 | |||
| 3c0a7cd57c | |||
| fa17c483e0 | |||
| 6fd47892ce | |||
| 69aae3547f | |||
| 96c686611d | |||
| 834975899b | |||
| 64e3127479 | |||
| d1553a450f | |||
| 8b3e86f2c6 | |||
| 417850001d | |||
| 3814fe3ad1 | |||
| 979fe0bad1 | |||
| 57326a9b35 | |||
| f9903baf14 | |||
| b8353322be | |||
| b95d1e2b2a | |||
| 54c51c4ffc | |||
| c5397a893b | |||
| b808c0ddfe | |||
| cd2f050f2f | |||
| 32a247471f | |||
| a35a63d4f9 | |||
| d7b6aa1206 | |||
| 74e129dc28 | |||
| 5ec19b643b | |||
| 946b7e040b | |||
| d5a757d46d | |||
| 01e199815f | |||
| 2116f7e4a4 | |||
| 10eac7c22b | |||
| e4fc20167b | |||
| 068d18189b | |||
| 9c0acf67eb | |||
| ca6b177db1 | |||
| a5aebda854 | |||
| f81d440ba6 | |||
| 42fb486735 | |||
| e283cea0e4 | |||
| 853d302356 | |||
| 258b3413be | |||
| 81498a7c6a | |||
| 4b2ee0fa85 | |||
| f125537610 | |||
| 5c565df024 | |||
| 3a03f293b5 | |||
| e4c88bf54b | |||
| e645ab78a2 | |||
| 3e10184c1c | |||
| d88c0fb117 | |||
| 15c5c503a4 | |||
| dbd3947002 | |||
| c3b14315d9 | |||
| 48c9cd8f51 | |||
| 0b7e742f6e | |||
| dbe9e8171f | |||
| d2621e3f98 | |||
| b1ad080741 | |||
| e58c42c5b2 | |||
| 61cdef1ee6 | |||
| 56f5c82c72 | |||
| 2af95a2cd4 | |||
| 096e171137 | |||
| 97af6a99dd | |||
| d5a48eaff0 | |||
| 5fcca2e83b | |||
| 8f94fe84b0 | |||
| f303df419b | |||
| 9050949b79 | |||
| 5325559795 | |||
| 594a312645 | |||
| b477165816 | |||
| 826228de85 | |||
| 38ba6ec9ea | |||
| 77fa33022b | |||
| 2a1f1bc182 | |||
| c10280618a | |||
| ca04d5bc31 | |||
| ee889ca841 | |||
| 3c2b57e9f9 | |||
| 332eefb464 | |||
| 3b7be53bbe | |||
| 8a3c4b8a6d | |||
| 4cf6a8334c | |||
| e4eeb4b55b | |||
| 3f809affd3 | |||
| 32b88f1fc7 | |||
| c4171a2de2 | |||
| a5797b9302 | |||
| e105e85d7a | |||
| f19b657658 | |||
| 62213208e0 | |||
| 1dbad8a057 | |||
| 78ba137383 | |||
| 2cb3bc2e76 | |||
| c6504f8fa5 | |||
| ca0eaab5bb | |||
| 7e93ec95cb | |||
| bcaa221e4b | |||
| b76e98e16f | |||
| 39d8bc699c | |||
| 57219aa153 | |||
| 907c851fd0 | |||
| de249270a8 | |||
| b3e6d1750c | |||
| 717ea692b9 | |||
| a47580ed62 | |||
| f523361e9e | |||
| e8b6a83a71 | |||
| ea542213bb | |||
| a25caa7c00 | |||
| 00ac93e3ec | |||
| 9f4c3a5ac8 | |||
| 3468888ae2 | |||
| 6a2d91bf7c | |||
| 446364ad1b | |||
| e8201d1f34 | |||
| b658b03afe | |||
| 6843ab6ea1 | |||
| 3bacd454e8 | |||
| 1038c5422f | |||
| 5daa6de160 | |||
| f608b143c5 | |||
| 2ebe83e93b | |||
| c1fbf4d940 | |||
| 627a451f53 | |||
| bae7bc809a | |||
| 74be568c65 | |||
| 66d4300734 | |||
| 234992470e | |||
| 11d18a5252 | |||
| 2f6fb2abda | |||
| 3dac5e3450 | |||
| db7adf737e | |||
| 79fa363d91 | |||
| 0d1ca858be | |||
| 176f41978b | |||
| 4c338eb4fc | |||
| bd6de24ace | |||
| 1340812caa | |||
| 9e9c6cf153 | |||
| 4b0e381b01 | |||
| aa010b024e | |||
| 9d0269ea97 | |||
| 7250ff3ad8 | |||
| 63ca2f3755 | |||
| 36fca744d5 | |||
| 428beaff4c | |||
| 6ad873fab4 | |||
| 84bb118b35 | |||
| 71afeeb6e3 | |||
| a42266c956 | |||
| 2dfa1f5c53 | |||
| 16c7f9fbf1 | |||
| 00c4df10c2 | |||
| 162de3eaf2 | |||
| ac32cbb4cf | |||
| b32003bb8c | |||
| 859dccd711 | |||
| 1a3e2adf1a | |||
| b6031d6bf0 | |||
| a9e476f0f7 | |||
| e1e6e1bb62 | |||
| 3e876b8fdd | |||
| 3ef8992c54 | |||
| a5ca78ad2a | |||
| d320df88a3 | |||
| f40066af9e | |||
| e1723bf09d | |||
| 86ed171e37 | |||
| 4d37c9240b | |||
| cab0945ac9 | |||
| ff5e720690 | |||
| dbdffd4c4e | |||
| dc229ac01f | |||
| c357385207 | |||
| 6b5fcc385c | |||
| b8ebad2dfa | |||
| ed7e4600b0 | |||
| 2ccaa1977f | |||
| 1f3e55f6fc | |||
| e98492baa2 | |||
| db52961f5c | |||
| 72f13ef38b | |||
| f33ab7ceca | |||
| 14037f1633 | |||
| ca580268eb | |||
| 7a8cf0e458 | |||
| 36c687a32f | |||
| fccc9691f9 | |||
| 8b68fe052d | |||
| 1273156097 | |||
| 33b7a3760f | |||
| 2cc6cbaa0d | |||
| 74b213ef60 | |||
| 277ffa998c | |||
| 5805b51f4d | |||
| 4dba58ddbe | |||
| a5c2a5b061 | |||
| 3b7f78a93d | |||
| 1524080078 | |||
| 3845980bda | |||
| 6fa4cc5a4a | |||
| 09098f891f | |||
| 9019298cc3 | |||
| 2635f0d65b | |||
| fd5991311a | |||
| 00a7cc393a | |||
| 5ad08fff46 | |||
| 000d6c309e | |||
| ccf161afba | |||
| bf1768e752 | |||
| 2a1aaf2d85 | |||
| a49ce8e50a | |||
| e6474f0597 | |||
| e7f709f0a7 | |||
| 812d47e817 | |||
| 6dbd3f8f06 | |||
| b784422ee3 | |||
| d84cb6536a | |||
| e18bdd557c | |||
| 8b721f425b | |||
| 9a32cda11c | |||
| 174c3bda20 | |||
| 406528eee2 | |||
| c4a3ad41ad | |||
| be772fa5ae | |||
| 46882525f4 | |||
| 1e24ff7bc1 | |||
| 7be21c4983 | |||
| b31f87cef6 | |||
| fe68977033 | |||
| b8969d1937 | |||
| 2460e42322 | |||
| 3658502c74 | |||
| a5cac90ad0 | |||
| fd88dcb274 | |||
| 62e0761351 | |||
| 8930a7f373 | |||
| 8a7ebbf94d | |||
| 75b8e5aa0e | |||
| 5e5c8b4af2 | |||
| c0f10276af | |||
| 384fdfc83d | |||
| 66d290225a | |||
| f125cdb6ae | |||
| 8c4d248afa | |||
| 763cfd921e | |||
| a8b0d454f9 | |||
| bd47f6c4ff | |||
| 6094e0725e | |||
| c488d824bb | |||
| e9c4977dc4 | |||
| 9d139a5975 | |||
| d9e3dd5c44 | |||
| 19b7e3e699 | |||
| 12a37e0f02 | |||
| d8c8767c3d | |||
| 1a822a1170 | |||
| 1d49f054c0 | |||
| f3f5430b1c | |||
| f2f8c4e8d3 | |||
| 8bfeda00d4 | |||
| f3d4dc2cf9 | |||
| 58902485ef | |||
| 891c449ad4 | |||
| 80e927e735 | |||
| 9f0dba173f | |||
| 9513f1007f | |||
| d265113984 | |||
| 526e6de82a | |||
| 2a3314df70 | |||
| 2cd7f4fe6d | |||
| 48505a3045 | |||
| dd6ddc8039 | |||
| 56f9a207de | |||
| 423ba10875 | |||
| 77a5158c2b | |||
| e209db392e | |||
| 726ccc367d | |||
| 6d5b1d9038 | |||
| 31af431d5a | |||
| 57f72b219e | |||
| 4dab96e28c | |||
| 58205a82bb | |||
| 4de49f4f58 | |||
| 85fdfc2024 | |||
| b1c05f40cc | |||
| 53806c2779 | |||
| c34317d13d | |||
| 4199237da0 | |||
| c9363809d8 | |||
| a255d99b0b | |||
| c61257143f | |||
| 9131907bb4 | |||
| 9b80ee1b2f | |||
| 26d31037ce | |||
| 0917623b18 | |||
| 44820a01b3 | |||
| 873730360c | |||
| 3ca8b88044 | |||
| 1e19292b6b | |||
| e196be855e | |||
| a5bafe6439 | |||
| 1f43cdf3da | |||
| f68a7e0c27 | |||
| ca02088666 | |||
| e48342a3f8 | |||
| e28ccedb6c | |||
| ee7ab9f36a | |||
| 07c31d0731 | |||
| 742132a2c8 | |||
| 342c576ea4 | |||
| fe6938675e | |||
| 703432b259 | |||
| ea388b34d0 | |||
| 8c3c38ded7 | |||
| 2fb812be13 | |||
| 628039bb16 | |||
| 80df0152b3 | |||
| 5bf4052d69 | |||
| 434c6026a5 | |||
| 73efc782e9 | |||
| aff9010250 | |||
| 1c30baa335 | |||
| 465119141a | |||
| beabb4a04d | |||
| fc61e178b6 | |||
| bd8acac329 | |||
| 83d1435444 | |||
| 56642ea396 | |||
| 3f307e4c4b | |||
| b1cfc5762d | |||
| 3f5b420a8c | |||
| d732929d7e | |||
| 02d2ef3c63 | |||
| 3aa41f053f | |||
| 8637afcbac | |||
| 08ad07701e | |||
| 01c2d9a5c0 | |||
| 8b75bc4f5f | |||
| d59083b65e | |||
| df211bd625 | |||
| 0495ff0917 | |||
| b77618e06f | |||
| 739b667cc5 | |||
| 1c79888ca6 | |||
| 809ecdb476 | |||
| da455f6dd0 | |||
| d859f42cc0 | |||
| 20a8701c34 | |||
| f6f6db1681 | |||
| 65b47a2e0e | |||
| 3f6d388925 | |||
| a1355e7687 | |||
| ef82ad1f29 | |||
| 9d93af1c81 | |||
| 30099a0cd0 | |||
| 167cfad72a | |||
| a8624e0cff | |||
| c8df6f2700 | |||
| d60f2dfa09 | |||
| a8516369f4 | |||
| 8c0b3f592f | |||
| 09ad740434 | |||
| 468d68ac6b | |||
| 16d854ca4c | |||
| 8ffdaeb099 | |||
| c7fa84529c | |||
| 21b2297aec | |||
| 429ddc12b6 | |||
| 1cfaf64418 | |||
| 97c4333495 | |||
| 1d9294c8e6 | |||
| 623e564f07 | |||
| a1e0686162 | |||
| acb54991bb | |||
| bb7b9e3f5a | |||
| 9e6bf021da | |||
| dbc9cb7a77 | |||
| 38bb19a275 | |||
| 286860a1b7 | |||
| 738b6addf0 | |||
| d2097c1dd7 | |||
| 4eaa82f978 | |||
| 9d886da6f9 | |||
| e1222fbfaa | |||
| 972dee623b | |||
| 38c1b14684 | |||
| 8899fc8e85 | |||
| 79367b82de | |||
| 0669b06650 | |||
| 42fa28f850 | |||
| 75e493a940 | |||
| feb2b736d3 | |||
| 606d419e92 | |||
| d844623a1e | |||
| 1b75164c4f | |||
| a85d4996c2 | |||
| 2fc3f2147b | |||
| 0010aff05d | |||
| ca3a343e2e | |||
| 2d68f4131c | |||
| b159b21c85 | |||
| b5492632a8 | |||
| bc004dfa3a | |||
| 21e852b8ec | |||
| 187f64c006 | |||
| 80ede3797f | |||
| 0b04eefd12 |
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"rules": {
|
||||
"comma-dangle": 2,
|
||||
"no-cond-assign": 2,
|
||||
"no-control-regex": 2,
|
||||
"no-debugger": 2,
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-empty-character-class": 2,
|
||||
"no-empty": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-parens": 2,
|
||||
"no-extra-semi": 2,
|
||||
"no-func-assign": 2,
|
||||
"no-inner-declarations": 2,
|
||||
"no-invalid-regexp": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-sparse-arrays": 2,
|
||||
"no-unexpected-multiline": 2,
|
||||
"no-unreachable": 2,
|
||||
"valid-typeof": 2,
|
||||
"accessor-pairs": 2,
|
||||
"block-scoped-var": 2,
|
||||
"complexity": 2,
|
||||
"curly": 2,
|
||||
"dot-notation": [2, {"allowKeywords": false}],
|
||||
"eqeqeq": 2,
|
||||
"guard-for-in": 2,
|
||||
"semi": 2,
|
||||
"no-alert": 2,
|
||||
"no-caller": 2,
|
||||
"no-case-declarations": 2,
|
||||
"no-div-regex": 2,
|
||||
"no-else-return": 2,
|
||||
"no-empty-label": 2,
|
||||
"no-empty-pattern": 2,
|
||||
"no-eq-null": 2,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-floating-decimal": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-invalid-this": 2,
|
||||
"no-iterator": 2,
|
||||
"no-lone-blocks": 2,
|
||||
"no-loop-func": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-native-reassign": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-new": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-octal": 2,
|
||||
"no-proto": 2,
|
||||
"no-redeclare": 2,
|
||||
"no-script-url": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-throw-literal": 2,
|
||||
"no-unused-expressions": 2,
|
||||
"no-useless-call": 2,
|
||||
"no-useless-concat": 2,
|
||||
"no-void": 2,
|
||||
"no-with": 2,
|
||||
"radix": 2,
|
||||
"wrap-iife": 2,
|
||||
"yoda": 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
The issues forum is __NOT__ for support requests. It is for bugs and feature requests only.
|
||||
Please read https://github.com/angular-ui/bootstrap/blob/master/CONTRIBUTING.md and search
|
||||
existing issues (both open and closed) prior to opening any new issue and ensure you follow the instructions therein.
|
||||
|
||||
### Bug description:
|
||||
|
||||
### Link to minimally-working plunker that reproduces the issue:
|
||||
|
||||
### Steps to reproduce the issue:
|
||||
|
||||
### Version of Angular, UIBS, and Bootstrap
|
||||
|
||||
Angular:
|
||||
|
||||
UIBS:
|
||||
|
||||
Bootstrap:
|
||||
+2
-1
@@ -9,13 +9,14 @@ lib-cov
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
.idea
|
||||
|
||||
pids
|
||||
logs
|
||||
results
|
||||
dist
|
||||
# test coverage files
|
||||
coverage/
|
||||
.coverage/
|
||||
|
||||
node_modules
|
||||
npm-debug.log
|
||||
|
||||
+4
-5
@@ -14,18 +14,14 @@ pids
|
||||
logs
|
||||
results
|
||||
# test coverage files
|
||||
coverage/
|
||||
.coverage/
|
||||
|
||||
node_modules
|
||||
npm-debug.log
|
||||
|
||||
template/**/*.js
|
||||
|
||||
.git
|
||||
docs
|
||||
misc
|
||||
src
|
||||
template
|
||||
.editorconfig
|
||||
.gitattributes
|
||||
.gitignore
|
||||
@@ -37,3 +33,6 @@ karma.conf.js
|
||||
ROADMAP.md
|
||||
|
||||
dist/assets
|
||||
dist/index.html
|
||||
dist/versions-mapping.json
|
||||
dist/*-SNAPSHOT*
|
||||
|
||||
+10
-2
@@ -1,11 +1,19 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.12"
|
||||
- "5.9"
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
|
||||
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
|
||||
|
||||
+1170
-421
File diff suppressed because it is too large
Load Diff
+2
-4
@@ -2,7 +2,7 @@
|
||||
|
||||
Firstly, please go over our FAQ: https://github.com/angular-ui/bootstrap/wiki/FAQ
|
||||
|
||||
Please, do not open issues for the general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [StackOverflow](http://stackoverflow.com/questions/tagged/angular-ui-bootstrap) where maintainers are looking at questions questions tagged with `angular-ui-bootstrap`.
|
||||
Please, do not open issues for the general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [StackOverflow](http://stackoverflow.com/questions/tagged/angular-ui-bootstrap) where maintainers are looking at questions tagged with `angular-ui-bootstrap`.
|
||||
|
||||
StackOverflow is a much better place to ask questions since:
|
||||
* there are hundreds of people willing to help on StackOverflow
|
||||
@@ -30,8 +30,6 @@ Unfortunately we are not able to investigate / fix bugs without a minimal reprod
|
||||
|
||||
## You want to contribute some code?
|
||||
|
||||
**NOTE: We are migrating all our components to use a prefix, in the meantime you will see both the new and the old directives separated by a comment. If you want to contribute with code, make all your changes on top of that comment.**
|
||||
|
||||
We are always looking for the quality contributions and will be happy to accept your Pull Requests as long as those adhere to some basic rules:
|
||||
|
||||
* Please make sure that your contribution fits well in the project's context:
|
||||
@@ -44,4 +42,4 @@ We are always looking for the quality contributions and will be happy to accept
|
||||
* Please assure that you are submitting quality code, specifically make sure that:
|
||||
* your directive has accompanying tests and all the tests are passing; don't hesitate to contact us (angular-ui@googlegroups.com) if you need any help with unit testing
|
||||
* your PR doesn't break the build; check the Travis-CI build status after opening a PR and push corrective commits if anything goes wrong
|
||||
* your commits conform to the conventions established [here](https://github.com/ajoslin/conventional-changelog/blob/master/conventions/angular.md)
|
||||
* your commits conform to the conventions established [here](https://github.com/stevemao/conventional-changelog-angular/blob/master/convention.md)
|
||||
|
||||
+58
-66
@@ -1,25 +1,16 @@
|
||||
/* jshint node: true */
|
||||
var markdown = require('node-markdown').Markdown;
|
||||
var marked = require('marked');
|
||||
var fs = require('fs');
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-html2js');
|
||||
grunt.loadNpmTasks('grunt-karma');
|
||||
grunt.loadNpmTasks('grunt-conventional-changelog');
|
||||
grunt.loadNpmTasks('grunt-ddescribe-iit');
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
// Project configuration.
|
||||
grunt.util.linefeed = '\n';
|
||||
|
||||
grunt.initConfig({
|
||||
ngversion: '1.4.7',
|
||||
bsversion: '3.1.1',
|
||||
ngversion: '1.6.1',
|
||||
bsversion: '3.3.7',
|
||||
modules: [],//to be filled in by build task
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
dist: 'dist',
|
||||
@@ -32,12 +23,14 @@ module.exports = function(grunt) {
|
||||
cssInclude: '',
|
||||
cssFileBanner: '/* Include this file in your html if you are using the CSP mode. */\n\n',
|
||||
cssFileDest: '<%= dist %>/<%= filename %>-<%= pkg.version %>-csp.css',
|
||||
banner: ['/*',
|
||||
' * <%= pkg.name %>',
|
||||
' * <%= pkg.homepage %>\n',
|
||||
' * Version: <%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>',
|
||||
' * License: <%= pkg.license %>',
|
||||
' */\n'].join('\n')
|
||||
banner: [
|
||||
'/*',
|
||||
' * <%= pkg.name %>',
|
||||
' * <%= pkg.homepage %>\n',
|
||||
' * Version: <%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>',
|
||||
' * License: <%= pkg.license %>',
|
||||
' */'
|
||||
].join('\n')
|
||||
},
|
||||
delta: {
|
||||
docs: {
|
||||
@@ -49,8 +42,7 @@ module.exports = function(grunt) {
|
||||
tasks: ['html2js', 'karma:watch:run']
|
||||
},
|
||||
js: {
|
||||
files: ['src/**/*.js'],
|
||||
//we don't need to jshint here, it slows down everything else
|
||||
files: ['src/**/*.js', '!src/**/index.js'],
|
||||
tasks: ['karma:watch:run']
|
||||
}
|
||||
},
|
||||
@@ -112,7 +104,10 @@ module.exports = function(grunt) {
|
||||
dist: {
|
||||
options: {
|
||||
module: null, // no bundle module for all the html2js templates
|
||||
base: '.'
|
||||
base: '.',
|
||||
rename: function(moduleName) {
|
||||
return `uib/${moduleName}`;
|
||||
}
|
||||
},
|
||||
files: [{
|
||||
expand: true,
|
||||
@@ -121,11 +116,8 @@ module.exports = function(grunt) {
|
||||
}]
|
||||
}
|
||||
},
|
||||
jshint: {
|
||||
files: ['Gruntfile.js','src/**/*.js'],
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
}
|
||||
eslint: {
|
||||
files: ['Gruntfile.js','src/**/*.js']
|
||||
},
|
||||
karma: {
|
||||
options: {
|
||||
@@ -169,7 +161,7 @@ module.exports = function(grunt) {
|
||||
}
|
||||
},
|
||||
shell: {
|
||||
//We use %version% and evluate it at run-time, because <%= pkg.version %>
|
||||
//We use %version% and evaluate it at run-time, because <%= pkg.version %>
|
||||
//is only evaluated once
|
||||
'release-prepare': [
|
||||
'grunt before-test after-test',
|
||||
@@ -194,7 +186,7 @@ module.exports = function(grunt) {
|
||||
|
||||
//register before and after test tasks so we've don't have to change cli
|
||||
//options on the google's CI server
|
||||
grunt.registerTask('before-test', ['enforce', 'ddescribe-iit', 'jshint', 'html2js']);
|
||||
grunt.registerTask('before-test', ['enforce', 'ddescribe-iit', 'eslint', 'html2js']);
|
||||
grunt.registerTask('after-test', ['build', 'copy']);
|
||||
|
||||
//Rename our watch task to 'delta', then make actual 'watch'
|
||||
@@ -205,7 +197,7 @@ module.exports = function(grunt) {
|
||||
// Default task.
|
||||
grunt.registerTask('default', ['before-test', 'test', 'after-test']);
|
||||
|
||||
grunt.registerTask('enforce', 'Install commit message enforce script if it doesn\'t exist', function() {
|
||||
grunt.registerTask('enforce', `Install commit message enforce script if it doesn't exist`, function() {
|
||||
if (!grunt.file.exists('.git/hooks/commit-msg')) {
|
||||
grunt.file.copy('misc/validate-commit-msg.js', '.git/hooks/commit-msg');
|
||||
require('fs').chmodSync('.git/hooks/commit-msg', '0755');
|
||||
@@ -230,25 +222,28 @@ module.exports = function(grunt) {
|
||||
});
|
||||
}
|
||||
function enquote(str) {
|
||||
return '"' + str + '"';
|
||||
return `"${str}"`;
|
||||
}
|
||||
function enquoteUibDir(str) {
|
||||
return enquote(`uib/${str}`);
|
||||
}
|
||||
|
||||
var module = {
|
||||
name: name,
|
||||
moduleName: enquote('ui.bootstrap.' + name),
|
||||
moduleName: enquote(`ui.bootstrap.${name}`),
|
||||
displayName: ucwords(breakup(name, ' ')),
|
||||
srcFiles: grunt.file.expand('src/'+name+'/*.js'),
|
||||
cssFiles: grunt.file.expand('src/'+name+'/*.css'),
|
||||
tplFiles: grunt.file.expand('template/'+name+'/*.html'),
|
||||
tpljsFiles: grunt.file.expand('template/'+name+'/*.html.js'),
|
||||
tplModules: grunt.file.expand('template/'+name+'/*.html').map(enquote),
|
||||
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`),
|
||||
tplModules: grunt.file.expand(`template/${name}/*.html`).map(enquoteUibDir),
|
||||
dependencies: dependenciesForModule(name),
|
||||
docs: {
|
||||
md: grunt.file.expand('src/'+name+'/docs/*.md')
|
||||
.map(grunt.file.read).map(markdown).join('\n'),
|
||||
js: grunt.file.expand('src/'+name+'/docs/*.js')
|
||||
md: grunt.file.expand(`src/${name}/docs/*.md`)
|
||||
.map(grunt.file.read).map((str) => marked(str)).join('\n'),
|
||||
js: grunt.file.expand(`src/${name}/docs/*.js`)
|
||||
.map(grunt.file.read).join('\n'),
|
||||
html: grunt.file.expand('src/'+name+'/docs/*.html')
|
||||
html: grunt.file.expand(`src/${name}/docs/*.html`)
|
||||
.map(grunt.file.read).join('\n')
|
||||
}
|
||||
};
|
||||
@@ -257,7 +252,7 @@ module.exports = function(grunt) {
|
||||
css: [],
|
||||
js: []
|
||||
};
|
||||
module.cssFiles.forEach(processCSS.bind(null, styles, true));
|
||||
module.cssFiles.forEach(processCSS.bind(null, module.name, styles, true));
|
||||
if (styles.css.length) {
|
||||
module.css = styles.css.join('\n');
|
||||
module.cssJs = styles.js.join('\n');
|
||||
@@ -269,7 +264,7 @@ module.exports = function(grunt) {
|
||||
|
||||
function dependenciesForModule(name) {
|
||||
var deps = [];
|
||||
grunt.file.expand('src/' + name + '/*.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,
|
||||
@@ -307,19 +302,17 @@ module.exports = function(grunt) {
|
||||
} else {
|
||||
grunt.file.expand({
|
||||
filter: 'isDirectory', cwd: '.'
|
||||
}, 'src/*').forEach(function(dir) {
|
||||
}, 'src/*').forEach((dir) => {
|
||||
findModule(dir.split('/')[1]);
|
||||
});
|
||||
}
|
||||
|
||||
var modules = grunt.config('modules');
|
||||
grunt.config('srcModules', _.pluck(modules, 'moduleName'));
|
||||
grunt.config('tplModules', _.pluck(modules, 'tplModules').filter(function(tpls) { return tpls.length > 0;} ));
|
||||
grunt.config('tplModules', _.pluck(modules, 'tplModules').filter((tpls) => tpls.length > 0));
|
||||
grunt.config('demoModules', modules
|
||||
.filter(function(module) {
|
||||
return module.docs.md && module.docs.js && module.docs.html;
|
||||
})
|
||||
.sort(function(a, b) {
|
||||
.filter((module) => module.docs.md && module.docs.js && module.docs.html)
|
||||
.sort((a, b) => {
|
||||
if (a.name < b.name) { return -1; }
|
||||
if (a.name > b.name) { return 1; }
|
||||
return 0;
|
||||
@@ -338,9 +331,7 @@ module.exports = function(grunt) {
|
||||
}
|
||||
|
||||
var moduleFileMapping = _.clone(modules, true);
|
||||
moduleFileMapping.forEach(function (module) {
|
||||
delete module.docs;
|
||||
});
|
||||
moduleFileMapping.forEach((module) => delete module.docs);
|
||||
|
||||
grunt.config('moduleFileMapping', moduleFileMapping);
|
||||
|
||||
@@ -356,14 +347,14 @@ module.exports = function(grunt) {
|
||||
grunt.task.run(['concat', 'uglify', 'makeModuleMappingFile', 'makeRawFilesJs', 'makeVersionsMappingFile']);
|
||||
});
|
||||
|
||||
grunt.registerTask('test', 'Run tests on singleRun karma server', function () {
|
||||
grunt.registerTask('test', 'Run tests on singleRun karma server', function() {
|
||||
//this task can be executed in 3 different environments: local, Travis-CI and Jenkins-CI
|
||||
//we need to take settings for each one into account
|
||||
if (process.env.TRAVIS) {
|
||||
grunt.task.run('karma:travis');
|
||||
} else {
|
||||
var isToRunJenkinsTask = !!this.args.length;
|
||||
if(grunt.option('coverage')) {
|
||||
if (grunt.option('coverage')) {
|
||||
var karmaOptions = grunt.config.get('karma.options'),
|
||||
coverageOpts = grunt.config.get('karma.coverage');
|
||||
grunt.util._.extend(karmaOptions, coverageOpts);
|
||||
@@ -373,7 +364,7 @@ module.exports = function(grunt) {
|
||||
}
|
||||
});
|
||||
|
||||
grunt.registerTask('makeModuleMappingFile', function () {
|
||||
grunt.registerTask('makeModuleMappingFile', function() {
|
||||
var _ = grunt.util._;
|
||||
var moduleMappingJs = 'dist/assets/module-mapping.json';
|
||||
var moduleMappings = grunt.config('moduleFileMapping');
|
||||
@@ -383,7 +374,7 @@ module.exports = function(grunt) {
|
||||
grunt.log.writeln('File ' + moduleMappingJs.cyan + ' created.');
|
||||
});
|
||||
|
||||
grunt.registerTask('makeRawFilesJs', function () {
|
||||
grunt.registerTask('makeRawFilesJs', function() {
|
||||
var _ = grunt.util._;
|
||||
var jsFilename = 'dist/assets/raw-files.json';
|
||||
var genRawFilesJs = require('./misc/raw-files-generator');
|
||||
@@ -392,7 +383,7 @@ module.exports = function(grunt) {
|
||||
grunt.config('meta.banner'), grunt.config('meta.cssFileBanner'));
|
||||
});
|
||||
|
||||
grunt.registerTask('makeVersionsMappingFile', function () {
|
||||
grunt.registerTask('makeVersionsMappingFile', function() {
|
||||
var done = this.async();
|
||||
|
||||
var exec = require('child_process').exec;
|
||||
@@ -403,17 +394,19 @@ module.exports = function(grunt) {
|
||||
// Let's remove the oldest 14 versions.
|
||||
var versions = stdout.split('\n').slice(0, -14);
|
||||
var jsContent = versions.map(function(version) {
|
||||
version = version.replace(/^v/, '');
|
||||
return {
|
||||
version: version,
|
||||
url: '/bootstrap/versioned-docs/' + version
|
||||
url: `/bootstrap/versioned-docs/${version}`
|
||||
};
|
||||
});
|
||||
jsContent[0] = {
|
||||
jsContent = _.sortBy(jsContent, 'version').reverse();
|
||||
jsContent.unshift({
|
||||
version: 'Current',
|
||||
url: '/bootstrap'
|
||||
};
|
||||
});
|
||||
grunt.file.write(versionsMappingFile, JSON.stringify(jsContent));
|
||||
grunt.log.writeln('File ' + versionsMappingFile.cyan + ' created.');
|
||||
grunt.log.writeln(`File ${versionsMappingFile.cyan} created.`);
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -423,13 +416,12 @@ module.exports = function(grunt) {
|
||||
* Logic from AngularJS
|
||||
* https://github.com/angular/angular.js/blob/36831eccd1da37c089f2141a2c073a6db69f3e1d/lib/grunt/utils.js#L121-L145
|
||||
*/
|
||||
function processCSS(state, minify, file) {
|
||||
/* jshint quotmark: false */
|
||||
function processCSS(moduleName, state, minify, file) {
|
||||
var css = fs.readFileSync(file).toString(),
|
||||
js;
|
||||
state.css.push(css);
|
||||
|
||||
if(minify){
|
||||
if (minify) {
|
||||
css = css
|
||||
.replace(/\r?\n/g, '')
|
||||
.replace(/\/\*.*?\*\//g, '')
|
||||
@@ -444,7 +436,7 @@ module.exports = function(grunt) {
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/'/g, "\\'")
|
||||
.replace(/\r?\n/g, '\\n');
|
||||
js = "!angular.$$csp() && angular.element(document).find('head').prepend('<style type=\"text/css\">" + css + "</style>');";
|
||||
js = `angular.module('ui.bootstrap.${moduleName}').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uib${_.capitalize(moduleName)}Css && angular.element(document).find('head').prepend('<style type="text/css">${css}</style>'); angular.$$uib${_.capitalize(moduleName)}Css = true; });`;
|
||||
state.js.push(js);
|
||||
|
||||
return state;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2012-2015 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,6 +14,7 @@
|
||||
- [NuGet](#install-with-nuget)
|
||||
- [Custom](#custom-build)
|
||||
- [Manual](#manual-download)
|
||||
- [Webpack / JSPM](#webpack--jspm)
|
||||
- [Support](#support)
|
||||
- [FAQ](#faq)
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
@@ -26,18 +28,27 @@
|
||||
|
||||
# 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.
|
||||
Note: Since version 0.13.0, UI Bootstrap depends on [ngAnimate](https://docs.angularjs.org/api/ngAnimate) for transitions and animations, such as the accordion, carousel, etc. Include `ngAnimate` in the module dependencies for your app in order to enable animation.
|
||||
*Notes:*
|
||||
* Since version 0.13.0, UI Bootstrap depends on [ngAnimate](https://docs.angularjs.org/api/ngAnimate) for transitions and animations, such as the accordion, carousel, etc. Include `ngAnimate` in the module dependencies for your app in order to enable animation.
|
||||
* UI Bootstrap depends on [ngTouch](https://docs.angularjs.org/api/ngTouch) for swipe actions. Include `ngTouch` in the module dependencies for your app in order to enable swiping.
|
||||
|
||||
Additionally, it is strongly recommended that for UI Bootstrap 0.13.3 and higher you use Angular 1.3.18 or higher due to animation fixes.
|
||||
## Angular Requirements
|
||||
* UI Bootstrap 1.0 and higher _requires_ Angular 1.4.x or higher and it has been tested with Angular 1.4.8.
|
||||
* UI Bootstrap 0.14.3 is the _last_ version that supports Angular 1.3.x.
|
||||
* UI Bootstrap 0.12.0 is the _last_ version that supports Angular 1.2.x.
|
||||
|
||||
## Bootstrap Requirements
|
||||
* UI Bootstrap requires Bootstrap CSS version 3.x or higher and it has been tested with Bootstrap CSS 3.3.6.
|
||||
* UI Bootstrap 0.8 is the _last_ version that supports Bootstrap CSS 2.3.x.
|
||||
|
||||
#### Install with NPM
|
||||
|
||||
@@ -63,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
|
||||
|
||||
@@ -79,14 +90,55 @@ When you are done downloading all the dependencies and project files the only re
|
||||
angular.module('myModule', ['ui.bootstrap']);
|
||||
```
|
||||
|
||||
If you're a Browserify or Webpack user, you can do:
|
||||
# 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:
|
||||
|
||||
```js
|
||||
var uibs = require('angular-ui-bootstrap');
|
||||
import accordion from 'angular-ui-bootstrap/src/accordion';
|
||||
|
||||
angular.module('myModule', [uibs]);
|
||||
angular.module('myModule', [accordion]);
|
||||
```
|
||||
|
||||
You can import all the pieces you need in the same way:
|
||||
|
||||
```js
|
||||
import accordion from 'angular-ui-bootstrap/src/accordion';
|
||||
import datepicker from 'angular-ui-bootstrap/src/datepicker';
|
||||
|
||||
angular.module('myModule', [accordion, datepicker]);
|
||||
```
|
||||
|
||||
This will load all the dependencies (if any) and also the templates (if any).
|
||||
|
||||
Be sure to have a loader able to process `css` files like `css-loader`.
|
||||
|
||||
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
|
||||
@@ -95,7 +147,7 @@ https://github.com/angular-ui/bootstrap/wiki/FAQ
|
||||
|
||||
# Code of Conduct
|
||||
|
||||
Take a moment to read or [Code of Conduct](CODE_OF_CONDUCT.md)
|
||||
Take a moment to read our [Code of Conduct](CODE_OF_CONDUCT.md)
|
||||
|
||||
## PREFIX MIGRATION GUIDE
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
require('./dist/ui-bootstrap-tpls');
|
||||
|
||||
module.exports = 'ui.bootstrap';
|
||||
+9
-1
@@ -27,6 +27,8 @@ module.exports = function(config) {
|
||||
|
||||
// list of files to exclude
|
||||
exclude: [
|
||||
'src/**/index.js',
|
||||
'src/**/index-nocss.js',
|
||||
'src/**/docs/*'
|
||||
],
|
||||
|
||||
@@ -34,13 +36,19 @@ module.exports = function(config) {
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
preprocessors: {
|
||||
'src/*/{*.js,!(test)/**/*.js}': ['coverage']
|
||||
},
|
||||
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ['progress'],
|
||||
reporters: ['progress', 'coverage'],
|
||||
|
||||
coverageReporter: {
|
||||
dir: '.coverage/',
|
||||
type: 'html'
|
||||
},
|
||||
|
||||
reportSlowerThan: 100,
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
## Features
|
||||
<% _(changelog.feat).keys().sort().forEach(function(scope) { %>
|
||||
- **<%= scope%>:** <% changelog.feat[scope].forEach(function(change) { %>
|
||||
- <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>) <% }); %><% }); %> <% } %>
|
||||
- <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>)<% }); %><% }); %> <% } %>
|
||||
<% if (_(changelog.fix).size() > 0) { %>
|
||||
## Bug Fixes
|
||||
<% _(changelog.fix).keys().sort().forEach(function(scope) { %>
|
||||
- **<%= scope%>:** <% changelog.fix[scope].forEach(function(change) { %>
|
||||
- <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>) <% }); %><% }); %> <% } %>
|
||||
- <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>)<% }); %><% }); %> <% } %>
|
||||
<% if (_(changelog.breaking).size() > 0) { %>
|
||||
## Breaking Changes
|
||||
<% _(changelog.breaking).keys().sort().forEach(function(scope) { %>
|
||||
|
||||
@@ -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
|
||||
@@ -48,7 +50,7 @@ angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'plunker', 'ngTouch', 'ngAn
|
||||
|
||||
function MainCtrl($scope, $http, $document, $uibModal, orderByFilter) {
|
||||
// Grab old version docs
|
||||
$http.get('/versions-mapping.json')
|
||||
$http.get('/bootstrap/versions-mapping.json')
|
||||
.then(function(result) {
|
||||
$scope.oldDocs = result.data;
|
||||
});
|
||||
|
||||
@@ -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/');
|
||||
|
||||
@@ -311,9 +311,9 @@ window['Rainbow'] = (function() {
|
||||
_processPattern(regex, pattern, code, callback);
|
||||
};
|
||||
|
||||
// every 100 items we process let's call set timeout
|
||||
// every 50 items we process let's call set timeout
|
||||
// to let the ui breathe a little
|
||||
return match_counter % 100 > 0 ? nextCall() : setTimeout(nextCall, 0);
|
||||
return match_counter % 50 > 0 ? nextCall() : setTimeout(nextCall, 0);
|
||||
};
|
||||
|
||||
// if this is not a child match and it falls inside of another
|
||||
|
||||
+36
-16
@@ -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
|
||||
@@ -154,8 +161,11 @@
|
||||
JavaScript is required. The <strong>only required dependencies</strong> are:
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="http://angularjs.org" target="_blank">AngularJS</a> (requires AngularJS 1.3.x, tested with <%= ngversion %>).
|
||||
0.12.0 is the last version of this library that supports AngularJS 1.2.x.</li>
|
||||
<li><a href="http://angularjs.org" target="_blank">AngularJS</a> (requires AngularJS 1.4.x or higher, tested with <%= ngversion %>).
|
||||
0.14.3 is the last version of this library that supports AngularJS 1.3.x and
|
||||
0.12.0 is the last version that supports AngularJS 1.2.x.</li>
|
||||
<li><a href="http://angularjs.org" target="_blank">Angular-animate</a> (the version should match with your angular's, tested with <%= ngversion %>) if you plan in using animations, you need to load angular-animate as well.</li>
|
||||
<li><a href="http://angularjs.org" target="_blank">Angular-touch</a> (the version should match with your angular's, tested with <%= ngversion %>) if you plan in using swipe actions, you need to load angular-touch as well.</li>
|
||||
<li><a href="http://getbootstrap.com" target="_blank">Bootstrap CSS</a> (tested with version <%= bsversion %>).
|
||||
This version of the library (<%= pkg.version%>) works only with Bootstrap CSS in version 3.x.
|
||||
0.8.0 is the last version of this library that supports Bootstrap CSS in version 2.3.x.
|
||||
@@ -170,14 +180,19 @@
|
||||
<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>
|
||||
<pre><code>angular.module('myModule', ['ui.bootstrap']);</code></pre>
|
||||
</p>
|
||||
<p>If you are using UI Bootstrap in the CSP mode, e.g. in an extension, make sure you link to the <code>ui-bootstrap-csp.css</code> in your HTML manually.</p>
|
||||
<p>You can fork one of the plunkers from this page to see a working example of what is described here.</p>
|
||||
<h3>Migration to prefixes</h3>
|
||||
<p>Since version 0.14.0 we started to prefix all our components. If you are upgrading from ui-bootstrap 0.13.4 or earlier,
|
||||
check our <a href="https://github.com/angular-ui/bootstrap/wiki/Migration-guide-for-prefixes">migration guide</a>.</p>
|
||||
<h3>CSS</h3>
|
||||
<p>Original Bootstrap's CSS depends on empty <code>href</code> attributes to style cursors for several components (pagination, tabs etc.).
|
||||
But in AngularJS adding empty <code>href</code> attributes to link tags will cause unwanted route changes. This is why we need to remove empty <code>href</code> attributes from directive templates and as a result styling is not applied correctly. The remedy is simple, just add the following styling to your application: <pre><code>.nav, .pagination, .carousel, .panel-title a { cursor: pointer; }</code></pre>
|
||||
@@ -189,15 +204,21 @@
|
||||
Each of the components provided in <code>ui-bootstrap</code> have documentation and interactive Plunker examples.
|
||||
</p>
|
||||
<p>
|
||||
For the directives, we list the different attributes with their default values, and whether they are readonly. In addition to this, some settings
|
||||
have an eye icon next to them like this <i class="glyphicon glyphicon-eye-open"></i>. The eye means that the setting has an Angular
|
||||
<a href="https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch" title="Angular $watch" target="_blank">$watch</a> listener applied to it.
|
||||
For the directives, we list the different attributes with their default values. In addition to this, some settings have a badge on it:
|
||||
|
||||
<ul>
|
||||
<li><i class="glyphicon glyphicon-eye-open"></i> - This setting has an angular $watch listener applied to it.</li>
|
||||
<li><small class="badge">B</small> - This setting is a boolean. It doesn't need a parameter.</li>
|
||||
<li><small class="badge">C</small> - This setting can be configured globally in a constant service*.</li>
|
||||
<li><small class="badge">$</small> - This setting expects an angular expression instead of a literal string. If the expression support a boolean / integer, you can pass it directly.</li>
|
||||
<li><small class="badge">readonly</small> - This setting is readonly.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
For the services (you will recognize them with the <code>$</code> prefix), we list all the possible parameters you can pass to them and their default values if any.
|
||||
</p>
|
||||
<p>
|
||||
Also, some components have default values that you can override globally within a <code>.config</code> function for example.
|
||||
* Some directives have a config service that follows the next pattern: <code>uibDirectiveConfig</code>. The service's settings use camel case. The services can be configured in a <code>.config</code> function for example.
|
||||
</p>
|
||||
</section>
|
||||
<% demoModules.forEach(function(module) { %>
|
||||
@@ -221,13 +242,13 @@
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn btn-info plunk-btn" ng-click="edit('<%= ngversion%>', '<%= bsversion %>', '<%= pkg.version%>', '<%= module.name %>')"><i class="glyphicon glyphicon-edit"></i> Edit in plunker</button>
|
||||
</div>
|
||||
<uib-tabset>
|
||||
<uib-tab heading="Markup">
|
||||
<uib-tabset active="activeTab">
|
||||
<uib-tab index="0" heading="Markup">
|
||||
<div plunker-content="markup">
|
||||
<pre ng-non-bindable><code data-language="html"><%- module.docs.html %></code></pre>
|
||||
</div>
|
||||
</uib-tab>
|
||||
<uib-tab heading="JavaScript">
|
||||
<uib-tab index="1" heading="JavaScript">
|
||||
<div plunker-content="javascript">
|
||||
<pre ng-non-bindable><code data-language="javascript"><%- module.docs.js %></code></pre>
|
||||
</div>
|
||||
@@ -244,7 +265,7 @@
|
||||
</div><!-- /.main -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<p>Designed and built by <a href="https://github.com/angular-ui?tab=members" target="_blank">Angular-UI team</a> and <a href="https://github.com/angular-ui/bootstrap/graphs/contributors" target="_blank">contributors</a>.</p>
|
||||
<p>Designed and built by all ui-bootstrap <a href="https://github.com/angular-ui/bootstrap/graphs/contributors" target="_blank">contributors</a>.</p>
|
||||
<p>Code licensed under <a href="https://github.com/angular-ui/bootstrap/blob/master/LICENSE"><%= pkg.license %> License</a>.</p>
|
||||
<p><a href="https://github.com/angular-ui/bootstrap/issues?state=open">Issues</a></p>
|
||||
</div>
|
||||
@@ -278,11 +299,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label"><strong>Bower</strong></label>
|
||||
<label class="col-sm-3 control-label"><strong>npm</strong></label>
|
||||
<div class="col-sm-9">
|
||||
<small>If you are using Bower just run:</small>
|
||||
<pre style="margin-bottom:0;">bower install angular-bootstrap</pre>
|
||||
<small class="help-block"><a href="http://bower.io/" target="_blank">Bower</a> is a package manager for the web.</small>
|
||||
<pre style="margin-bottom:0;">npm install angular-ui-bootstrap</pre>
|
||||
<small class="help-block"><a href="https://www.npmjs.com/" target="_blank">npm</a> is a package manager for the web.</small>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+36
-14
@@ -1,10 +1,28 @@
|
||||
{
|
||||
"author": "https://github.com/angular-ui/bootstrap/graphs/contributors",
|
||||
"name": "angular-ui-bootstrap",
|
||||
"version": "0.14.3",
|
||||
"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": {},
|
||||
"scripts":{
|
||||
"directories": {
|
||||
"lib": "src/"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"dist/",
|
||||
"src/",
|
||||
"template/"
|
||||
],
|
||||
"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": {
|
||||
@@ -12,28 +30,32 @@
|
||||
"url": "https://github.com/angular-ui/bootstrap.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular": "^1.4.4",
|
||||
"angular-mocks": "^1.4.4",
|
||||
"angular-sanitize": "^1.4.4",
|
||||
"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-jshint": "^0.11.1",
|
||||
"grunt-contrib-uglify": "^0.9.1",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"grunt-conventional-changelog": "^4.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.3",
|
||||
"karma": "0.13.22",
|
||||
"karma-chrome-launcher": "^0.2.0",
|
||||
"karma-coverage": "^0.5.0",
|
||||
"karma-firefox-launcher": "^0.1.4",
|
||||
"karma-jasmine": "^0.3.5",
|
||||
"node-markdown": "0.1.1",
|
||||
"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"
|
||||
}
|
||||
|
||||
+25
-129
@@ -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
|
||||
@@ -38,7 +38,6 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
||||
this.groups.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
}])
|
||||
|
||||
// The accordion directive simply sets up the directive controller
|
||||
@@ -49,7 +48,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
||||
controllerAs: 'accordion',
|
||||
transclude: true,
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'template/accordion/accordion.html';
|
||||
return attrs.templateUrl || 'uib/template/accordion/accordion.html';
|
||||
}
|
||||
};
|
||||
})
|
||||
@@ -59,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 || 'template/accordion/accordion-group.html';
|
||||
return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
|
||||
},
|
||||
scope: {
|
||||
heading: '@', // Interpolate the heading attribute onto this scope
|
||||
panelClass: '@?', // Ditto with panelClass
|
||||
isOpen: '=?',
|
||||
isDisabled: '=?'
|
||||
},
|
||||
@@ -74,10 +74,11 @@ 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';
|
||||
scope.panelClass = attrs.panelClass;
|
||||
scope.panelClass = attrs.panelClass || 'panel-default';
|
||||
scope.$watch('isOpen', function(value) {
|
||||
element.toggleClass(scope.openClass, !!value);
|
||||
if (value) {
|
||||
@@ -92,6 +93,10 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
|
||||
scope.headingId = id + '-tab';
|
||||
scope.panelId = id + '-panel';
|
||||
}
|
||||
};
|
||||
})
|
||||
@@ -116,134 +121,25 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
||||
// You must provide the property on the accordion-group controller that will hold the transcluded element
|
||||
.directive('uibAccordionTransclude', function() {
|
||||
return {
|
||||
require: ['?^uibAccordionGroup', '?^accordionGroup'],
|
||||
require: '^uibAccordionGroup',
|
||||
link: function(scope, element, attrs, controller) {
|
||||
controller = controller[0] ? controller[0] : controller[1]; // Delete after we remove deprecation
|
||||
scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
|
||||
if (heading) {
|
||||
element.find('span').html('');
|
||||
element.find('span').append(heading);
|
||||
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]';
|
||||
}
|
||||
});
|
||||
|
||||
/* Deprecated accordion below */
|
||||
|
||||
angular.module('ui.bootstrap.accordion')
|
||||
|
||||
.value('$accordionSuppressWarning', false)
|
||||
|
||||
.controller('AccordionController', ['$scope', '$attrs', '$controller', '$log', '$accordionSuppressWarning', function($scope, $attrs, $controller, $log, $accordionSuppressWarning) {
|
||||
if (!$accordionSuppressWarning) {
|
||||
$log.warn('AccordionController is now deprecated. Use UibAccordionController instead.');
|
||||
}
|
||||
|
||||
angular.extend(this, $controller('UibAccordionController', {
|
||||
$scope: $scope,
|
||||
$attrs: $attrs
|
||||
}));
|
||||
}])
|
||||
|
||||
.directive('accordion', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
controller: 'AccordionController',
|
||||
controllerAs: 'accordion',
|
||||
transclude: true,
|
||||
replace: false,
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'template/accordion/accordion.html';
|
||||
},
|
||||
link: function() {
|
||||
if (!$accordionSuppressWarning) {
|
||||
$log.warn('accordion is now deprecated. Use uib-accordion instead.');
|
||||
}
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('accordionGroup', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) {
|
||||
return {
|
||||
require: '^accordion', // We need this directive to be inside an accordion
|
||||
restrict: 'EA',
|
||||
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
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'template/accordion/accordion-group.html';
|
||||
},
|
||||
scope: {
|
||||
heading: '@', // Interpolate the heading attribute onto this scope
|
||||
isOpen: '=?',
|
||||
isDisabled: '=?'
|
||||
},
|
||||
controller: function() {
|
||||
this.setHeading = function(element) {
|
||||
this.heading = element;
|
||||
};
|
||||
},
|
||||
link: function(scope, element, attrs, accordionCtrl) {
|
||||
if (!$accordionSuppressWarning) {
|
||||
$log.warn('accordion-group is now deprecated. Use uib-accordion-group instead.');
|
||||
}
|
||||
|
||||
accordionCtrl.addGroup(scope);
|
||||
|
||||
scope.openClass = attrs.openClass || 'panel-open';
|
||||
scope.panelClass = attrs.panelClass;
|
||||
scope.$watch('isOpen', function(value) {
|
||||
element.toggleClass(scope.openClass, !!value);
|
||||
if (value) {
|
||||
accordionCtrl.closeOthers(scope);
|
||||
}
|
||||
});
|
||||
|
||||
scope.toggleOpen = function($event) {
|
||||
if (!scope.isDisabled) {
|
||||
if (!$event || $event.which === 32) {
|
||||
scope.isOpen = !scope.isOpen;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('accordionHeading', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
transclude: true, // Grab the contents to be used as the heading
|
||||
template: '', // In effect remove this element!
|
||||
replace: true,
|
||||
require: '^accordionGroup',
|
||||
link: function(scope, element, attr, accordionGroupCtrl, transclude) {
|
||||
if (!$accordionSuppressWarning) {
|
||||
$log.warn('accordion-heading is now deprecated. Use uib-accordion-heading instead.');
|
||||
}
|
||||
// Pass the heading to the accordion-group controller
|
||||
// so that it can be transcluded into the right place in the template
|
||||
// [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
|
||||
accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('accordionTransclude', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) {
|
||||
return {
|
||||
require: '^accordionGroup',
|
||||
link: function(scope, element, attr, controller) {
|
||||
if (!$accordionSuppressWarning) {
|
||||
$log.warn('accordion-transclude is now deprecated. Use uib-accordion-transclude instead.');
|
||||
}
|
||||
|
||||
scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
|
||||
if (heading) {
|
||||
element.find('span').html('');
|
||||
element.find('span').append(heading);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -5,43 +5,45 @@ The body of each accordion group is transcluded into the body of the collapsible
|
||||
### uib-accordion settings
|
||||
|
||||
* `close-others`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
_(Default: `true`)_ -
|
||||
Control whether expanding an item will cause the other items to close.
|
||||
|
||||
|
||||
* `template-url`
|
||||
_(Default: `template/accordion/accordion.html`)_ -
|
||||
Add the ability to override the template used on the component.
|
||||
|
||||
### uib-accordion-group settings
|
||||
|
||||
* `heading`
|
||||
_(Default: `none`)_ -
|
||||
The clickable text on the group's header. You need one to be able to click on the header for toggling.
|
||||
|
||||
* `is-disabled`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `false`)_ -
|
||||
Whether the accordion group is disabled or not.
|
||||
|
||||
* `is-open`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `false`)_ -
|
||||
Whether accordion group is open or closed.
|
||||
|
||||
* `heading`
|
||||
_(Default: `none`)_ -
|
||||
The clickable text on the group's header. You need one to be able to click on the header for toggling.
|
||||
|
||||
* `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: `template/accordion/accordion-group.html`)_ -
|
||||
_(Default: `uib/template/accordion/accordion-group.html`)_ -
|
||||
Add the ability to override the template used on the component.
|
||||
|
||||
### Default settings `uibAccordionConfig`
|
||||
|
||||
* `closeOthers`
|
||||
_(Default: `true`)_ -
|
||||
Control whether expanding an item will cause the other items to close.
|
||||
|
||||
### Accordion heading
|
||||
|
||||
Instead of the `heading` attribute on the `uib-accordion-group`, you can use an `uib-accordion-heading` element inside a group that will be used as the group's header.
|
||||
|
||||
If you're using a custom template for the `uib-accordion-group`, you'll need to have an element for the heading to be transcluded into using `uib-accordion-header` (e.g. `<div uib-accordion-header></div>`).
|
||||
|
||||
### Known issues
|
||||
|
||||
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>`.
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
require('../collapse');
|
||||
require('../tabindex');
|
||||
require('../../template/accordion/accordion-group.html.js');
|
||||
require('../../template/accordion/accordion.html.js');
|
||||
require('./accordion');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.accordion';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.accordion', 'uib/template/accordion/accordion.html', 'uib/template/accordion/accordion-group.html']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
@@ -3,8 +3,8 @@ describe('uib-accordion', function() {
|
||||
|
||||
beforeEach(module('ui.bootstrap.accordion'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
beforeEach(module('template/accordion/accordion.html'));
|
||||
beforeEach(module('template/accordion/accordion-group.html'));
|
||||
beforeEach(module('uib/template/accordion/accordion.html'));
|
||||
beforeEach(module('uib/template/accordion/accordion-group.html'));
|
||||
|
||||
beforeEach(inject(function(_$animate_, $rootScope) {
|
||||
$animate = _$animate_;
|
||||
@@ -36,6 +36,7 @@ describe('uib-accordion', function() {
|
||||
ctrl.addGroup(group2 = { isOpen: true, $on : angular.noop });
|
||||
ctrl.addGroup(group3 = { isOpen: true, $on : angular.noop });
|
||||
});
|
||||
|
||||
it('should close other panels if close-others attribute is not defined', function() {
|
||||
delete $attrs.closeOthers;
|
||||
ctrl.closeOthers(group2);
|
||||
@@ -66,6 +67,7 @@ describe('uib-accordion', function() {
|
||||
originalCloseOthers = uibAccordionConfig.closeOthers;
|
||||
uibAccordionConfig.closeOthers = false;
|
||||
}));
|
||||
|
||||
afterEach(inject(function(uibAccordionConfig) {
|
||||
// return it to the original value
|
||||
uibAccordionConfig.closeOthers = originalCloseOthers;
|
||||
@@ -121,8 +123,14 @@ describe('uib-accordion', function() {
|
||||
$templateCache = _$templateCache_;
|
||||
}));
|
||||
|
||||
it('should be a tablist', function() {
|
||||
element = $compile('<uib-accordion></uib-accordion>')(scope);
|
||||
scope.$digest();
|
||||
expect(element.html()).toContain('role="tablist"');
|
||||
});
|
||||
|
||||
it('should expose the controller on the view', function() {
|
||||
$templateCache.put('template/accordion/accordion.html', '<div>{{accordion.text}}</div>');
|
||||
$templateCache.put('uib/template/accordion/accordion.html', '<div>{{accordion.text}}</div>');
|
||||
|
||||
element = $compile('<uib-accordion></uib-accordion>')(scope);
|
||||
scope.$digest();
|
||||
@@ -139,18 +147,20 @@ describe('uib-accordion', function() {
|
||||
it('should allow custom templates', function() {
|
||||
$templateCache.put('foo/bar.html', '<div>baz</div>');
|
||||
|
||||
element = $compile('<accordion template-url="foo/bar.html"></accordion>')(scope);
|
||||
element = $compile('<uib-accordion template-url="foo/bar.html"></uib-accordion>')(scope);
|
||||
scope.$digest();
|
||||
expect(element.html()).toBe('<div>baz</div>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('uib-accordion-group', function() {
|
||||
|
||||
var scope, $compile;
|
||||
var element, groups;
|
||||
var findGroupHeading = function(index) {
|
||||
return groups.eq(index).find('.panel-heading').eq(0);
|
||||
};
|
||||
var findGroupLink = function(index) {
|
||||
return groups.eq(index).find('a').eq(0);
|
||||
return groups.eq(index).find('.accordion-toggle').eq(0);
|
||||
};
|
||||
var findGroupBody = function(index) {
|
||||
return groups.eq(index).find('.panel-collapse').eq(0);
|
||||
@@ -166,26 +176,28 @@ 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() {
|
||||
beforeEach(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>' +
|
||||
'</uib-accordion>';
|
||||
'<uib-accordion>' +
|
||||
'<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);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
element.remove();
|
||||
});
|
||||
@@ -202,20 +214,26 @@ describe('uib-accordion', function() {
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(findGroupBody(0).scope().isOpen).toBe(true);
|
||||
expect(findGroupHeading(0).html()).toContain('aria-expanded="true"');
|
||||
|
||||
findGroupLink(1).click();
|
||||
scope.$digest();
|
||||
expect(findGroupBody(0).scope().isOpen).toBe(false);
|
||||
expect(findGroupHeading(0).html()).toContain('aria-expanded="false"');
|
||||
expect(findGroupBody(1).scope().isOpen).toBe(true);
|
||||
expect(findGroupHeading(1).html()).toContain('aria-expanded="true"');
|
||||
});
|
||||
|
||||
it('should toggle element on click', function() {
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(findGroupBody(0).scope().isOpen).toBe(true);
|
||||
expect(groups.eq(0).html()).toContain('aria-hidden="false"');
|
||||
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(findGroupBody(0).scope().isOpen).toBe(false);
|
||||
expect(groups.eq(0).html()).toContain('aria-hidden="true"');
|
||||
});
|
||||
|
||||
it('should add, by default, "panel-open" when opened', function() {
|
||||
@@ -254,20 +272,31 @@ describe('uib-accordion', function() {
|
||||
|
||||
expect(group).not.toHaveClass('panel-open');
|
||||
});
|
||||
|
||||
it('should generate an Id for the heading', function() {
|
||||
var groupScope = findGroupBody(0).scope();
|
||||
expect(groupScope.headingId).toEqual('accordiongroup-' + groupScope.$id + '-1000-tab');
|
||||
});
|
||||
|
||||
it('should generate an Id for the panel', function() {
|
||||
var groupScope = findGroupBody(0).scope();
|
||||
expect(groupScope.panelId).toEqual('accordiongroup-' + groupScope.$id + '-1000-panel');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with open-class attribute', 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>' +
|
||||
'</uib-accordion>';
|
||||
'<uib-accordion>' +
|
||||
'<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);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
element.remove();
|
||||
});
|
||||
@@ -289,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 = [
|
||||
@@ -334,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 };
|
||||
@@ -363,10 +392,10 @@ describe('uib-accordion', function() {
|
||||
describe('is-open attribute with dynamic content', 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>' +
|
||||
'</uib-accordion>';
|
||||
'<uib-accordion>' +
|
||||
'<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'];
|
||||
scope.open1 = true;
|
||||
@@ -392,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 = [
|
||||
@@ -423,13 +452,36 @@ describe('uib-accordion', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('is-open attribute with custom class', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<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 = [
|
||||
{name: 'title 1', content: 'Content 1', open: false},
|
||||
{name: 'title 2', content: 'Content 2', open: true}
|
||||
];
|
||||
$compile(element)(scope);
|
||||
scope.$digest();
|
||||
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
|
||||
it('should add "panel-open" class', function(){
|
||||
expect(groups.eq(0)).not.toHaveClass('panel-open');
|
||||
expect(groups.eq(1)).toHaveClass('panel-open');
|
||||
});
|
||||
});
|
||||
|
||||
describe('`is-disabled` attribute', function() {
|
||||
var groupBody;
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<uib-accordion-group heading="title 1" is-disabled="disabled">Content 1</uib-accordion-group>' +
|
||||
'</uib-accordion>';
|
||||
'<uib-accordion>' +
|
||||
'<div uib-accordion-group heading="title 1" is-disabled="disabled">Content 1</div>' +
|
||||
'</uib-accordion>';
|
||||
element = angular.element(tpl);
|
||||
scope.disabled = true;
|
||||
$compile(element)(scope);
|
||||
@@ -465,29 +517,29 @@ describe('uib-accordion', function() {
|
||||
|
||||
// This is re-used in both the uib-accordion-heading element and the uib-accordion-heading attribute tests
|
||||
function isDisabledStyleCheck() {
|
||||
var tpl =
|
||||
'<uib-accordion ng-init="a = [1,2,3]">' +
|
||||
'<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>' +
|
||||
'</uib-accordion>';
|
||||
scope.disabled = true;
|
||||
element = $compile(tpl)(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
var tpl =
|
||||
'<uib-accordion ng-init="a = [1,2,3]">' +
|
||||
'<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' +
|
||||
'</div>' +
|
||||
'</uib-accordion>';
|
||||
scope.disabled = true;
|
||||
element = $compile(tpl)(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
|
||||
expect(findGroupLink(0).find('span').hasClass('text-muted')).toBe(true);
|
||||
expect(findGroupLink(0).find('span').hasClass('text-muted')).toBe(true);
|
||||
}
|
||||
|
||||
describe('uib-accordion-heading element', 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();
|
||||
@@ -499,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() {
|
||||
@@ -507,17 +559,16 @@ describe('uib-accordion', function() {
|
||||
});
|
||||
|
||||
it('should have disabled styling when is-disabled is true', isDisabledStyleCheck);
|
||||
|
||||
});
|
||||
|
||||
describe('uib-accordion-heading attribute', 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();
|
||||
@@ -529,16 +580,15 @@ 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);
|
||||
|
||||
});
|
||||
|
||||
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);
|
||||
@@ -550,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);
|
||||
@@ -560,76 +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');
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* Deprecation tests below */
|
||||
|
||||
describe('accordion deprecation', function() {
|
||||
beforeEach(module('ui.bootstrap.accordion'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
beforeEach(module('template/accordion/accordion.html'));
|
||||
beforeEach(module('template/accordion/accordion-group.html'));
|
||||
|
||||
it('should suppress warning', function() {
|
||||
module(function($provide) {
|
||||
$provide.value('$accordionSuppressWarning', true);
|
||||
});
|
||||
|
||||
inject(function($compile, $log, $rootScope) {
|
||||
spyOn($log, 'warn');
|
||||
|
||||
var element =
|
||||
'<accordion ng-init="a = [1,2,3]">' +
|
||||
'<accordion-group heading="I get overridden">' +
|
||||
'<div accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </div>' +
|
||||
'Body' +
|
||||
'</accordion-group>' +
|
||||
'</accordion>';
|
||||
element = $compile(element)($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($log.warn.calls.count()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should give warning by default', inject(function($compile, $log, $rootScope) {
|
||||
spyOn($log, 'warn');
|
||||
|
||||
var element =
|
||||
'<accordion ng-init="a = [1,2,3]">' +
|
||||
'<accordion-group heading="I get overridden">' +
|
||||
'<div accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </div>' +
|
||||
'Body' +
|
||||
'</accordion-group>' +
|
||||
'</accordion>';
|
||||
element = $compile(element)($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($log.warn.calls.count()).toBe(4);
|
||||
expect($log.warn.calls.argsFor(0)).toEqual(['AccordionController is now deprecated. Use UibAccordionController instead.']);
|
||||
expect($log.warn.calls.argsFor(1)).toEqual(['accordion-heading is now deprecated. Use uib-accordion-heading instead.']);
|
||||
expect($log.warn.calls.argsFor(2)).toEqual(['accordion-group is now deprecated. Use uib-accordion-group instead.']);
|
||||
expect($log.warn.calls.argsFor(3)).toEqual(['accordion is now deprecated. Use uib-accordion instead.']);
|
||||
}));
|
||||
});
|
||||
|
||||
+8
-42
@@ -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,52 +22,13 @@ angular.module('ui.bootstrap.alert', [])
|
||||
return {
|
||||
controller: 'UibAlertController',
|
||||
controllerAs: 'alert',
|
||||
restrict: 'A',
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'template/alert/alert.html';
|
||||
return attrs.templateUrl || 'uib/template/alert/alert.html';
|
||||
},
|
||||
transclude: true,
|
||||
replace: true,
|
||||
scope: {
|
||||
type: '@',
|
||||
close: '&'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/* Deprecated alert below */
|
||||
|
||||
angular.module('ui.bootstrap.alert')
|
||||
|
||||
.value('$alertSuppressWarning', false)
|
||||
|
||||
.controller('AlertController', ['$scope', '$attrs', '$controller', '$log', '$alertSuppressWarning', function($scope, $attrs, $controller, $log, $alertSuppressWarning) {
|
||||
if (!$alertSuppressWarning) {
|
||||
$log.warn('AlertController is now deprecated. Use UibAlertController instead.');
|
||||
}
|
||||
|
||||
angular.extend(this, $controller('UibAlertController', {
|
||||
$scope: $scope,
|
||||
$attrs: $attrs
|
||||
}));
|
||||
}])
|
||||
|
||||
.directive('alert', ['$log', '$alertSuppressWarning', function($log, $alertSuppressWarning) {
|
||||
return {
|
||||
controller: 'AlertController',
|
||||
controllerAs: 'alert',
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'template/alert/alert.html';
|
||||
},
|
||||
transclude: true,
|
||||
replace: true,
|
||||
scope: {
|
||||
type: '@',
|
||||
close: '&'
|
||||
},
|
||||
link: function() {
|
||||
if (!$alertSuppressWarning) {
|
||||
$log.warn('alert is now deprecated. Use uib-alert instead.');
|
||||
}
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -2,11 +2,14 @@ This directive can be used both to generate alerts from static and dynamic model
|
||||
|
||||
### uib-alert settings
|
||||
|
||||
* `close` _(Default: `none`)_ -
|
||||
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`)(Optional)_ -
|
||||
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: `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.
|
||||
* `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.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
require('../../template/alert/alert.html.js');
|
||||
require('./alert');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.alert';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.alert', 'uib/template/alert/alert.html']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
@@ -2,7 +2,7 @@ describe('uib-alert', function() {
|
||||
var element, scope, $compile, $templateCache, $timeout;
|
||||
|
||||
beforeEach(module('ui.bootstrap.alert'));
|
||||
beforeEach(module('template/alert/alert.html'));
|
||||
beforeEach(module('uib/template/alert/alert.html'));
|
||||
|
||||
beforeEach(inject(function($rootScope, _$compile_, _$templateCache_, _$timeout_) {
|
||||
scope = $rootScope;
|
||||
@@ -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('template/alert/alert.html', '<div>{{alert.text}}</div>');
|
||||
$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);
|
||||
@@ -150,37 +127,3 @@ describe('uib-alert', function() {
|
||||
expect(scope.removeAlert).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
/* Deprecation tests below */
|
||||
|
||||
describe('alert deprecation', function() {
|
||||
beforeEach(module('ui.bootstrap.alert'));
|
||||
beforeEach(module('template/alert/alert.html'));
|
||||
|
||||
it('should suppress warning', function() {
|
||||
module(function($provide) {
|
||||
$provide.value('$alertSuppressWarning', true);
|
||||
});
|
||||
|
||||
inject(function($compile, $log, $rootScope) {
|
||||
spyOn($log, 'warn');
|
||||
|
||||
var element = '<alert></alert>';
|
||||
element = $compile(element)($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($log.warn.calls.count()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should give warning by default', inject(function($compile, $log, $rootScope) {
|
||||
spyOn($log, 'warn');
|
||||
|
||||
var element = '<alert></alert>';
|
||||
element = $compile(element)($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($log.warn.calls.count()).toBe(2);
|
||||
expect($log.warn.calls.argsFor(0)).toEqual(['AlertController is now deprecated. Use UibAlertController instead.']);
|
||||
expect($log.warn.calls.argsFor(1)).toEqual(['alert is now deprecated. Use uib-alert instead.']);
|
||||
}));
|
||||
});
|
||||
|
||||
+9
-114
@@ -10,13 +10,14 @@ angular.module('ui.bootstrap.buttons', [])
|
||||
this.toggleEvent = buttonConfig.toggleEvent || 'click';
|
||||
}])
|
||||
|
||||
.directive('uibBtnRadio', function() {
|
||||
.directive('uibBtnRadio', ['$parse', function($parse) {
|
||||
return {
|
||||
require: ['uibBtnRadio', 'ngModel'],
|
||||
controller: 'UibButtonsController',
|
||||
controllerAs: 'buttons',
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
||||
var uncheckableExpr = $parse(attrs.uibUncheckable);
|
||||
|
||||
element.find('input').css({display: 'none'});
|
||||
|
||||
@@ -40,9 +41,15 @@ angular.module('ui.bootstrap.buttons', [])
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (attrs.uibUncheckable) {
|
||||
scope.$watch(uncheckableExpr, function(uncheckable) {
|
||||
attrs.$set('uncheckable', uncheckable ? '' : undefined);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
}])
|
||||
|
||||
.directive('uibBtnCheckbox', function() {
|
||||
return {
|
||||
@@ -85,115 +92,3 @@ angular.module('ui.bootstrap.buttons', [])
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/* Deprecated buttons below */
|
||||
|
||||
angular.module('ui.bootstrap.buttons')
|
||||
|
||||
.value('$buttonsSuppressWarning', false)
|
||||
|
||||
.controller('ButtonsController', ['$controller', '$log', '$buttonsSuppressWarning', function($controller, $log, $buttonsSuppressWarning) {
|
||||
if (!$buttonsSuppressWarning) {
|
||||
$log.warn('ButtonsController is now deprecated. Use UibButtonsController instead.');
|
||||
}
|
||||
|
||||
angular.extend(this, $controller('UibButtonsController'));
|
||||
}])
|
||||
|
||||
.directive('btnRadio', ['$log', '$buttonsSuppressWarning', function($log, $buttonsSuppressWarning) {
|
||||
return {
|
||||
require: ['btnRadio', 'ngModel'],
|
||||
controller: 'ButtonsController',
|
||||
controllerAs: 'buttons',
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
if (!$buttonsSuppressWarning) {
|
||||
$log.warn('btn-radio is now deprecated. Use uib-btn-radio instead.');
|
||||
}
|
||||
|
||||
var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
||||
|
||||
element.find('input').css({display: 'none'});
|
||||
|
||||
//model -> UI
|
||||
ngModelCtrl.$render = function() {
|
||||
element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
|
||||
};
|
||||
|
||||
//ui->model
|
||||
element.bind(buttonsCtrl.toggleEvent, function() {
|
||||
if (attrs.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var isActive = element.hasClass(buttonsCtrl.activeClass);
|
||||
|
||||
if (!isActive || angular.isDefined(attrs.uncheckable)) {
|
||||
scope.$apply(function() {
|
||||
ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio));
|
||||
ngModelCtrl.$render();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('btnCheckbox', ['$document', '$log', '$buttonsSuppressWarning', function($document, $log, $buttonsSuppressWarning) {
|
||||
return {
|
||||
require: ['btnCheckbox', 'ngModel'],
|
||||
controller: 'ButtonsController',
|
||||
controllerAs: 'button',
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
if (!$buttonsSuppressWarning) {
|
||||
$log.warn('btn-checkbox is now deprecated. Use uib-btn-checkbox instead.');
|
||||
}
|
||||
|
||||
var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
||||
|
||||
element.find('input').css({display: 'none'});
|
||||
|
||||
function getTrueValue() {
|
||||
return getCheckboxValue(attrs.btnCheckboxTrue, true);
|
||||
}
|
||||
|
||||
function getFalseValue() {
|
||||
return getCheckboxValue(attrs.btnCheckboxFalse, false);
|
||||
}
|
||||
|
||||
function getCheckboxValue(attributeValue, defaultValue) {
|
||||
var val = scope.$eval(attributeValue);
|
||||
return angular.isDefined(val) ? val : defaultValue;
|
||||
}
|
||||
|
||||
//model -> UI
|
||||
ngModelCtrl.$render = function() {
|
||||
element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
|
||||
};
|
||||
|
||||
//ui->model
|
||||
element.bind(buttonsCtrl.toggleEvent, function() {
|
||||
if (attrs.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.$apply(function() {
|
||||
ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
|
||||
ngModelCtrl.$render();
|
||||
});
|
||||
});
|
||||
|
||||
//accessibility
|
||||
element.on('keypress', function(e) {
|
||||
if (attrs.disabled || e.which !== 32 || $document[0].activeElement !== element[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.$apply(function() {
|
||||
ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
|
||||
ngModelCtrl.$render();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
<div class="btn-group">
|
||||
<label class="btn btn-success" ng-model="radioModel" uib-btn-radio="'Left'" uncheckable>Left</label>
|
||||
<label class="btn btn-success" ng-model="radioModel" uib-btn-radio="'Middle'" uncheckable>Middle</label>
|
||||
<label class="btn btn-success" ng-model="radioModel" uib-btn-radio="'Right'" uncheckable>Right</label>
|
||||
<label class="btn btn-success" ng-model="radioModel" uib-btn-radio="'Right'" uib-uncheckable="uncheckable">Right</label>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-default" ng-click="uncheckable = !uncheckable">
|
||||
Toggle uncheckable
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+24
-10
@@ -2,30 +2,40 @@ With the buttons directive, we can make a group of buttons behave like a set of
|
||||
|
||||
### uib-btn-checkbox settings
|
||||
|
||||
* `ng-model` -
|
||||
Model where we set the checkbox status. By default `true` or `false`.
|
||||
* `btn-checkbox-false`
|
||||
_(Default: `false`)_ -
|
||||
Sets the value for the unchecked status.
|
||||
|
||||
* `btn-checkbox-true`
|
||||
_(Default: `true`)_ -
|
||||
Sets the value for the checked status.
|
||||
|
||||
* `btn-checkbox-false`
|
||||
_(Default: `false`)_ -
|
||||
Sets the value for the unchecked status.
|
||||
|
||||
* `ng-model`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i> -
|
||||
Model where we set the checkbox status. By default `true` or `false`.
|
||||
|
||||
### uib-btn-radio settings
|
||||
|
||||
* `ng-model`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i> -
|
||||
Model where we set the radio status. All radio buttons in a group should use the same `ng-model`.
|
||||
|
||||
* `uib-btn-radio` -
|
||||
<small class="badge">$</small>
|
||||
Value to assign to the `ng-model` if we check this radio button.
|
||||
|
||||
* `ng-model` -
|
||||
Model where we set the radio status. All radio buttons in a group should use the same `ng-model`.
|
||||
* `uib-uncheckable`
|
||||
<small class="badge">$</small>
|
||||
_(Default: `null`)_ -
|
||||
An expression that evaluates to a truthy or falsy value that determines whether the `uncheckable` attribute is present.
|
||||
|
||||
* `uncheckable`
|
||||
_(Boolean attribute)_ -
|
||||
<small class="badge">B</small> -
|
||||
Whether a radio button can be unchecked or not.
|
||||
|
||||
### Default settings `uibButtonConfig`
|
||||
### Additional settings `uibButtonConfig`
|
||||
|
||||
* `activeClass`
|
||||
_(Default: `active`)_ -
|
||||
@@ -34,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.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
require('./buttons');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.buttons';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.buttons']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
@@ -162,7 +162,6 @@ describe('buttons', function() {
|
||||
});
|
||||
|
||||
describe('radio', function() {
|
||||
|
||||
var compileButtons = function(markup, scope) {
|
||||
var el = $compile('<div>'+markup+'</div>')(scope);
|
||||
scope.$digest();
|
||||
@@ -323,49 +322,45 @@ describe('buttons', function() {
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* Deprecation tests below */
|
||||
describe('uibUncheckable', function() {
|
||||
it('should set uncheckable', function() {
|
||||
$scope.uncheckable = false;
|
||||
var btns = compileButtons('<button ng-model="model" uib-btn-radio="1">click1</button><button ng-model="model" uib-btn-radio="2" uib-uncheckable="uncheckable">click2</button>', $scope);
|
||||
expect(btns.eq(0).attr('uncheckable')).toBeUndefined();
|
||||
expect(btns.eq(1).attr('uncheckable')).toBeUndefined();
|
||||
|
||||
describe('buttons deprecation', function() {
|
||||
beforeEach(module('ui.bootstrap.buttons'));
|
||||
expect($scope.model).toBeUndefined();
|
||||
|
||||
it('should suppress warning', function() {
|
||||
module(function($provide) {
|
||||
$provide.value('$buttonsSuppressWarning', true);
|
||||
});
|
||||
btns.eq(0).click();
|
||||
expect($scope.model).toEqual(1);
|
||||
|
||||
inject(function($compile, $log, $rootScope) {
|
||||
spyOn($log, 'warn');
|
||||
btns.eq(0).click();
|
||||
expect($scope.model).toEqual(1);
|
||||
|
||||
var element = $compile('<button ng-model="model" btn-checkbox>click</button>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
btns.eq(1).click();
|
||||
expect($scope.model).toEqual(2);
|
||||
|
||||
expect($log.warn.calls.count()).toBe(0);
|
||||
btns.eq(1).click();
|
||||
expect($scope.model).toEqual(2);
|
||||
|
||||
element = $compile('<button ng-model="model" btn-radio="1">click1</button><button ng-model="model" btn-radio="2">click2</button>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
$scope.uncheckable = true;
|
||||
$scope.$digest();
|
||||
expect(btns.eq(0).attr('uncheckable')).toBeUndefined();
|
||||
expect(btns.eq(1).attr('uncheckable')).toBeDefined();
|
||||
|
||||
expect($log.warn.calls.count()).toBe(0);
|
||||
btns.eq(0).click();
|
||||
expect($scope.model).toEqual(1);
|
||||
|
||||
btns.eq(0).click();
|
||||
expect($scope.model).toEqual(1);
|
||||
|
||||
btns.eq(1).click();
|
||||
expect($scope.model).toEqual(2);
|
||||
|
||||
btns.eq(1).click();
|
||||
expect($scope.model).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should give warning by default', inject(function($compile, $log, $rootScope) {
|
||||
spyOn($log, 'warn');
|
||||
|
||||
var element = $compile('<button ng-model="model" btn-checkbox>click</button>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
element = $compile('<button ng-model="model" btn-radio="1">click1</button><button ng-model="model" btn-radio="2">click2</button>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($log.warn.calls.count()).toBe(6);
|
||||
expect($log.warn.calls.argsFor(0)).toEqual(['ButtonsController is now deprecated. Use UibButtonsController instead.']);
|
||||
expect($log.warn.calls.argsFor(1)).toEqual(['btn-checkbox is now deprecated. Use uib-btn-checkbox instead.']);
|
||||
expect($log.warn.calls.argsFor(2)).toEqual(['ButtonsController is now deprecated. Use UibButtonsController instead.']);
|
||||
expect($log.warn.calls.argsFor(3)).toEqual(['btn-radio is now deprecated. Use uib-btn-radio instead.']);
|
||||
expect($log.warn.calls.argsFor(4)).toEqual(['ButtonsController is now deprecated. Use UibButtonsController instead.']);
|
||||
expect($log.warn.calls.argsFor(5)).toEqual(['btn-radio is now deprecated. Use uib-btn-radio instead.']);
|
||||
}));
|
||||
});
|
||||
|
||||
+228
-368
@@ -1,101 +1,48 @@
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name ui.bootstrap.carousel
|
||||
*
|
||||
* @description
|
||||
* AngularJS version of an image carousel.
|
||||
*
|
||||
*/
|
||||
angular.module('ui.bootstrap.carousel', [])
|
||||
|
||||
.controller('UibCarouselController', ['$scope', '$element', '$interval', '$animate', function($scope, $element, $interval, $animate) {
|
||||
.controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
|
||||
var self = this,
|
||||
slides = self.slides = $scope.slides = [],
|
||||
NEW_ANIMATE = angular.version.minor >= 4,
|
||||
NO_TRANSITION = 'uib-noTransition',
|
||||
SLIDE_DIRECTION = 'uib-slideDirection',
|
||||
currentIndex = -1,
|
||||
currentIndex = $scope.active,
|
||||
currentInterval, isPlaying;
|
||||
self.currentSlide = null;
|
||||
|
||||
var destroyed = false;
|
||||
/* direction: "prev" or "next" */
|
||||
self.select = $scope.select = function(nextSlide, direction) {
|
||||
var nextIndex = $scope.indexOfSlide(nextSlide);
|
||||
//Decide direction if it's not given
|
||||
if (direction === undefined) {
|
||||
direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
|
||||
}
|
||||
//Prevent this user-triggered transition from occurring if there is already one in progress
|
||||
if (nextSlide && nextSlide !== self.currentSlide && !$scope.$currentTransition) {
|
||||
goNext(nextSlide, nextIndex, direction);
|
||||
$element.addClass('carousel');
|
||||
|
||||
self.addSlide = function(slide, element) {
|
||||
slides.push({
|
||||
slide: slide,
|
||||
element: element
|
||||
});
|
||||
slides.sort(function(a, b) {
|
||||
return +a.slide.index - +b.slide.index;
|
||||
});
|
||||
//if this is the first slide or the slide is set to active, select it
|
||||
if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) {
|
||||
if ($scope.$currentTransition) {
|
||||
$scope.$currentTransition = null;
|
||||
}
|
||||
|
||||
currentIndex = slide.index;
|
||||
$scope.active = slide.index;
|
||||
setActive(currentIndex);
|
||||
self.select(slides[findSlideIndex(slide)]);
|
||||
if (slides.length === 1) {
|
||||
$scope.play();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function goNext(slide, index, direction) {
|
||||
// Scope has been destroyed, stop here.
|
||||
if (destroyed) { return; }
|
||||
|
||||
angular.extend(slide, {direction: direction, active: true});
|
||||
angular.extend(self.currentSlide || {}, {direction: direction, active: false});
|
||||
if ($animate.enabled() && !$scope.noTransition && !$scope.$currentTransition &&
|
||||
slide.$element && self.slides.length > 1) {
|
||||
slide.$element.data(SLIDE_DIRECTION, slide.direction);
|
||||
if (self.currentSlide && self.currentSlide.$element) {
|
||||
self.currentSlide.$element.data(SLIDE_DIRECTION, slide.direction);
|
||||
}
|
||||
|
||||
$scope.$currentTransition = true;
|
||||
if (NEW_ANIMATE) {
|
||||
$animate.on('addClass', slide.$element, function(element, phase) {
|
||||
if (phase === 'close') {
|
||||
$scope.$currentTransition = null;
|
||||
$animate.off('addClass', element);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
slide.$element.one('$animate:close', function closeFn() {
|
||||
$scope.$currentTransition = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.currentSlide = slide;
|
||||
currentIndex = index;
|
||||
|
||||
//every time you change slides, reset the timer
|
||||
restartTimer();
|
||||
}
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
destroyed = true;
|
||||
});
|
||||
|
||||
function getSlideByIndex(index) {
|
||||
if (angular.isUndefined(slides[index].index)) {
|
||||
return slides[index];
|
||||
}
|
||||
var i, len = slides.length;
|
||||
for (i = 0; i < slides.length; ++i) {
|
||||
if (slides[i].index == index) {
|
||||
return slides[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.getCurrentIndex = function() {
|
||||
if (self.currentSlide && angular.isDefined(self.currentSlide.index)) {
|
||||
return +self.currentSlide.index;
|
||||
for (var i = 0; i < slides.length; i++) {
|
||||
if (slides[i].slide.index === currentIndex) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return currentIndex;
|
||||
};
|
||||
|
||||
/* Allow outside people to call indexOf on slides array */
|
||||
$scope.indexOfSlide = function(slide) {
|
||||
return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide);
|
||||
};
|
||||
|
||||
$scope.next = function() {
|
||||
self.next = $scope.next = function() {
|
||||
var newIndex = (self.getCurrentIndex() + 1) % slides.length;
|
||||
|
||||
if (newIndex === 0 && $scope.noWrap()) {
|
||||
@@ -103,10 +50,10 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
return;
|
||||
}
|
||||
|
||||
return self.select(getSlideByIndex(newIndex), 'next');
|
||||
return self.select(slides[newIndex], 'next');
|
||||
};
|
||||
|
||||
$scope.prev = function() {
|
||||
self.prev = $scope.prev = function() {
|
||||
var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
|
||||
|
||||
if ($scope.noWrap() && newIndex === slides.length - 1) {
|
||||
@@ -114,22 +61,169 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
return;
|
||||
}
|
||||
|
||||
return self.select(getSlideByIndex(newIndex), 'prev');
|
||||
return self.select(slides[newIndex], 'prev');
|
||||
};
|
||||
|
||||
self.removeSlide = function(slide) {
|
||||
var index = findSlideIndex(slide);
|
||||
|
||||
//get the index of the slide inside the carousel
|
||||
slides.splice(index, 1);
|
||||
if (slides.length > 0 && currentIndex === index) {
|
||||
if (index >= slides.length) {
|
||||
currentIndex = slides.length - 1;
|
||||
$scope.active = currentIndex;
|
||||
setActive(currentIndex);
|
||||
self.select(slides[slides.length - 1]);
|
||||
} else {
|
||||
currentIndex = index;
|
||||
$scope.active = currentIndex;
|
||||
setActive(currentIndex);
|
||||
self.select(slides[index]);
|
||||
}
|
||||
} else if (currentIndex > index) {
|
||||
currentIndex--;
|
||||
$scope.active = currentIndex;
|
||||
}
|
||||
|
||||
//clean the active value when no more slide
|
||||
if (slides.length === 0) {
|
||||
currentIndex = null;
|
||||
$scope.active = null;
|
||||
}
|
||||
};
|
||||
|
||||
/* direction: "prev" or "next" */
|
||||
self.select = $scope.select = function(nextSlide, direction) {
|
||||
var nextIndex = findSlideIndex(nextSlide.slide);
|
||||
//Decide direction if it's not given
|
||||
if (direction === undefined) {
|
||||
direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
|
||||
}
|
||||
//Prevent this user-triggered transition from occurring if there is already one in progress
|
||||
if (nextSlide.slide.index !== currentIndex &&
|
||||
!$scope.$currentTransition) {
|
||||
goNext(nextSlide.slide, nextIndex, direction);
|
||||
}
|
||||
};
|
||||
|
||||
/* Allow outside people to call indexOf on slides array */
|
||||
$scope.indexOfSlide = function(slide) {
|
||||
return +slide.slide.index;
|
||||
};
|
||||
|
||||
$scope.isActive = function(slide) {
|
||||
return self.currentSlide === slide;
|
||||
return $scope.active === slide.slide.index;
|
||||
};
|
||||
|
||||
$scope.$watch('interval', restartTimer);
|
||||
$scope.$watchCollection('slides', resetTransition);
|
||||
$scope.$on('$destroy', resetTimer);
|
||||
$scope.isPrevDisabled = function() {
|
||||
return $scope.active === 0 && $scope.noWrap();
|
||||
};
|
||||
|
||||
function restartTimer() {
|
||||
$scope.isNextDisabled = function() {
|
||||
return $scope.active === slides.length - 1 && $scope.noWrap();
|
||||
};
|
||||
|
||||
$scope.pause = function() {
|
||||
if (!$scope.noPause) {
|
||||
isPlaying = false;
|
||||
resetTimer();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.play = function() {
|
||||
if (!isPlaying) {
|
||||
isPlaying = true;
|
||||
restartTimer();
|
||||
}
|
||||
};
|
||||
|
||||
$element.on('mouseenter', $scope.pause);
|
||||
$element.on('mouseleave', $scope.play);
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
destroyed = true;
|
||||
resetTimer();
|
||||
var interval = +$scope.interval;
|
||||
if (!isNaN(interval) && interval > 0) {
|
||||
currentInterval = $interval(timerFn, interval);
|
||||
});
|
||||
|
||||
$scope.$watch('noTransition', function(noTransition) {
|
||||
$animate.enabled($element, !noTransition);
|
||||
});
|
||||
|
||||
$scope.$watch('interval', restartTimer);
|
||||
|
||||
$scope.$watchCollection('slides', resetTransition);
|
||||
|
||||
$scope.$watch('active', function(index) {
|
||||
if (angular.isNumber(index) && currentIndex !== index) {
|
||||
for (var i = 0; i < slides.length; i++) {
|
||||
if (slides[i].slide.index === index) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var slide = slides[index];
|
||||
if (slide) {
|
||||
setActive(index);
|
||||
self.select(slides[index]);
|
||||
currentIndex = index;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function getSlideByIndex(index) {
|
||||
for (var i = 0, l = slides.length; i < l; ++i) {
|
||||
if (slides[i].index === index) {
|
||||
return slides[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setActive(index) {
|
||||
for (var i = 0; i < slides.length; i++) {
|
||||
slides[i].slide.active = i === index;
|
||||
}
|
||||
}
|
||||
|
||||
function goNext(slide, index, direction) {
|
||||
if (destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
angular.extend(slide, {direction: direction});
|
||||
angular.extend(slides[currentIndex].slide || {}, {direction: direction});
|
||||
if ($animate.enabled($element) && !$scope.$currentTransition &&
|
||||
slides[index].element && self.slides.length > 1) {
|
||||
slides[index].element.data(SLIDE_DIRECTION, slide.direction);
|
||||
var currentIdx = self.getCurrentIndex();
|
||||
|
||||
if (angular.isNumber(currentIdx) && slides[currentIdx].element) {
|
||||
slides[currentIdx].element.data(SLIDE_DIRECTION, slide.direction);
|
||||
}
|
||||
|
||||
$scope.$currentTransition = true;
|
||||
$animate.on('addClass', slides[index].element, function(element, phase) {
|
||||
if (phase === 'close') {
|
||||
$scope.$currentTransition = null;
|
||||
$animate.off('addClass', element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.active = slide.index;
|
||||
currentIndex = slide.index;
|
||||
setActive(index);
|
||||
|
||||
//every time you change slides, reset the timer
|
||||
restartTimer();
|
||||
}
|
||||
|
||||
function findSlideIndex(slide) {
|
||||
for (var i = 0; i < slides.length; i++) {
|
||||
if (slides[i].slide === slide) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +234,20 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
}
|
||||
}
|
||||
|
||||
function resetTransition(slides) {
|
||||
if (!slides.length) {
|
||||
$scope.$currentTransition = null;
|
||||
}
|
||||
}
|
||||
|
||||
function restartTimer() {
|
||||
resetTimer();
|
||||
var interval = +$scope.interval;
|
||||
if (!isNaN(interval) && interval > 0) {
|
||||
currentInterval = $interval(timerFn, interval);
|
||||
}
|
||||
}
|
||||
|
||||
function timerFn() {
|
||||
var interval = +$scope.interval;
|
||||
if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
|
||||
@@ -148,185 +256,41 @@ angular.module('ui.bootstrap.carousel', [])
|
||||
$scope.pause();
|
||||
}
|
||||
}
|
||||
|
||||
function resetTransition(slides) {
|
||||
if (!slides.length) {
|
||||
$scope.$currentTransition = null;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.play = function() {
|
||||
if (!isPlaying) {
|
||||
isPlaying = true;
|
||||
restartTimer();
|
||||
}
|
||||
};
|
||||
$scope.pause = function() {
|
||||
if (!$scope.noPause) {
|
||||
isPlaying = false;
|
||||
resetTimer();
|
||||
}
|
||||
};
|
||||
|
||||
self.addSlide = function(slide, element) {
|
||||
slide.$element = element;
|
||||
slides.push(slide);
|
||||
//if this is the first slide or the slide is set to active, select it
|
||||
if (slides.length === 1 || slide.active) {
|
||||
self.select(slides[slides.length - 1]);
|
||||
if (slides.length === 1) {
|
||||
$scope.play();
|
||||
}
|
||||
} else {
|
||||
slide.active = false;
|
||||
}
|
||||
};
|
||||
|
||||
self.removeSlide = function(slide) {
|
||||
if (angular.isDefined(slide.index)) {
|
||||
slides.sort(function(a, b) {
|
||||
return +a.index > +b.index;
|
||||
});
|
||||
}
|
||||
//get the index of the slide inside the carousel
|
||||
var index = slides.indexOf(slide);
|
||||
slides.splice(index, 1);
|
||||
if (slides.length > 0 && slide.active) {
|
||||
if (index >= slides.length) {
|
||||
self.select(slides[index - 1]);
|
||||
} else {
|
||||
self.select(slides[index]);
|
||||
}
|
||||
} else if (currentIndex > index) {
|
||||
currentIndex--;
|
||||
}
|
||||
|
||||
//clean the currentSlide when no more slide
|
||||
if (slides.length === 0) {
|
||||
self.currentSlide = null;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$watch('noTransition', function(noTransition) {
|
||||
$element.data(NO_TRANSITION, noTransition);
|
||||
});
|
||||
|
||||
}])
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ui.bootstrap.carousel.directive:carousel
|
||||
* @restrict EA
|
||||
*
|
||||
* @description
|
||||
* Carousel is the outer container for a set of image 'slides' to showcase.
|
||||
*
|
||||
* @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide.
|
||||
* @param {boolean=} noTransition Whether to disable transitions on the carousel.
|
||||
* @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover).
|
||||
*
|
||||
* @example
|
||||
<example module="ui.bootstrap">
|
||||
<file name="index.html">
|
||||
<uib-carousel>
|
||||
<uib-slide>
|
||||
<img src="http://placekitten.com/150/150" style="margin:auto;">
|
||||
<div class="carousel-caption">
|
||||
<p>Beautiful!</p>
|
||||
</div>
|
||||
</uib-slide>
|
||||
<uib-slide>
|
||||
<img src="http://placekitten.com/100/150" style="margin:auto;">
|
||||
<div class="carousel-caption">
|
||||
<p>D'aww!</p>
|
||||
</div>
|
||||
</uib-slide>
|
||||
</uib-carousel>
|
||||
</file>
|
||||
<file name="demo.css">
|
||||
.carousel-indicators {
|
||||
top: auto;
|
||||
bottom: 15px;
|
||||
}
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
.directive('uibCarousel', [function() {
|
||||
.directive('uibCarousel', function() {
|
||||
return {
|
||||
transclude: true,
|
||||
replace: true,
|
||||
controller: 'UibCarouselController',
|
||||
controllerAs: 'carousel',
|
||||
require: 'carousel',
|
||||
restrict: 'A',
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'template/carousel/carousel.html';
|
||||
return attrs.templateUrl || 'uib/template/carousel/carousel.html';
|
||||
},
|
||||
scope: {
|
||||
active: '=',
|
||||
interval: '=',
|
||||
noTransition: '=',
|
||||
noPause: '=',
|
||||
noWrap: '&'
|
||||
}
|
||||
};
|
||||
}])
|
||||
})
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ui.bootstrap.carousel.directive:slide
|
||||
* @restrict EA
|
||||
*
|
||||
* @description
|
||||
* Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element.
|
||||
*
|
||||
* @param {boolean=} active Model binding, whether or not this slide is currently active.
|
||||
* @param {number=} index The index of the slide. The slides will be sorted by this parameter.
|
||||
*
|
||||
* @example
|
||||
<example module="ui.bootstrap">
|
||||
<file name="index.html">
|
||||
<div ng-controller="CarouselDemoCtrl">
|
||||
<uib-carousel>
|
||||
<uib-slide ng-repeat="slide in slides" active="slide.active" index="$index">
|
||||
<img ng-src="{{slide.image}}" style="margin:auto;">
|
||||
<div class="carousel-caption">
|
||||
<h4>Slide {{$index}}</h4>
|
||||
<p>{{slide.text}}</p>
|
||||
</div>
|
||||
</uib-slide>
|
||||
</uib-carousel>
|
||||
Interval, in milliseconds: <input type="number" ng-model="myInterval">
|
||||
<br />Enter a negative number to stop the interval.
|
||||
</div>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
function CarouselDemoCtrl($scope) {
|
||||
$scope.myInterval = 5000;
|
||||
}
|
||||
</file>
|
||||
<file name="demo.css">
|
||||
.carousel-indicators {
|
||||
top: auto;
|
||||
bottom: 15px;
|
||||
}
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
|
||||
.directive('uibSlide', function() {
|
||||
.directive('uibSlide', ['$animate', function($animate) {
|
||||
return {
|
||||
require: '^uibCarousel',
|
||||
restrict: 'EA',
|
||||
restrict: 'A',
|
||||
transclude: true,
|
||||
replace: true,
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'template/carousel/slide.html';
|
||||
return attrs.templateUrl || 'uib/template/carousel/slide.html';
|
||||
},
|
||||
scope: {
|
||||
active: '=?',
|
||||
actual: '=?',
|
||||
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() {
|
||||
@@ -334,24 +298,15 @@ function CarouselDemoCtrl($scope) {
|
||||
});
|
||||
|
||||
scope.$watch('active', function(active) {
|
||||
if (active) {
|
||||
carouselCtrl.select(scope);
|
||||
}
|
||||
$animate[active ? 'addClass' : 'removeClass'](element, 'active');
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
}])
|
||||
|
||||
.animation('.item', [
|
||||
'$injector', '$animate',
|
||||
function ($injector, $animate) {
|
||||
var NO_TRANSITION = 'uib-noTransition',
|
||||
SLIDE_DIRECTION = 'uib-slideDirection',
|
||||
$animateCss = null;
|
||||
|
||||
if ($injector.has('$animateCss')) {
|
||||
$animateCss = $injector.get('$animateCss');
|
||||
}
|
||||
.animation('.item', ['$animateCss',
|
||||
function($animateCss) {
|
||||
var SLIDE_DIRECTION = 'uib-slideDirection';
|
||||
|
||||
function removeClass(element, className, callback) {
|
||||
element.removeClass(className);
|
||||
@@ -362,56 +317,35 @@ function ($injector, $animate) {
|
||||
|
||||
return {
|
||||
beforeAddClass: function(element, className, done) {
|
||||
// Due to transclusion, noTransition property is on parent's scope
|
||||
if (className == 'active' && element.parent() && element.parent().parent() &&
|
||||
!element.parent().parent().data(NO_TRANSITION)) {
|
||||
if (className === 'active') {
|
||||
var stopped = false;
|
||||
var direction = element.data(SLIDE_DIRECTION);
|
||||
var directionClass = direction == 'next' ? 'left' : 'right';
|
||||
var directionClass = direction === 'next' ? 'left' : 'right';
|
||||
var removeClassFn = removeClass.bind(this, element,
|
||||
directionClass + ' ' + direction, done);
|
||||
element.addClass(direction);
|
||||
|
||||
if ($animateCss) {
|
||||
$animateCss(element, {addClass: directionClass})
|
||||
.start()
|
||||
.done(removeClassFn);
|
||||
} else {
|
||||
$animate.addClass(element, directionClass).then(function () {
|
||||
if (!stopped) {
|
||||
removeClassFn();
|
||||
}
|
||||
done();
|
||||
});
|
||||
}
|
||||
$animateCss(element, {addClass: directionClass})
|
||||
.start()
|
||||
.done(removeClassFn);
|
||||
|
||||
return function () {
|
||||
return function() {
|
||||
stopped = true;
|
||||
};
|
||||
}
|
||||
done();
|
||||
},
|
||||
beforeRemoveClass: function (element, className, done) {
|
||||
// Due to transclusion, noTransition property is on parent's scope
|
||||
if (className === 'active' && element.parent() && element.parent().parent() &&
|
||||
!element.parent().parent().data(NO_TRANSITION)) {
|
||||
if (className === 'active') {
|
||||
var stopped = false;
|
||||
var direction = element.data(SLIDE_DIRECTION);
|
||||
var directionClass = direction == 'next' ? 'left' : 'right';
|
||||
var directionClass = direction === 'next' ? 'left' : 'right';
|
||||
var removeClassFn = removeClass.bind(this, element, directionClass, done);
|
||||
|
||||
if ($animateCss) {
|
||||
$animateCss(element, {addClass: directionClass})
|
||||
.start()
|
||||
.done(removeClassFn);
|
||||
} else {
|
||||
$animate.addClass(element, directionClass).then(function() {
|
||||
if (!stopped) {
|
||||
removeClassFn();
|
||||
}
|
||||
done();
|
||||
});
|
||||
}
|
||||
$animateCss(element, {addClass: directionClass})
|
||||
.start()
|
||||
.done(removeClassFn);
|
||||
|
||||
return function() {
|
||||
stopped = true;
|
||||
};
|
||||
@@ -420,77 +354,3 @@ function ($injector, $animate) {
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
/* deprecated carousel below */
|
||||
|
||||
angular.module('ui.bootstrap.carousel')
|
||||
|
||||
.value('$carouselSuppressWarning', false)
|
||||
|
||||
.controller('CarouselController', ['$scope', '$element', '$controller', '$log', '$carouselSuppressWarning', function($scope, $element, $controller, $log, $carouselSuppressWarning) {
|
||||
if (!$carouselSuppressWarning) {
|
||||
$log.warn('CarouselController is now deprecated. Use UibCarouselController instead.');
|
||||
}
|
||||
|
||||
angular.extend(this, $controller('UibCarouselController', {
|
||||
$scope: $scope,
|
||||
$element: $element
|
||||
}));
|
||||
}])
|
||||
|
||||
.directive('carousel', ['$log', '$carouselSuppressWarning', function($log, $carouselSuppressWarning) {
|
||||
return {
|
||||
transclude: true,
|
||||
replace: true,
|
||||
controller: 'CarouselController',
|
||||
controllerAs: 'carousel',
|
||||
require: 'carousel',
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'template/carousel/carousel.html';
|
||||
},
|
||||
scope: {
|
||||
interval: '=',
|
||||
noTransition: '=',
|
||||
noPause: '=',
|
||||
noWrap: '&'
|
||||
},
|
||||
link: function() {
|
||||
if (!$carouselSuppressWarning) {
|
||||
$log.warn('carousel is now deprecated. Use uib-carousel instead.');
|
||||
}
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('slide', ['$log', '$carouselSuppressWarning', function($log, $carouselSuppressWarning) {
|
||||
return {
|
||||
require: '^carousel',
|
||||
transclude: true,
|
||||
replace: true,
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'template/carousel/slide.html';
|
||||
},
|
||||
scope: {
|
||||
active: '=?',
|
||||
actual: '=?',
|
||||
index: '=?'
|
||||
},
|
||||
link: function (scope, element, attrs, carouselCtrl) {
|
||||
if (!$carouselSuppressWarning) {
|
||||
$log.warn('slide is now deprecated. Use uib-slide instead.');
|
||||
}
|
||||
|
||||
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) {
|
||||
if (active) {
|
||||
carouselCtrl.select(scope);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -2,10 +2,56 @@ Carousel creates a carousel similar to bootstrap's image carousel.
|
||||
|
||||
The carousel also offers support for touchscreen devices in the form of swiping. To enable swiping, load the `ngTouch` module as a dependency.
|
||||
|
||||
Use a `<uib-carousel>` element with `<uib-slide>` elements inside it. It will automatically cycle through the slides at a given rate, and a current-index variable will be kept in sync with the currently visible slide.
|
||||
Use a `<uib-carousel>` element with `<uib-slide>` elements inside it.
|
||||
|
||||
Use the `no-wrap` attribute on a `<uib-carousel>` element to control the looping of slides; setting `no-wrap` to an expression which evaluates to a truthy value will prevent looping.
|
||||
### uib-carousel settings
|
||||
|
||||
Use the `template-url` attribute on a `<uib-carousel>` or `<uib-slide>` element to specify the url of a custom template to override the default templates.
|
||||
* `active`
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `Index of first slide`)_ -
|
||||
Index of current active slide.
|
||||
|
||||
Use the `actual` attribute on a `<uib-slide>` element to bind the slide model (or any object of interest) onto the slide directive's `$scope`, which makes it available for customization in the carousel template.
|
||||
* `interval`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `none`)_ -
|
||||
Sets an interval to cycle through the slides. You need a number bigger than 0 to make the interval work.
|
||||
|
||||
* `no-pause`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `false`)_ -
|
||||
The interval pauses on mouseover. Setting this to truthy, disables this pause.
|
||||
|
||||
* `no-transition`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `false`)_ -
|
||||
Whether to disable the transition animation between slides. Setting this to truthy, disables this transition.
|
||||
|
||||
* `no-wrap`
|
||||
<small class="badge">$</small>
|
||||
_(Default: `false`)_ -
|
||||
Disables the looping of slides. Setting `no-wrap` to an expression which evaluates to a truthy value will prevent looping.
|
||||
|
||||
* `template-url`
|
||||
_(Default: `uib/template/carousel/carousel.html`)_ -
|
||||
Add the ability to override the template used on the component.
|
||||
|
||||
### uib-slide settings
|
||||
|
||||
* `actual`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `none`)_ -
|
||||
Use this attribute to bind the slide model (or any object of interest) onto the slide scope, which makes it available for customization in the carousel template.
|
||||
|
||||
* `index`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `none`)_ -
|
||||
The index of the slide. Must be unique.
|
||||
|
||||
* `template-url`
|
||||
_(Default: `uib/template/carousel/slide.html`)_ -
|
||||
Add the ability to override the template used on the component.
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<div ng-controller="CarouselDemoCtrl">
|
||||
<div style="height: 305px">
|
||||
<uib-carousel interval="myInterval" no-wrap="noWrapSlides">
|
||||
<uib-slide ng-repeat="slide in slides" active="slide.active">
|
||||
<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 {{$index}}</h4>
|
||||
<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">
|
||||
<button type="button" class="btn btn-info" ng-click="addSlide()">Add Slide</button>
|
||||
<button type="button" class="btn btn-info" ng-click="randomize()">Randomize slides</button>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="noWrapSlides">
|
||||
|
||||
@@ -1,16 +1,57 @@
|
||||
angular.module('ui.bootstrap.demo').controller('CarouselDemoCtrl', function ($scope) {
|
||||
$scope.myInterval = 5000;
|
||||
$scope.noWrapSlides = false;
|
||||
$scope.active = 0;
|
||||
var slides = $scope.slides = [];
|
||||
var currIndex = 0;
|
||||
|
||||
$scope.addSlide = function() {
|
||||
var newWidth = 600 + slides.length + 1;
|
||||
slides.push({
|
||||
image: '//placekitten.com/' + newWidth + '/300',
|
||||
text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' ' +
|
||||
['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4]
|
||||
image: '//unsplash.it/' + newWidth + '/300',
|
||||
text: ['Nice image','Awesome photograph','That is so cool','I love that'][slides.length % 4],
|
||||
id: currIndex++
|
||||
});
|
||||
};
|
||||
for (var i=0; i<4; i++) {
|
||||
|
||||
$scope.randomize = function() {
|
||||
var indexes = generateIndexesArray();
|
||||
assignNewIndexesToSlides(indexes);
|
||||
};
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
$scope.addSlide();
|
||||
}
|
||||
|
||||
// Randomize logic below
|
||||
|
||||
function assignNewIndexesToSlides(indexes) {
|
||||
for (var i = 0, l = slides.length; i < l; i++) {
|
||||
slides[i].id = indexes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
function generateIndexesArray() {
|
||||
var indexes = [];
|
||||
for (var i = 0; i < currIndex; ++i) {
|
||||
indexes[i] = i;
|
||||
}
|
||||
return shuffle(indexes);
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/questions/962802#962890
|
||||
function shuffle(array) {
|
||||
var tmp, current, top = array.length;
|
||||
|
||||
if (top) {
|
||||
while (--top) {
|
||||
current = Math.floor(Math.random() * (top + 1));
|
||||
tmp = array[current];
|
||||
array[current] = array[top];
|
||||
array[top] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
@@ -0,0 +1,2 @@
|
||||
require('./carousel.css');
|
||||
module.exports = require('./index-nocss.js');
|
||||
+274
-272
@@ -1,26 +1,17 @@
|
||||
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('template/carousel/carousel.html', 'template/carousel/slide.html'));
|
||||
beforeEach(module('ui.bootstrap.carousel'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
beforeEach(module('uib/template/carousel/carousel.html', 'uib/template/carousel/slide.html'));
|
||||
|
||||
var $rootScope, $compile, $controller, $interval, $templateCache;
|
||||
beforeEach(inject(function(_$rootScope_, _$compile_, _$controller_, _$interval_, _$templateCache_) {
|
||||
var $rootScope, $compile, $controller, $interval, $templateCache, $timeout, $animate;
|
||||
beforeEach(inject(function(_$rootScope_, _$compile_, _$controller_, _$interval_, _$templateCache_, _$timeout_, _$animate_) {
|
||||
$rootScope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
$controller = _$controller_;
|
||||
$interval = _$interval_;
|
||||
$templateCache = _$templateCache_;
|
||||
$timeout = _$timeout_;
|
||||
$animate = _$animate_;
|
||||
}));
|
||||
|
||||
describe('basics', function() {
|
||||
@@ -28,16 +19,16 @@ describe('carousel', function() {
|
||||
beforeEach(function() {
|
||||
scope = $rootScope.$new();
|
||||
scope.slides = [
|
||||
{active:false,content:'one'},
|
||||
{active:false,content:'two'},
|
||||
{active:false,content:'three'}
|
||||
{content: 'one', index: 0},
|
||||
{content: 'two', index: 1},
|
||||
{content: 'three', index: 2}
|
||||
];
|
||||
elm = $compile(
|
||||
'<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<uib-slide ng-repeat="slide in slides" active="slide.active">' +
|
||||
'<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;
|
||||
@@ -46,10 +37,10 @@ describe('carousel', function() {
|
||||
|
||||
function testSlideActive(slideIndex) {
|
||||
for (var i = 0; i < scope.slides.length; i++) {
|
||||
if (i == slideIndex) {
|
||||
expect(scope.slides[i].active).toBe(true);
|
||||
if (i === slideIndex) {
|
||||
expect(scope.active).toBe(scope.slides[i].index);
|
||||
} else {
|
||||
expect(scope.slides[i].active).not.toBe(true);
|
||||
expect(scope.active).not.toBe(scope.slides[i].index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,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();
|
||||
|
||||
@@ -77,10 +68,9 @@ describe('carousel', function() {
|
||||
expect(slide.html()).toBe('bar');
|
||||
});
|
||||
|
||||
it('should set the selected slide to active = true', function() {
|
||||
expect(scope.slides[0].content).toBe('one');
|
||||
it('should be able to select a slide via model changes', function() {
|
||||
testSlideActive(0);
|
||||
scope.$apply('slides[1].active=true');
|
||||
scope.$apply('active=1');
|
||||
testSlideActive(1);
|
||||
});
|
||||
|
||||
@@ -99,59 +89,58 @@ describe('carousel', function() {
|
||||
|
||||
it('should stop cycling slides forward when noWrap is truthy', function () {
|
||||
elm = $compile(
|
||||
'<uib-carousel interval="interval" no-wrap="noWrap">' +
|
||||
'<uib-slide ng-repeat="slide in slides" active="slide.active">' +
|
||||
'{{slide.content}}' +
|
||||
'</uib-slide>' +
|
||||
'</uib-carousel>'
|
||||
)(scope);
|
||||
'<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}}' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
|
||||
scope.noWrap = true;
|
||||
scope.$apply();
|
||||
|
||||
scope = elm.isolateScope();
|
||||
spyOn(scope, 'pause');
|
||||
var $scope = elm.isolateScope();
|
||||
spyOn($scope, 'pause');
|
||||
|
||||
for (var i = 0; i < scope.slides.length - 1; ++i) {
|
||||
scope.next();
|
||||
}
|
||||
testSlideActive(scope.slides.length - 1);
|
||||
scope.next();
|
||||
testSlideActive(scope.slides.length - 1);
|
||||
expect(scope.pause).toHaveBeenCalled();
|
||||
scope.active = $scope.slides.length - 1;
|
||||
scope.$apply();
|
||||
testSlideActive($scope.slides.length - 1);
|
||||
$scope.next();
|
||||
testSlideActive($scope.slides.length - 1);
|
||||
expect($scope.pause).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should stop cycling slides backward when noWrap is truthy', function () {
|
||||
elm = $compile(
|
||||
'<uib-carousel interval="interval" no-wrap="noWrap">' +
|
||||
'<uib-slide ng-repeat="slide in slides" active="slide.active">' +
|
||||
'{{slide.content}}' +
|
||||
'</uib-slide>' +
|
||||
'</uib-carousel>'
|
||||
)(scope);
|
||||
'<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}}' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
|
||||
scope.noWrap = true;
|
||||
scope.$apply();
|
||||
|
||||
scope = elm.isolateScope();
|
||||
spyOn(scope, 'pause');
|
||||
var $scope = elm.isolateScope();
|
||||
spyOn($scope, 'pause');
|
||||
|
||||
testSlideActive(0);
|
||||
scope.prev();
|
||||
$scope.prev();
|
||||
testSlideActive(0);
|
||||
expect(scope.pause).toHaveBeenCalled();
|
||||
expect($scope.pause).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should hide navigation when only one slide', function () {
|
||||
scope.slides = [{active:false,content:'one'}];
|
||||
scope.$apply();
|
||||
elm = $compile(
|
||||
'<uib-carousel interval="interval" no-transition="true">' +
|
||||
'<uib-slide ng-repeat="slide in slides" active="slide.active">' +
|
||||
'{{slide.content}}' +
|
||||
'</uib-slide>' +
|
||||
'</uib-carousel>'
|
||||
)(scope);
|
||||
'<div uib-carousel active="active" interval="interval" no-transition="true">' +
|
||||
'<div uib-slide ng-repeat="slide in slides" index="$index">' +
|
||||
'{{slide.content}}' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
var indicators = elm.find('ol.carousel-indicators > li');
|
||||
expect(indicators.length).toBe(0);
|
||||
|
||||
@@ -162,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);
|
||||
@@ -195,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();
|
||||
@@ -236,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');
|
||||
@@ -289,22 +296,28 @@ describe('carousel', function() {
|
||||
});
|
||||
|
||||
it('should remove slide from dom and change active slide', function() {
|
||||
scope.$apply('slides[2].active = true');
|
||||
scope.$apply('active = 2');
|
||||
testSlideActive(2);
|
||||
scope.$apply('slides.splice(0,1)');
|
||||
scope.$apply('slides.splice(2,1)');
|
||||
$timeout.flush(0);
|
||||
expect(elm.find('div.item').length).toBe(2);
|
||||
testSlideActive(1);
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(0);
|
||||
scope.$apply('slides.splice(1,1)');
|
||||
$timeout.flush(0);
|
||||
expect(elm.find('div.item').length).toBe(1);
|
||||
testSlideActive(0);
|
||||
});
|
||||
|
||||
it('should change dom when you reassign ng-repeat slides array', function() {
|
||||
scope.slides = [{content:'new1'},{content:'new2'},{content:'new3'}];
|
||||
scope.slides = [
|
||||
{content:'new1', index: 4},
|
||||
{content:'new2', index: 5},
|
||||
{content:'new3', index: 6}
|
||||
];
|
||||
scope.$apply();
|
||||
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');
|
||||
@@ -326,6 +339,39 @@ describe('carousel', function() {
|
||||
testSlideActive(1);
|
||||
});
|
||||
|
||||
it('should buffer the slides if transition is clicked and only transition to the last requested', function() {
|
||||
var carouselScope = elm.children().scope();
|
||||
|
||||
testSlideActive(0);
|
||||
carouselScope.$currentTransition = null;
|
||||
carouselScope.select(carouselScope.slides[1]);
|
||||
$animate.flush();
|
||||
|
||||
testSlideActive(1);
|
||||
|
||||
carouselScope.$currentTransition = true;
|
||||
carouselScope.select(carouselScope.slides[2]);
|
||||
scope.$apply();
|
||||
|
||||
testSlideActive(1);
|
||||
|
||||
carouselScope.select(carouselScope.slides[0]);
|
||||
scope.$apply();
|
||||
|
||||
testSlideActive(1);
|
||||
|
||||
carouselScope.$currentTransition = null;
|
||||
$interval.flush(scope.interval);
|
||||
$animate.flush();
|
||||
|
||||
testSlideActive(2);
|
||||
|
||||
$interval.flush(scope.interval);
|
||||
$animate.flush();
|
||||
|
||||
testSlideActive(0);
|
||||
});
|
||||
|
||||
it('issue 1414 - should not continue running timers after scope is destroyed', function() {
|
||||
testSlideActive(0);
|
||||
$interval.flush(scope.interval);
|
||||
@@ -342,7 +388,11 @@ describe('carousel', function() {
|
||||
it('issue 4390 - should reset the currentTransition if there are no slides', function() {
|
||||
var carouselScope = elm.children().scope();
|
||||
var next = elm.find('a.right');
|
||||
scope.slides = [{content:'new1'},{content:'new2'},{content:'new3'}];
|
||||
scope.slides = [
|
||||
{content:'new1', index: 1},
|
||||
{content:'new2', index: 2},
|
||||
{content:'new3', index: 3}
|
||||
];
|
||||
scope.$apply();
|
||||
|
||||
testSlideActive(0);
|
||||
@@ -353,154 +403,161 @@ describe('carousel', function() {
|
||||
|
||||
expect(carouselScope.$currentTransition).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('slide order', function() {
|
||||
beforeEach(function() {
|
||||
scope.slides = [
|
||||
{active:false,content:'one', id:1},
|
||||
{active:false,content:'two', id:2},
|
||||
{active:false,content:'three', id:3}
|
||||
];
|
||||
elm = $compile(
|
||||
'<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<uib-slide ng-repeat="slide in slides | orderBy: \'id\' " active="slide.active" index="$index">' +
|
||||
'{{slide.content}}' +
|
||||
'</uib-slide>' +
|
||||
'</uib-carousel>'
|
||||
)(scope);
|
||||
scope.$apply();
|
||||
scope.slides[0].id = 3;
|
||||
scope.slides[1].id = 1;
|
||||
scope.slides[2].id = 2;
|
||||
scope.$apply();
|
||||
});
|
||||
|
||||
it('should change dom when an order of the slides was changed', function() {
|
||||
testSlideActive(0);
|
||||
var contents = elm.find('div.item');
|
||||
expect(contents.length).toBe(3);
|
||||
expect(contents.eq(0).text()).toBe('two');
|
||||
expect(contents.eq(1).text()).toBe('three');
|
||||
expect(contents.eq(2).text()).toBe('one');
|
||||
});
|
||||
|
||||
it('should select next after order change', function() {
|
||||
testSlideActive(0);
|
||||
var next = elm.find('a.right');
|
||||
next.click();
|
||||
testSlideActive(1);
|
||||
});
|
||||
|
||||
it('should select prev after order change', function() {
|
||||
testSlideActive(0);
|
||||
var prev = elm.find('a.left');
|
||||
prev.click();
|
||||
testSlideActive(2);
|
||||
});
|
||||
|
||||
it('should add slide in the specified position', function() {
|
||||
testSlideActive(0);
|
||||
scope.slides[2].id = 4;
|
||||
scope.slides.push({active:false,content:'four', id:2});
|
||||
scope.$apply();
|
||||
var contents = elm.find('div.item');
|
||||
expect(contents.length).toBe(4);
|
||||
expect(contents.eq(0).text()).toBe('two');
|
||||
expect(contents.eq(1).text()).toBe('four');
|
||||
expect(contents.eq(2).text()).toBe('one');
|
||||
expect(contents.eq(3).text()).toBe('three');
|
||||
});
|
||||
|
||||
it('should remove slide after order change', function() {
|
||||
testSlideActive(0);
|
||||
scope.slides.splice(1, 1);
|
||||
scope.$apply();
|
||||
var contents = elm.find('div.item');
|
||||
expect(contents.length).toBe(2);
|
||||
expect(contents.eq(0).text()).toBe('three');
|
||||
expect(contents.eq(1).text()).toBe('one');
|
||||
});
|
||||
|
||||
describe('slide order', function() {
|
||||
var elm, scope;
|
||||
beforeEach(function() {
|
||||
scope = $rootScope.$new();
|
||||
scope.slides = [
|
||||
{content: 'one', id: 3},
|
||||
{content: 'two', id: 1},
|
||||
{content: 'three', id: 2}
|
||||
];
|
||||
elm = $compile(
|
||||
'<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}}' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
scope.$apply();
|
||||
});
|
||||
|
||||
function testSlideActive(slideIndex) {
|
||||
for (var i = 0; i < scope.slides.length; i++) {
|
||||
if (i === slideIndex) {
|
||||
expect(scope.active).toBe(scope.slides[i].id);
|
||||
} else {
|
||||
expect(scope.active).not.toBe(scope.slides[i].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it('should change dom when the order of the slides changes', function() {
|
||||
scope.slides[0].id = 3;
|
||||
scope.slides[1].id = 2;
|
||||
scope.slides[2].id = 1;
|
||||
scope.$apply();
|
||||
var contents = elm.find('div.item [ng-transclude]');
|
||||
expect(contents.length).toBe(3);
|
||||
expect(contents.eq(0).text()).toBe('three');
|
||||
expect(contents.eq(1).text()).toBe('two');
|
||||
expect(contents.eq(2).text()).toBe('one');
|
||||
});
|
||||
|
||||
it('should select next after order change', function() {
|
||||
testSlideActive(1);
|
||||
var next = elm.find('a.right');
|
||||
next.click();
|
||||
testSlideActive(2);
|
||||
});
|
||||
|
||||
it('should select prev after order change', function() {
|
||||
testSlideActive(1);
|
||||
var prev = elm.find('a.left');
|
||||
prev.click();
|
||||
testSlideActive(0);
|
||||
});
|
||||
|
||||
it('should add slide in the specified position', function() {
|
||||
testSlideActive(1);
|
||||
scope.slides[2].id = 4;
|
||||
scope.slides.push({content:'four', id: 5});
|
||||
scope.$apply();
|
||||
var contents = elm.find('div.item [ng-transclude]');
|
||||
expect(contents.length).toBe(4);
|
||||
expect(contents.eq(0).text()).toBe('two');
|
||||
expect(contents.eq(1).text()).toBe('one');
|
||||
expect(contents.eq(2).text()).toBe('three');
|
||||
expect(contents.eq(3).text()).toBe('four');
|
||||
});
|
||||
|
||||
it('should remove slide after order change', function() {
|
||||
testSlideActive(1);
|
||||
scope.slides.splice(1, 1);
|
||||
scope.$apply();
|
||||
var contents = elm.find('div.item [ng-transclude]');
|
||||
expect(contents.length).toBe(2);
|
||||
expect(contents.eq(0).text()).toBe('three');
|
||||
expect(contents.eq(1).text()).toBe('one');
|
||||
});
|
||||
});
|
||||
|
||||
describe('controller', function() {
|
||||
var scope, ctrl;
|
||||
//create an array of slides and add to the scope
|
||||
var slides = [{'content':1},{'content':2},{'content':3},{'content':4}];
|
||||
var slides = [
|
||||
{'content': 1, index: 0},
|
||||
{'content': 2, index: 1},
|
||||
{'content': 3, index: 2},
|
||||
{'content': 4, index: 3}
|
||||
];
|
||||
|
||||
beforeEach(function() {
|
||||
scope = $rootScope.$new();
|
||||
scope.noWrap = angular.noop;
|
||||
ctrl = $controller('UibCarouselController', {$scope: scope, $element: angular.element('<div></div>')});
|
||||
for(var i = 0;i < slides.length;i++){
|
||||
for (var i = 0; i < slides.length; i++) {
|
||||
ctrl.addSlide(slides[i]);
|
||||
}
|
||||
});
|
||||
|
||||
describe('addSlide', function() {
|
||||
it('should set first slide to active = true and the rest to false', function() {
|
||||
angular.forEach(ctrl.slides, function(slide, i) {
|
||||
if (i !== 0) {
|
||||
expect(slide.active).not.toBe(true);
|
||||
} else {
|
||||
expect(slide.active).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should add new slide and change active to true if active is true on the added slide', function() {
|
||||
var newSlide = {active: true};
|
||||
expect(ctrl.slides.length).toBe(4);
|
||||
ctrl.addSlide(newSlide);
|
||||
expect(ctrl.slides.length).toBe(5);
|
||||
expect(ctrl.slides[4].active).toBe(true);
|
||||
expect(ctrl.slides[0].active).toBe(false);
|
||||
});
|
||||
|
||||
it('should add a new slide and not change the active slide', function() {
|
||||
var newSlide = {active: false};
|
||||
expect(ctrl.slides.length).toBe(4);
|
||||
ctrl.addSlide(newSlide);
|
||||
expect(ctrl.slides.length).toBe(5);
|
||||
expect(ctrl.slides[4].active).toBe(false);
|
||||
expect(ctrl.slides[0].active).toBe(true);
|
||||
});
|
||||
|
||||
it('should remove slide and change active slide if needed', function() {
|
||||
expect(ctrl.slides.length).toBe(4);
|
||||
ctrl.removeSlide(ctrl.slides[0]);
|
||||
expect(ctrl.slides.length).toBe(3);
|
||||
expect(ctrl.currentSlide).toBe(ctrl.slides[0]);
|
||||
ctrl.select(ctrl.slides[2]);
|
||||
ctrl.removeSlide(ctrl.slides[2]);
|
||||
expect(ctrl.slides.length).toBe(2);
|
||||
expect(ctrl.currentSlide).toBe(ctrl.slides[1]);
|
||||
ctrl.removeSlide(ctrl.slides[0]);
|
||||
expect(ctrl.slides.length).toBe(1);
|
||||
expect(ctrl.currentSlide).toBe(ctrl.slides[0]);
|
||||
});
|
||||
|
||||
it('issue 1414 - should not continue running timers after scope is destroyed', function() {
|
||||
spyOn(scope, 'next').and.callThrough();
|
||||
scope.interval = 2000;
|
||||
scope.$digest();
|
||||
|
||||
$interval.flush(scope.interval);
|
||||
expect(scope.next.calls.count()).toBe(1);
|
||||
|
||||
scope.$destroy();
|
||||
|
||||
$interval.flush(scope.interval);
|
||||
expect(scope.next.calls.count()).toBe(1);
|
||||
it('should set first slide to active = true and the rest to false', function() {
|
||||
angular.forEach(ctrl.slides, function(slide, i) {
|
||||
if (i !== 0) {
|
||||
expect(slide.slide.active).not.toBe(true);
|
||||
} else {
|
||||
expect(slide.slide.active).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a new slide and not change the active slide', function() {
|
||||
var newSlide = {active: false, index: 4};
|
||||
expect(ctrl.slides.length).toBe(4);
|
||||
ctrl.addSlide(newSlide);
|
||||
expect(ctrl.slides.length).toBe(5);
|
||||
expect(ctrl.slides[4].slide.active).toBe(false);
|
||||
expect(ctrl.slides[0].slide.active).toBe(true);
|
||||
});
|
||||
|
||||
it('should remove slide and change active slide if needed', function() {
|
||||
expect(ctrl.slides.length).toBe(4);
|
||||
ctrl.removeSlide(ctrl.slides[0].slide);
|
||||
$timeout.flush(0);
|
||||
expect(ctrl.slides.length).toBe(3);
|
||||
expect(scope.active).toBe(1);
|
||||
ctrl.select(ctrl.slides[2]);
|
||||
ctrl.removeSlide(ctrl.slides[2].slide);
|
||||
$timeout.flush(0);
|
||||
expect(ctrl.slides.length).toBe(2);
|
||||
expect(scope.active).toBe(2);
|
||||
ctrl.removeSlide(ctrl.slides[0].slide);
|
||||
$timeout.flush(0);
|
||||
expect(ctrl.slides.length).toBe(1);
|
||||
expect(scope.active).toBe(1);
|
||||
});
|
||||
|
||||
it('issue 1414 - should not continue running timers after scope is destroyed', function() {
|
||||
spyOn(scope, 'next');
|
||||
scope.interval = 2000;
|
||||
scope.$digest();
|
||||
|
||||
$interval.flush(scope.interval);
|
||||
expect(scope.next.calls.count()).toBe(1);
|
||||
|
||||
scope.$destroy();
|
||||
|
||||
$interval.flush(scope.interval);
|
||||
expect(scope.next.calls.count()).toBe(1);
|
||||
});
|
||||
|
||||
it('should be exposed in the template', inject(function($templateCache) {
|
||||
$templateCache.put('template/carousel/carousel.html', '<div>{{carousel.text}}</div>');
|
||||
$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');
|
||||
@@ -510,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>');
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -522,73 +579,18 @@ describe('carousel', function() {
|
||||
{active:false,content:'three'}
|
||||
];
|
||||
var elm = $compile(
|
||||
'<uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<uib-slide ng-repeat="slide in slides" active="slide.active" actual="slide">' +
|
||||
'<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();
|
||||
|
||||
var ctrl = elm.controller('uibCarousel');
|
||||
|
||||
expect(angular.equals(ctrl.slides.map(function(slide) {
|
||||
return slide.actual;
|
||||
return slide.slide.actual;
|
||||
}), scope.slides)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('carousel deprecation', function() {
|
||||
beforeEach(module('ui.bootstrap.carousel'));
|
||||
beforeEach(module('template/carousel/carousel.html', 'template/carousel/slide.html'));
|
||||
|
||||
it('should suppress warning', function() {
|
||||
module(function($provide) {
|
||||
$provide.value('$carouselSuppressWarning', true);
|
||||
});
|
||||
|
||||
inject(function($compile, $log, $rootScope) {
|
||||
spyOn($log, 'warn');
|
||||
|
||||
var element = '<carousel interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<slide ng-repeat="slide in slides" active="slide.active">' +
|
||||
'{{slide.content}}' +
|
||||
'</slide>' +
|
||||
'</carousel>';
|
||||
element = $compile(element)($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($log.warn.calls.count()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should give warning by default', inject(function($compile, $log, $rootScope) {
|
||||
spyOn($log, 'warn');
|
||||
|
||||
var element = '<carousel interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<slide ng-repeat="slide in slides" active="slide.active">' +
|
||||
'{{slide.content}}' +
|
||||
'</slide>' +
|
||||
'</carousel>';
|
||||
element = $compile(element)($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($log.warn.calls.count()).toBe(2);
|
||||
expect($log.warn.calls.argsFor(0)).toEqual(['CarouselController is now deprecated. Use UibCarouselController instead.']);
|
||||
expect($log.warn.calls.argsFor(1)).toEqual(['carousel is now deprecated. Use uib-carousel instead.']);
|
||||
}));
|
||||
|
||||
it('should give warning by default for slider', inject(function($compile, $log, $rootScope) {
|
||||
spyOn($log, 'warn');
|
||||
|
||||
var element = '<carousel interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<slide></slide>' +
|
||||
'</carousel>';
|
||||
element = $compile(element)($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($log.warn.calls.count()).toBe(3);
|
||||
expect($log.warn.calls.argsFor(0)).toEqual(['CarouselController is now deprecated. Use UibCarouselController instead.']);
|
||||
expect($log.warn.calls.argsFor(1)).toEqual(['slide is now deprecated. Use uib-slide instead.']);
|
||||
expect($log.warn.calls.argsFor(2)).toEqual(['carousel is now deprecated. Use uib-carousel instead.']);
|
||||
}));
|
||||
});
|
||||
|
||||
+95
-121
@@ -1,32 +1,85 @@
|
||||
angular.module('ui.bootstrap.collapse', [])
|
||||
|
||||
.directive('uibCollapse', ['$animate', '$injector', function($animate, $injector) {
|
||||
.directive('uibCollapse', ['$animate', '$q', '$parse', '$injector', function($animate, $q, $parse, $injector) {
|
||||
var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
|
||||
return {
|
||||
link: function(scope, element, attrs) {
|
||||
function expand() {
|
||||
element.removeClass('collapse')
|
||||
.addClass('collapsing')
|
||||
.attr('aria-expanded', true)
|
||||
.attr('aria-hidden', false);
|
||||
var expandingExpr = $parse(attrs.expanding),
|
||||
expandedExpr = $parse(attrs.expanded),
|
||||
collapsingExpr = $parse(attrs.collapsing),
|
||||
collapsedExpr = $parse(attrs.collapsed),
|
||||
horizontal = false,
|
||||
css = {},
|
||||
cssTo = {};
|
||||
|
||||
if ($animateCss) {
|
||||
$animateCss(element, {
|
||||
addClass: 'in',
|
||||
easing: 'ease',
|
||||
to: { height: element[0].scrollHeight + 'px' }
|
||||
}).start().finally(expandDone);
|
||||
init();
|
||||
|
||||
function init() {
|
||||
horizontal = !!('horizontal' in attrs);
|
||||
if (horizontal) {
|
||||
css = {
|
||||
width: ''
|
||||
};
|
||||
cssTo = {width: '0'};
|
||||
} else {
|
||||
$animate.addClass(element, 'in', {
|
||||
to: { height: element[0].scrollHeight + 'px' }
|
||||
}).then(expandDone);
|
||||
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() {
|
||||
if (element.hasClass('collapse') && element.hasClass('in')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$q.resolve(expandingExpr(scope))
|
||||
.then(function() {
|
||||
element.removeClass('collapse')
|
||||
.addClass('collapsing')
|
||||
.attr('aria-expanded', true)
|
||||
.attr('aria-hidden', false);
|
||||
|
||||
if ($animateCss) {
|
||||
$animateCss(element, {
|
||||
addClass: 'in',
|
||||
easing: 'ease',
|
||||
css: {
|
||||
overflow: 'hidden'
|
||||
},
|
||||
to: getScrollFromElement(element[0])
|
||||
}).start()['finally'](expandDone);
|
||||
} else {
|
||||
$animate.addClass(element, 'in', {
|
||||
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);
|
||||
}
|
||||
|
||||
function collapse() {
|
||||
@@ -34,34 +87,38 @@ angular.module('ui.bootstrap.collapse', [])
|
||||
return collapseDone();
|
||||
}
|
||||
|
||||
element
|
||||
// IMPORTANT: The height must be set before adding "collapsing" class.
|
||||
// Otherwise, the browser attempts to animate from height 0 (in
|
||||
// collapsing class) to the given height here.
|
||||
.css({height: element[0].scrollHeight + 'px'})
|
||||
// initially all panel collapse have the collapse class, this removal
|
||||
// prevents the animation from jumping to collapsed state
|
||||
.removeClass('collapse')
|
||||
.addClass('collapsing')
|
||||
.attr('aria-expanded', false)
|
||||
.attr('aria-hidden', true);
|
||||
$q.resolve(collapsingExpr(scope))
|
||||
.then(function() {
|
||||
element
|
||||
// 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')
|
||||
.addClass('collapsing')
|
||||
.attr('aria-expanded', false)
|
||||
.attr('aria-hidden', true);
|
||||
|
||||
if ($animateCss) {
|
||||
$animateCss(element, {
|
||||
removeClass: 'in',
|
||||
to: {height: '0'}
|
||||
}).start().finally(collapseDone);
|
||||
} else {
|
||||
$animate.removeClass(element, 'in', {
|
||||
to: {height: '0'}
|
||||
}).then(collapseDone);
|
||||
}
|
||||
if ($animateCss) {
|
||||
$animateCss(element, {
|
||||
removeClass: 'in',
|
||||
to: cssTo
|
||||
}).start()['finally'](collapseDone);
|
||||
} else {
|
||||
$animate.removeClass(element, 'in', {
|
||||
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);
|
||||
}
|
||||
|
||||
scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
|
||||
@@ -74,86 +131,3 @@ angular.module('ui.bootstrap.collapse', [])
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
/* Deprecated collapse below */
|
||||
|
||||
angular.module('ui.bootstrap.collapse')
|
||||
|
||||
.value('$collapseSuppressWarning', false)
|
||||
|
||||
.directive('collapse', ['$animate', '$injector', '$log', '$collapseSuppressWarning', function($animate, $injector, $log, $collapseSuppressWarning) {
|
||||
var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
|
||||
return {
|
||||
link: function(scope, element, attrs) {
|
||||
if (!$collapseSuppressWarning) {
|
||||
$log.warn('collapse is now deprecated. Use uib-collapse instead.');
|
||||
}
|
||||
|
||||
function expand() {
|
||||
element.removeClass('collapse')
|
||||
.addClass('collapsing')
|
||||
.attr('aria-expanded', true)
|
||||
.attr('aria-hidden', false);
|
||||
|
||||
if ($animateCss) {
|
||||
$animateCss(element, {
|
||||
easing: 'ease',
|
||||
to: { height: element[0].scrollHeight + 'px' }
|
||||
}).start().done(expandDone);
|
||||
} else {
|
||||
$animate.animate(element, {}, {
|
||||
height: element[0].scrollHeight + 'px'
|
||||
}).then(expandDone);
|
||||
}
|
||||
}
|
||||
|
||||
function expandDone() {
|
||||
element.removeClass('collapsing')
|
||||
.addClass('collapse in')
|
||||
.css({height: 'auto'});
|
||||
}
|
||||
|
||||
function collapse() {
|
||||
if (!element.hasClass('collapse') && !element.hasClass('in')) {
|
||||
return collapseDone();
|
||||
}
|
||||
|
||||
element
|
||||
// IMPORTANT: The height must be set before adding "collapsing" class.
|
||||
// Otherwise, the browser attempts to animate from height 0 (in
|
||||
// collapsing class) to the given height here.
|
||||
.css({height: element[0].scrollHeight + 'px'})
|
||||
// initially all panel collapse have the collapse class, this removal
|
||||
// prevents the animation from jumping to collapsed state
|
||||
.removeClass('collapse in')
|
||||
.addClass('collapsing')
|
||||
.attr('aria-expanded', false)
|
||||
.attr('aria-hidden', true);
|
||||
|
||||
if ($animateCss) {
|
||||
$animateCss(element, {
|
||||
to: {height: '0'}
|
||||
}).start().done(collapseDone);
|
||||
} else {
|
||||
$animate.animate(element, {}, {
|
||||
height: '0'
|
||||
}).then(collapseDone);
|
||||
}
|
||||
}
|
||||
|
||||
function collapseDone() {
|
||||
element.css({height: '0'}); // Required so that collapse works when animation is disabled
|
||||
element.removeClass('collapsing')
|
||||
.addClass('collapse');
|
||||
}
|
||||
|
||||
scope.$watch(attrs.collapse, function(shouldCollapse) {
|
||||
if (shouldCollapse) {
|
||||
collapse();
|
||||
} else {
|
||||
expand();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -2,7 +2,36 @@
|
||||
|
||||
### uib-collapse settings
|
||||
|
||||
* `collapsed()`
|
||||
<small class="badge">$</small> -
|
||||
An optional expression called after the element finished collapsing.
|
||||
|
||||
* `collapsing()`
|
||||
<small class="badge">$</small> -
|
||||
An optional expression called before the element begins collapsing.
|
||||
If the expression returns a promise, animation won't start until the promise resolves.
|
||||
If the returned promise is rejected, collapsing will be cancelled.
|
||||
|
||||
* `expanded()`
|
||||
<small class="badge">$</small> -
|
||||
An optional expression called after the element finished expanding.
|
||||
|
||||
* `expanding()`
|
||||
<small class="badge">$</small> -
|
||||
An optional expression called before the element begins expanding.
|
||||
If the expression returns a promise, animation won't start until the promise resolves.
|
||||
If the returned promise is rejected, expanding will be cancelled.
|
||||
|
||||
* `uib-collapse`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(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,7 @@
|
||||
require('./collapse');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.collapse';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.collapse']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
@@ -1,16 +1,24 @@
|
||||
describe('collapse directive', function() {
|
||||
var element, scope, $compile, $animate;
|
||||
var element, compileFn, scope, $compile, $animate, $q;
|
||||
|
||||
beforeEach(module('ui.bootstrap.collapse'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
beforeEach(inject(function(_$rootScope_, _$compile_, _$animate_) {
|
||||
beforeEach(inject(function(_$rootScope_, _$compile_, _$animate_, _$q_) {
|
||||
scope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
$animate = _$animate_;
|
||||
$q = _$q_;
|
||||
}));
|
||||
|
||||
beforeEach(function() {
|
||||
element = $compile('<div uib-collapse="isCollapsed">Some Content</div>')(scope);
|
||||
element = angular.element(
|
||||
'<div uib-collapse="isCollapsed" '
|
||||
+ 'expanding="expanding()" '
|
||||
+ 'expanded="expanded()" '
|
||||
+ 'collapsing="collapsing()" '
|
||||
+ 'collapsed="collapsed()">'
|
||||
+ 'Some Content</div>');
|
||||
compileFn = $compile(element);
|
||||
angular.element(document.body).append(element);
|
||||
});
|
||||
|
||||
@@ -18,62 +26,121 @@ describe('collapse directive', function() {
|
||||
element.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;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
expect(element.height()).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;
|
||||
compileFn(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;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
initCallbacks();
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(element.height()).toBe(0);
|
||||
assertCallbacks({ collapsing: true, collapsed: true });
|
||||
});
|
||||
|
||||
it('should be shown on initialization if isCollapsed = false', function() {
|
||||
it('should show after toggled from collapsed', function() {
|
||||
initCallbacks();
|
||||
scope.isCollapsed = true;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
expect(element.height()).toBe(0);
|
||||
assertCallbacks({ collapsed: true });
|
||||
scope.collapsed.calls.reset();
|
||||
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(element.height()).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;
|
||||
compileFn(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;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
initCallbacks();
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(element.height()).not.toBe(0);
|
||||
assertCallbacks({ expanding: true, expanded: true });
|
||||
});
|
||||
|
||||
it('should expand if isCollapsed = true on subsequent uses', function() {
|
||||
it('should collapse if isCollapsed = true on subsequent uses', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
initCallbacks();
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(element.height()).toBe(0);
|
||||
assertCallbacks({ collapsing: true, collapsed: true });
|
||||
});
|
||||
|
||||
it('should change aria-expanded attribute', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(element.attr('aria-expanded')).toBe('true');
|
||||
|
||||
scope.isCollapsed = true;
|
||||
@@ -84,8 +151,8 @@ describe('collapse directive', function() {
|
||||
|
||||
it('should change aria-hidden attribute', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(element.attr('aria-hidden')).toBe('false');
|
||||
|
||||
scope.isCollapsed = true;
|
||||
@@ -111,7 +178,6 @@ describe('collapse directive', function() {
|
||||
scope.exp = false;
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
var collapseHeight = element.height();
|
||||
scope.exp = true;
|
||||
scope.$digest();
|
||||
@@ -122,44 +188,101 @@ describe('collapse directive', function() {
|
||||
scope.exp = true;
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
var collapseHeight = element.height();
|
||||
scope.exp = false;
|
||||
scope.$digest();
|
||||
expect(element.height()).toBeLessThan(collapseHeight);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* Deprecation tests below */
|
||||
describe('expanding callback returning a promise', function() {
|
||||
var defer, collapsedHeight;
|
||||
|
||||
describe('collapse deprecation', function() {
|
||||
beforeEach(module('ui.bootstrap.collapse'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
beforeEach(function() {
|
||||
defer = $q.defer();
|
||||
|
||||
it('should suppress warning', function() {
|
||||
module(function($provide) {
|
||||
$provide.value('$collapseSuppressWarning', true);
|
||||
scope.isCollapsed = true;
|
||||
scope.expanding = function() {
|
||||
return defer.promise;
|
||||
};
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
collapsedHeight = element.height();
|
||||
|
||||
// set flag to expand ...
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
|
||||
// ... shouldn't expand yet ...
|
||||
expect(element.attr('aria-expanded')).not.toBe('true');
|
||||
expect(element.height()).toBe(collapsedHeight);
|
||||
});
|
||||
|
||||
inject(function($compile, $log, $rootScope) {
|
||||
spyOn($log, 'warn');
|
||||
it('should wait for it to resolve before animating', function() {
|
||||
defer.resolve();
|
||||
|
||||
var element = $compile('<div collapse="isCollapsed">Some Content</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
// should now expand
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
|
||||
expect($log.warn.calls.count()).toBe(0);
|
||||
expect(element.attr('aria-expanded')).toBe('true');
|
||||
expect(element.height()).toBeGreaterThan(collapsedHeight);
|
||||
});
|
||||
|
||||
it('should not animate if it rejects', function() {
|
||||
defer.reject();
|
||||
|
||||
// should NOT expand
|
||||
scope.$digest();
|
||||
|
||||
expect(element.attr('aria-expanded')).not.toBe('true');
|
||||
expect(element.height()).toBe(collapsedHeight);
|
||||
});
|
||||
});
|
||||
|
||||
it('should give warning by default', inject(function($compile, $log, $rootScope) {
|
||||
spyOn($log, 'warn');
|
||||
describe('collapsing callback returning a promise', function() {
|
||||
var defer, expandedHeight;
|
||||
|
||||
var element = $compile('<div collapse="isCollapsed">Some Content</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
beforeEach(function() {
|
||||
defer = $q.defer();
|
||||
scope.isCollapsed = false;
|
||||
scope.collapsing = function() {
|
||||
return defer.promise;
|
||||
};
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
|
||||
expect($log.warn.calls.count()).toBe(1);
|
||||
expect($log.warn.calls.argsFor(0)).toEqual(['collapse is now deprecated. Use uib-collapse instead.']);
|
||||
expandedHeight = element.height();
|
||||
|
||||
// set flag to collapse ...
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
|
||||
// ... but it shouldn't collapse yet ...
|
||||
expect(element.attr('aria-expanded')).not.toBe('false');
|
||||
expect(element.height()).toBe(expandedHeight);
|
||||
});
|
||||
|
||||
it('should wait for it to resolve before animating', function() {
|
||||
defer.resolve();
|
||||
|
||||
// should now collapse
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
|
||||
expect(element.attr('aria-expanded')).toBe('false');
|
||||
expect(element.height()).toBeLessThan(expandedHeight);
|
||||
});
|
||||
|
||||
it('should not animate if it rejects', function() {
|
||||
defer.reject();
|
||||
|
||||
// should NOT collapse
|
||||
scope.$digest();
|
||||
|
||||
expect(element.attr('aria-expanded')).not.toBe('false');
|
||||
expect(element.height()).toBe(expandedHeight);
|
||||
});
|
||||
});
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
+377
-71
@@ -1,6 +1,6 @@
|
||||
angular.module('ui.bootstrap.dateparser', [])
|
||||
|
||||
.service('uibDateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, 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;
|
||||
|
||||
@@ -11,87 +11,167 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
localeId = $locale.id;
|
||||
|
||||
this.parsers = {};
|
||||
this.formatters = {};
|
||||
|
||||
formatCodeToRegex = {
|
||||
'yyyy': {
|
||||
formatCodeToRegex = [
|
||||
{
|
||||
key: 'yyyy',
|
||||
regex: '\\d{4}',
|
||||
apply: function(value) { this.year = +value; }
|
||||
apply: function(value) { this.year = +value; },
|
||||
formatter: function(date) {
|
||||
var _date = new Date();
|
||||
_date.setFullYear(Math.abs(date.getFullYear()));
|
||||
return dateFilter(_date, 'yyyy');
|
||||
}
|
||||
},
|
||||
'yy': {
|
||||
{
|
||||
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()));
|
||||
return dateFilter(_date, 'yy');
|
||||
}
|
||||
},
|
||||
'y': {
|
||||
{
|
||||
key: 'y',
|
||||
regex: '\\d{1,4}',
|
||||
apply: function(value) { this.year = +value; }
|
||||
apply: function(value) { this.year = +value; },
|
||||
formatter: function(date) {
|
||||
var _date = new Date();
|
||||
_date.setFullYear(Math.abs(date.getFullYear()));
|
||||
return dateFilter(_date, 'y');
|
||||
}
|
||||
},
|
||||
'MMMM': {
|
||||
{
|
||||
key: 'M!',
|
||||
regex: '0?[1-9]|1[0-2]',
|
||||
apply: function(value) { this.month = value - 1; },
|
||||
formatter: function(date) {
|
||||
var value = date.getMonth();
|
||||
if (/^[0-9]$/.test(value)) {
|
||||
return dateFilter(date, 'MM');
|
||||
}
|
||||
|
||||
return dateFilter(date, 'M');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'MMMM',
|
||||
regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
|
||||
apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
|
||||
apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); },
|
||||
formatter: function(date) { return dateFilter(date, 'MMMM'); }
|
||||
},
|
||||
'MMM': {
|
||||
{
|
||||
key: 'MMM',
|
||||
regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
|
||||
apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
|
||||
apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); },
|
||||
formatter: function(date) { return dateFilter(date, 'MMM'); }
|
||||
},
|
||||
'MM': {
|
||||
{
|
||||
key: 'MM',
|
||||
regex: '0[1-9]|1[0-2]',
|
||||
apply: function(value) { this.month = value - 1; }
|
||||
apply: function(value) { this.month = value - 1; },
|
||||
formatter: function(date) { return dateFilter(date, 'MM'); }
|
||||
},
|
||||
'M': {
|
||||
{
|
||||
key: 'M',
|
||||
regex: '[1-9]|1[0-2]',
|
||||
apply: function(value) { this.month = value - 1; }
|
||||
apply: function(value) { this.month = value - 1; },
|
||||
formatter: function(date) { return dateFilter(date, 'M'); }
|
||||
},
|
||||
'dd': {
|
||||
{
|
||||
key: 'd!',
|
||||
regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
|
||||
apply: function(value) { this.date = +value; },
|
||||
formatter: function(date) {
|
||||
var value = date.getDate();
|
||||
if (/^[1-9]$/.test(value)) {
|
||||
return dateFilter(date, 'dd');
|
||||
}
|
||||
|
||||
return dateFilter(date, 'd');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'dd',
|
||||
regex: '[0-2][0-9]{1}|3[0-1]{1}',
|
||||
apply: function(value) { this.date = +value; }
|
||||
apply: function(value) { this.date = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'dd'); }
|
||||
},
|
||||
'd': {
|
||||
{
|
||||
key: 'd',
|
||||
regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
|
||||
apply: function(value) { this.date = +value; }
|
||||
apply: function(value) { this.date = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'd'); }
|
||||
},
|
||||
'EEEE': {
|
||||
regex: $locale.DATETIME_FORMATS.DAY.join('|')
|
||||
{
|
||||
key: 'EEEE',
|
||||
regex: $locale.DATETIME_FORMATS.DAY.join('|'),
|
||||
formatter: function(date) { return dateFilter(date, 'EEEE'); }
|
||||
},
|
||||
'EEE': {
|
||||
regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
|
||||
{
|
||||
key: 'EEE',
|
||||
regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'),
|
||||
formatter: function(date) { return dateFilter(date, 'EEE'); }
|
||||
},
|
||||
'HH': {
|
||||
{
|
||||
key: 'HH',
|
||||
regex: '(?:0|1)[0-9]|2[0-3]',
|
||||
apply: function(value) { this.hours = +value; }
|
||||
apply: function(value) { this.hours = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'HH'); }
|
||||
},
|
||||
'hh': {
|
||||
{
|
||||
key: 'hh',
|
||||
regex: '0[0-9]|1[0-2]',
|
||||
apply: function(value) { this.hours = +value; }
|
||||
apply: function(value) { this.hours = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'hh'); }
|
||||
},
|
||||
'H': {
|
||||
{
|
||||
key: 'H',
|
||||
regex: '1?[0-9]|2[0-3]',
|
||||
apply: function(value) { this.hours = +value; }
|
||||
apply: function(value) { this.hours = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'H'); }
|
||||
},
|
||||
'h': {
|
||||
{
|
||||
key: 'h',
|
||||
regex: '[0-9]|1[0-2]',
|
||||
apply: function(value) { this.hours = +value; }
|
||||
apply: function(value) { this.hours = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'h'); }
|
||||
},
|
||||
'mm': {
|
||||
{
|
||||
key: 'mm',
|
||||
regex: '[0-5][0-9]',
|
||||
apply: function(value) { this.minutes = +value; }
|
||||
apply: function(value) { this.minutes = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'mm'); }
|
||||
},
|
||||
'm': {
|
||||
{
|
||||
key: 'm',
|
||||
regex: '[0-9]|[1-5][0-9]',
|
||||
apply: function(value) { this.minutes = +value; }
|
||||
apply: function(value) { this.minutes = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'm'); }
|
||||
},
|
||||
'sss': {
|
||||
{
|
||||
key: 'sss',
|
||||
regex: '[0-9][0-9][0-9]',
|
||||
apply: function(value) { this.milliseconds = +value; }
|
||||
apply: function(value) { this.milliseconds = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'sss'); }
|
||||
},
|
||||
'ss': {
|
||||
{
|
||||
key: 'ss',
|
||||
regex: '[0-5][0-9]',
|
||||
apply: function(value) { this.seconds = +value; }
|
||||
apply: function(value) { this.seconds = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'ss'); }
|
||||
},
|
||||
's': {
|
||||
{
|
||||
key: 's',
|
||||
regex: '[0-9]|[1-5][0-9]',
|
||||
apply: function(value) { this.seconds = +value; }
|
||||
apply: function(value) { this.seconds = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 's'); }
|
||||
},
|
||||
'a': {
|
||||
{
|
||||
key: 'a',
|
||||
regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
|
||||
apply: function(value) {
|
||||
if (this.hours === 12) {
|
||||
@@ -101,31 +181,137 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
if (value === 'PM') {
|
||||
this.hours += 12;
|
||||
}
|
||||
},
|
||||
formatter: function(date) { return dateFilter(date, 'a'); }
|
||||
},
|
||||
{
|
||||
key: 'Z',
|
||||
regex: '[+-]\\d{4}',
|
||||
apply: function(value) {
|
||||
var matches = value.match(/([+-])(\d{2})(\d{2})/),
|
||||
sign = matches[1],
|
||||
hours = matches[2],
|
||||
minutes = matches[3];
|
||||
this.hours += toInt(sign + hours);
|
||||
this.minutes += toInt(sign + minutes);
|
||||
},
|
||||
formatter: function(date) {
|
||||
return dateFilter(date, 'Z');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'ww',
|
||||
regex: '[0-4][0-9]|5[0-3]',
|
||||
formatter: function(date) { return dateFilter(date, 'ww'); }
|
||||
},
|
||||
{
|
||||
key: 'w',
|
||||
regex: '[0-9]|[1-4][0-9]|5[0-3]',
|
||||
formatter: function(date) { return dateFilter(date, 'w'); }
|
||||
},
|
||||
{
|
||||
key: 'GGGG',
|
||||
regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'),
|
||||
formatter: function(date) { return dateFilter(date, 'GGGG'); }
|
||||
},
|
||||
{
|
||||
key: 'GGG',
|
||||
regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
|
||||
formatter: function(date) { return dateFilter(date, 'GGG'); }
|
||||
},
|
||||
{
|
||||
key: 'GG',
|
||||
regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
|
||||
formatter: function(date) { return dateFilter(date, 'GG'); }
|
||||
},
|
||||
{
|
||||
key: 'G',
|
||||
regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
|
||||
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 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('');
|
||||
|
||||
angular.forEach(formatCodeToRegex, function(data, code) {
|
||||
var index = format.indexOf(code);
|
||||
// check for literal values
|
||||
var quoteIndex = format.indexOf('\'');
|
||||
if (quoteIndex > -1) {
|
||||
var inLiteral = false;
|
||||
format = format.split('');
|
||||
for (var i = quoteIndex; i < format.length; i++) {
|
||||
if (inLiteral) {
|
||||
if (format[i] === '\'') {
|
||||
if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote
|
||||
format[i+1] = '$';
|
||||
regex[i+1] = '';
|
||||
} else { // end of literal
|
||||
regex[i] = '';
|
||||
inLiteral = false;
|
||||
}
|
||||
}
|
||||
format[i] = '$';
|
||||
} else {
|
||||
if (format[i] === '\'') { // start of literal
|
||||
format[i] = '$';
|
||||
regex[i] = '';
|
||||
inLiteral = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
format = format.join('');
|
||||
}
|
||||
|
||||
angular.forEach(formatCodeToRegex, function(data) {
|
||||
var index = format.indexOf(data.key);
|
||||
|
||||
if (index > -1) {
|
||||
format = format.split('');
|
||||
|
||||
regex[index] = '(' + data.regex + ')';
|
||||
format[index] = '$'; // Custom symbol to define consumed part of format
|
||||
for (var i = index + 1, n = index + code.length; i < n; i++) {
|
||||
for (var i = index + 1, n = index + data.key.length; i < n; i++) {
|
||||
regex[i] = '';
|
||||
format[i] = '$';
|
||||
}
|
||||
format = format.join('');
|
||||
|
||||
map.push({ index: index, apply: data.apply });
|
||||
map.push({
|
||||
index: index,
|
||||
key: data.key,
|
||||
apply: data.apply,
|
||||
matcher: data.regex
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -135,6 +321,92 @@ 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 '';
|
||||
}
|
||||
|
||||
format = $locale.DATETIME_FORMATS[format] || format;
|
||||
|
||||
if ($locale.id !== localeId) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
if (!this.formatters[format]) {
|
||||
this.formatters[format] = createFormatter(format);
|
||||
}
|
||||
|
||||
var formatters = this.formatters[format];
|
||||
|
||||
return formatters.reduce(function(str, formatter) {
|
||||
return str + formatter(date);
|
||||
}, '');
|
||||
};
|
||||
|
||||
this.parse = function(input, format, baseDate) {
|
||||
if (!angular.isString(input) || !format) {
|
||||
return input;
|
||||
@@ -148,14 +420,14 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
}
|
||||
|
||||
if (!this.parsers[format]) {
|
||||
this.parsers[format] = createParser(format);
|
||||
this.parsers[format] = createParser(format, 'apply');
|
||||
}
|
||||
|
||||
var parser = this.parsers[format],
|
||||
regex = parser.regex,
|
||||
map = parser.map,
|
||||
results = input.match(regex);
|
||||
|
||||
results = input.match(regex),
|
||||
tzOffset = false;
|
||||
if (results && results.length) {
|
||||
var fields, dt;
|
||||
if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
|
||||
@@ -176,22 +448,32 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
}
|
||||
|
||||
for (var i = 1, n = results.length; i < n; i++) {
|
||||
var mapper = map[i-1];
|
||||
var mapper = map[i - 1];
|
||||
if (mapper.matcher === 'Z') {
|
||||
tzOffset = true;
|
||||
}
|
||||
|
||||
if (mapper.apply) {
|
||||
mapper.apply.call(fields, results[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
|
||||
Date.prototype.setFullYear;
|
||||
var timesetter = tzOffset ? Date.prototype.setUTCHours :
|
||||
Date.prototype.setHours;
|
||||
|
||||
if (isValid(fields.year, fields.month, fields.date)) {
|
||||
if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
|
||||
if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
|
||||
dt = new Date(baseDate);
|
||||
dt.setFullYear(fields.year, fields.month, fields.date,
|
||||
fields.hours, fields.minutes, fields.seconds,
|
||||
fields.milliseconds || 0);
|
||||
datesetter.call(dt, fields.year, fields.month, fields.date);
|
||||
timesetter.call(dt, fields.hours, fields.minutes,
|
||||
fields.seconds, fields.milliseconds);
|
||||
} else {
|
||||
dt = new Date(fields.year, fields.month, fields.date,
|
||||
fields.hours, fields.minutes, fields.seconds,
|
||||
fields.milliseconds || 0);
|
||||
dt = new Date(0);
|
||||
datesetter.call(dt, fields.year, fields.month, fields.date);
|
||||
timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
|
||||
fields.seconds || 0, fields.milliseconds || 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +489,7 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
}
|
||||
|
||||
if (month === 1 && date > 28) {
|
||||
return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
|
||||
return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);
|
||||
}
|
||||
|
||||
if (month === 3 || month === 5 || month === 8 || month === 10) {
|
||||
@@ -216,18 +498,42 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
|
||||
return true;
|
||||
}
|
||||
}]);
|
||||
|
||||
/* Deprecated dateparser below */
|
||||
|
||||
angular.module('ui.bootstrap.dateparser')
|
||||
|
||||
.value('$dateParserSuppressWarning', false)
|
||||
|
||||
.service('dateParser', ['$log', '$dateParserSuppressWarning', 'uibDateParser', function($log, $dateParserSuppressWarning, uibDateParser) {
|
||||
if (!$dateParserSuppressWarning) {
|
||||
$log.warn('dateParser is now deprecated. Use uibDateParser instead.');
|
||||
function toInt(str) {
|
||||
return parseInt(str, 10);
|
||||
}
|
||||
|
||||
angular.extend(this, uibDateParser);
|
||||
this.toTimezone = toTimezone;
|
||||
this.fromTimezone = fromTimezone;
|
||||
this.timezoneToOffset = timezoneToOffset;
|
||||
this.addDateMinutes = addDateMinutes;
|
||||
this.convertTimezoneToLocal = convertTimezoneToLocal;
|
||||
|
||||
function toTimezone(date, timezone) {
|
||||
return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
|
||||
}
|
||||
|
||||
function fromTimezone(date, timezone) {
|
||||
return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
function addDateMinutes(date, minutes) {
|
||||
date = new Date(date.getTime());
|
||||
date.setMinutes(date.getMinutes() + minutes);
|
||||
return date;
|
||||
}
|
||||
|
||||
function convertTimezoneToLocal(date, timezone, reverse) {
|
||||
reverse = reverse ? -1 : 1;
|
||||
var dateTimezoneOffset = date.getTimezoneOffset();
|
||||
var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
|
||||
return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
|
||||
}
|
||||
}]);
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
The `uibDateParser` is what the `uib-datepicker` uses internally to parse the dates. You can use it standalone by injecting the `uibDateParser` service where you need it.
|
||||
|
||||
The public API for the dateParser is a single method called `parse`.
|
||||
|
||||
Certain format codes support i18n. Check this [guide](https://docs.angularjs.org/guide/i18n) for more information.
|
||||
|
||||
### uibDateParser's parse function
|
||||
|
||||
##### parameters
|
||||
|
||||
* `input`
|
||||
_(Type: `string`, Example: `2004/Sep/4`)_ -
|
||||
The input date to parse.
|
||||
|
||||
* `format`
|
||||
_(Type: `string`, Example: `yyyy/MMM/d`)_ -
|
||||
The format we want to use. Check all the supported formats below.
|
||||
|
||||
* `baseDate`
|
||||
_(Type: `Date`, Example: `new Date()`)_ -
|
||||
If you want to parse a date but maintain the timezone, you can pass an existing date here.
|
||||
|
||||
##### return
|
||||
|
||||
* If the specified input matches the format, a new date with the input will be returned, otherwise, it will return undefined.
|
||||
|
||||
### uibDateParser's format codes
|
||||
|
||||
* `yyyy`
|
||||
_(Example: `2015`)_ -
|
||||
Parses a 4 digits year.
|
||||
|
||||
* `yy`
|
||||
_(Example: `15`)_ -
|
||||
Parses a 2 digits year.
|
||||
|
||||
* `y`
|
||||
_(Example: `15`)_ -
|
||||
Parses a year with 1, 2, 3, or 4 digits.
|
||||
|
||||
* `MMMM`
|
||||
_(Example: `February`, i18n support)_ -
|
||||
Parses the full name of a month.
|
||||
|
||||
* `MMM`
|
||||
_(Example: `Feb`, i18n support)_ -
|
||||
Parses the short name of a month.
|
||||
|
||||
* `MM`
|
||||
_(Example: `12`, Leading 0)_ -
|
||||
Parses a numeric month.
|
||||
|
||||
* `M`
|
||||
_(Example: `3`)_ -
|
||||
Parses a numeric month.
|
||||
|
||||
* `M!`
|
||||
_(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.
|
||||
|
||||
* `d`
|
||||
_(Example: `5`)_ -
|
||||
Parses a numeric day.
|
||||
|
||||
* `d!`
|
||||
_(Example: `3` or `03`)_ -
|
||||
Parses a numeric day, but allowing an optional leading zero
|
||||
|
||||
* `EEEE`
|
||||
_(Example: `Sunday`, i18n support)_ -
|
||||
Parses the full name of a day.
|
||||
|
||||
* `EEE`
|
||||
_(Example: `Mon`, i18n support)_ -
|
||||
Parses the short name of a day.
|
||||
|
||||
* `HH`
|
||||
_(Example: `14`, Leading 0)_ -
|
||||
Parses a 24 hours time.
|
||||
|
||||
* `H`
|
||||
_(Example: `3`)_ -
|
||||
Parses a 24 hours time.
|
||||
|
||||
* `hh`
|
||||
_(Example: `11`, Leading 0)_ -
|
||||
Parses a 12 hours time.
|
||||
|
||||
* `h`
|
||||
_(Example: `3`)_ -
|
||||
Parses a 12 hours time.
|
||||
|
||||
* `mm`
|
||||
_(Example: `09`, Leading 0)_ -
|
||||
Parses the minutes.
|
||||
|
||||
* `m`
|
||||
_(Example: `3`)_ -
|
||||
Parses the minutes.
|
||||
|
||||
* `sss`
|
||||
_(Example: `094`, Leading 0)_ -
|
||||
Parses the milliseconds.
|
||||
|
||||
* `ss`
|
||||
_(Example: `08`, Leading 0)_ -
|
||||
Parses the seconds.
|
||||
|
||||
* `s`
|
||||
_(Example: `22`)_ -
|
||||
Parses the seconds.
|
||||
|
||||
* `a`
|
||||
_(Example: `10AM`)_ -
|
||||
Parses a 12 hours time with AM/PM.
|
||||
|
||||
* `Z`
|
||||
_(Example: `-0800`)_ -
|
||||
Parses the timezone offset in a signed 4 digit representation
|
||||
|
||||
* `ww`
|
||||
_(Example: `03`, Leading 0)_ -
|
||||
Parses the week number
|
||||
|
||||
* `w`
|
||||
_(Example: `03`)_ -
|
||||
Parses the week number
|
||||
|
||||
* `G`, `GG`, `GGG`
|
||||
_(Example: `AD`)_ -
|
||||
Parses the era (`AD` or `BC`)
|
||||
* `GGGG`
|
||||
_(Example: `Anno Domini`)_ -
|
||||
Parses the long form of the era (`Anno Domini` or `Before Christ`)
|
||||
|
||||
\* The ones marked with `Leading 0`, needs a leading 0 for values less than 10. Exception being milliseconds which needs it for values under 100.
|
||||
|
||||
\** It also supports `fullDate|longDate|medium|mediumDate|mediumTime|short|shortDate|shortTime` as the format for parsing.
|
||||
|
||||
\*** It supports template literals as a string between the single quote `'` character, i.e. `'The Date is' MM/DD/YYYY`. If one wants the literal single quote character, one must use `''''`.
|
||||
@@ -0,0 +1,11 @@
|
||||
<div ng-controller="DateParserDemoCtrl">
|
||||
<h4>Formatting codes playground</h4>
|
||||
<p class="form-group">
|
||||
<label>Define your format</label>
|
||||
<input type="text" ng-model="format" class="form-control">
|
||||
</p>
|
||||
<p class="form-group">
|
||||
<label>Result</label>
|
||||
<input type="text" class="form-control" uib-datepicker-popup="{{format}}" ng-model="date" />
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
angular.module('ui.bootstrap.demo').controller('DateParserDemoCtrl', function ($scope, uibDateParser) {
|
||||
$scope.format = 'yyyy/MM/dd';
|
||||
$scope.date = new Date();
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
require('./dateparser');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.dateparser';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.dateparser']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
@@ -1,15 +1,282 @@
|
||||
describe('date parser', function() {
|
||||
var dateParser;
|
||||
var dateParser, oldDate;
|
||||
|
||||
beforeEach(module('ui.bootstrap.dateparser'));
|
||||
beforeEach(inject(function (uibDateParser) {
|
||||
dateParser = uibDateParser;
|
||||
oldDate = new Date(1, 2, 6);
|
||||
oldDate.setFullYear(1);
|
||||
}));
|
||||
|
||||
function expectFilter(date, format, display) {
|
||||
expect(dateParser.filter(date, format)).toEqual(display);
|
||||
}
|
||||
|
||||
function expectParse(input, format, date) {
|
||||
expect(dateParser.parse(input, format)).toEqual(date);
|
||||
}
|
||||
|
||||
function expectBaseParse(input, format, baseDate, date) {
|
||||
expect(dateParser.parse(input, format, baseDate)).toEqual(date);
|
||||
}
|
||||
|
||||
describe('filter', function() {
|
||||
it('should work correctly for `dd`, `MM`, `yyyy`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.yyyy', '17.11.2013');
|
||||
expectFilter(new Date(2013, 11, 31, 0), 'dd.MM.yyyy', '31.12.2013');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'dd-MM-yyyy', '08-03-1991');
|
||||
expectFilter(new Date(1980, 2, 5, 0), 'MM/dd/yyyy', '03/05/1980');
|
||||
expectFilter(new Date(1983, 0, 10, 0), 'dd.MM/yyyy', '10.01/1983');
|
||||
expectFilter(new Date(1980, 10, 9, 0), 'MM-dd-yyyy', '11-09-1980');
|
||||
expectFilter(new Date(2011, 1, 5, 0), 'yyyy/MM/dd', '2011/02/05');
|
||||
expectFilter(oldDate, 'yyyy/MM/dd', '0001/03/06');
|
||||
});
|
||||
|
||||
it('should work correctly for `yy`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.yy', '17.11.13');
|
||||
expectFilter(new Date(2011, 4, 2, 0), 'dd-MM-yy', '02-05-11');
|
||||
expectFilter(new Date(2080, 1, 5, 0), 'MM/dd/yy', '02/05/80');
|
||||
expectFilter(new Date(2055, 1, 5, 0), 'yy/MM/dd', '55/02/05');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'dd-MM-yy', '11-08-13');
|
||||
});
|
||||
|
||||
it('should work correctly for `y`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.y', '17.11.2013');
|
||||
expectFilter(new Date(2013, 11, 31, 0), 'dd.MM.y', '31.12.2013');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'dd-MM-y', '08-03-1991');
|
||||
expectFilter(new Date(1980, 2, 5, 0), 'MM/dd/y', '03/05/1980');
|
||||
expectFilter(new Date(1983, 0, 10, 0), 'dd.MM/y', '10.01/1983');
|
||||
expectFilter(new Date(1980, 10, 9, 0), 'MM-dd-y', '11-09-1980');
|
||||
expectFilter(new Date(2011, 1, 5, 0), 'y/MM/dd', '2011/02/05');
|
||||
});
|
||||
|
||||
it('should work correctly for `MMMM`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'dd.MMMM.yy', '17.November.13');
|
||||
expectFilter(new Date(1980, 2, 5, 0), 'dd-MMMM-yyyy', '05-March-1980');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/dd/yyyy', 'February/05/1980');
|
||||
expectFilter(new Date(1949, 11, 20, 0), 'yyyy/MMMM/dd', '1949/December/20');
|
||||
expectFilter(oldDate, 'yyyy/MMMM/dd', '0001/March/06');
|
||||
});
|
||||
|
||||
it('should work correctly for `MMM`', function() {
|
||||
expectFilter(new Date(2010, 8, 30, 0), 'dd.MMM.yy', '30.Sep.10');
|
||||
expectFilter(new Date(2011, 4, 2, 0), 'dd-MMM-yy', '02-May-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMM/dd/yyyy', 'Feb/05/1980');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMM/dd', '1955/Feb/05');
|
||||
expectFilter(oldDate, 'yyyy/MMM/dd', '0001/Mar/06');
|
||||
});
|
||||
|
||||
it('should work correctly for `M`', function() {
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'M/dd/yyyy', '8/11/2013');
|
||||
expectFilter(new Date(2005, 10, 7, 0), 'dd.M.yy', '07.11.05');
|
||||
expectFilter(new Date(2011, 4, 2, 0), 'dd-M-yy', '02-5-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'M/dd/yyyy', '2/05/1980');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/M/dd', '1955/2/05');
|
||||
expectFilter(new Date(2011, 4, 2, 0), 'dd-M-yy', '02-5-11');
|
||||
});
|
||||
|
||||
it('should work correctly for `M!`', function() {
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'M!/dd/yyyy', '08/11/2013');
|
||||
expectFilter(new Date(2005, 10, 7, 0), 'dd.M!.yy', '07.11.05');
|
||||
expectFilter(new Date(2011, 4, 2, 0), 'dd-M!-yy', '02-05-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'M!/dd/yyyy', '02/05/1980');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/M!/dd', '1955/02/05');
|
||||
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');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'd-MMMM-yyyy', '8-March-1991');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy', 'February/5/1980');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d', '1955/February/5');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy', '11-08-13');
|
||||
expectFilter(oldDate, 'yyyy/MM/d', '0001/03/6');
|
||||
});
|
||||
|
||||
it('should work correctly for `d!`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd!.MMMM.yy', '17.November.13');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'd!-MMMM-yyyy', '08-March-1991');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d!/yyyy', 'February/05/1980');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d!', '1955/February/05');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd!-MM-yy', '11-08-13');
|
||||
expectFilter(oldDate, 'yyyy/MM/d!', '0001/03/06');
|
||||
});
|
||||
|
||||
it('should work correctly for `EEEE`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'EEEE.d.MMMM.yy', 'Sunday.17.November.13');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'd-EEEE-MMMM-yyyy', '8-Friday-March-1991');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/EEEE', 'February/5/1980/Tuesday');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/EEEE/MMMM/d', '1955/Saturday/February/5');
|
||||
});
|
||||
|
||||
it('should work correctly for `EEE`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'EEE.d.MMMM.yy', 'Sun.17.November.13');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'd-EEE-MMMM-yyyy', '8-Fri-March-1991');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/EEE', 'February/5/1980/Tue');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/EEE/MMMM/d', '1955/Sat/February/5');
|
||||
});
|
||||
|
||||
it('should work correctly for `HH`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 22), 'd.MMMM.yy.HH', '22.March.15.22');
|
||||
expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-HH', '8-March-1991-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/HH', 'February/5/1980/00');
|
||||
expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d HH', '1955/February/5 03');
|
||||
expectFilter(new Date(2013, 7, 11, 23), 'd-MM-yy HH', '11-08-13 23');
|
||||
});
|
||||
|
||||
it('should work correctly for `H`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 22), 'd.MMMM.yy.H', '22.March.15.22');
|
||||
expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-H', '8-March-1991-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/H', 'February/5/1980/0');
|
||||
expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d H', '1955/February/5 3');
|
||||
expectFilter(new Date(2013, 7, 11, 23), 'd-MM-yy H', '11-08-13 23');
|
||||
});
|
||||
|
||||
it('should work correctly for `hh`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 12), 'd.MMMM.yy.hh', '22.March.15.12');
|
||||
expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-hh', '8-March-1991-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/hh', 'February/5/1980/12');
|
||||
expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d hh', '1955/February/5 03');
|
||||
expectFilter(new Date(2013, 7, 11, 9), 'd-MM-yy hh', '11-08-13 09');
|
||||
});
|
||||
|
||||
it('should work correctly for `h`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 12), 'd.MMMM.yy.h', '22.March.15.12');
|
||||
expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-h', '8-March-1991-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/h', 'February/5/1980/12');
|
||||
expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d h', '1955/February/5 3');
|
||||
expectFilter(new Date(2013, 7, 11, 3), 'd-MM-yy h', '11-08-13 3');
|
||||
});
|
||||
|
||||
it('should work correctly for `mm`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 0, 22), 'd.MMMM.yy.mm', '22.March.15.22');
|
||||
expectFilter(new Date(1991, 2, 8, 0, 59), 'd-MMMM-yyyy-mm', '8-March-1991-59');
|
||||
expectFilter(new Date(1980, 1, 5, 0, 0), 'MMMM/d/yyyy/mm', 'February/5/1980/00');
|
||||
expectFilter(new Date(1955, 1, 5, 0, 3), 'yyyy/MMMM/d mm', '1955/February/5 03');
|
||||
expectFilter(new Date(2013, 7, 11, 0, 46), 'd-MM-yy mm', '11-08-13 46');
|
||||
expectFilter(new Date(2015, 2, 22, 22, 33), 'd.MMMM.yy.HH:mm', '22.March.15.22:33');
|
||||
expectFilter(new Date(2015, 2, 22, 2, 1), 'd.MMMM.yy.H:mm', '22.March.15.2:01');
|
||||
});
|
||||
|
||||
it('should work correctly for `m`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 0, 22), 'd.MMMM.yy.m', '22.March.15.22');
|
||||
expectFilter(new Date(1991, 2, 8, 0, 59), 'd-MMMM-yyyy-m', '8-March-1991-59');
|
||||
expectFilter(new Date(1980, 1, 5, 0, 0), 'MMMM/d/yyyy/m', 'February/5/1980/0');
|
||||
expectFilter(new Date(1955, 1, 5, 0, 3), 'yyyy/MMMM/d m', '1955/February/5 3');
|
||||
expectFilter(new Date(2013, 7, 11, 0, 46), 'd-MM-yy m', '11-08-13 46');
|
||||
expectFilter(new Date(2015, 2, 22, 22, 3), 'd.MMMM.yy.HH:m', '22.March.15.22:3');
|
||||
expectFilter(new Date(2015, 2, 22, 2, 1), 'd.MMMM.yy.H:m', '22.March.15.2:1');
|
||||
});
|
||||
|
||||
it('should work correctly for `sss`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 0, 0, 0, 123), 'd.MMMM.yy.sss', '22.March.15.123');
|
||||
expectFilter(new Date(1991, 2, 8, 0, 0, 0, 59), 'd-MMMM-yyyy-sss', '8-March-1991-059');
|
||||
expectFilter(new Date(1980, 1, 5, 0, 0, 0), 'MMMM/d/yyyy/sss', 'February/5/1980/000');
|
||||
expectFilter(new Date(1955, 1, 5, 0, 0, 0, 3), 'yyyy/MMMM/d sss', '1955/February/5 003');
|
||||
expectFilter(new Date(2013, 7, 11, 0, 0, 0, 46), 'd-MM-yy sss', '11-08-13 046');
|
||||
expectFilter(new Date(2015, 2, 22, 22, 33, 0, 44), 'd.MMMM.yy.HH:mm:sss', '22.March.15.22:33:044');
|
||||
expectFilter(new Date(2015, 2, 22, 0, 0, 0, 1), 'd.MMMM.yy.H:m:sss', '22.March.15.0:0:001');
|
||||
});
|
||||
|
||||
it('should work correctly for `ss`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 0, 0, 22), 'd.MMMM.yy.ss', '22.March.15.22');
|
||||
expectFilter(new Date(1991, 2, 8, 0, 0, 59), 'd-MMMM-yyyy-ss', '8-March-1991-59');
|
||||
expectFilter(new Date(1980, 1, 5, 0, 0, 0), 'MMMM/d/yyyy/ss', 'February/5/1980/00');
|
||||
expectFilter(new Date(1955, 1, 5, 0, 0, 3), 'yyyy/MMMM/d ss', '1955/February/5 03');
|
||||
expectFilter(new Date(2013, 7, 11, 0, 0, 46), 'd-MM-yy ss', '11-08-13 46');
|
||||
expectFilter(new Date(2015, 2, 22, 22, 33, 44), 'd.MMMM.yy.HH:mm:ss', '22.March.15.22:33:44');
|
||||
expectFilter(new Date(2015, 2, 22, 0, 0, 1), 'd.MMMM.yy.H:m:ss', '22.March.15.0:0:01');
|
||||
});
|
||||
|
||||
it('should work correctly for `s`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 0, 0, 22), 'd.MMMM.yy.s', '22.March.15.22');
|
||||
expectFilter(new Date(1991, 2, 8, 0, 0, 59), 'd-MMMM-yyyy-s', '8-March-1991-59');
|
||||
expectFilter(new Date(1980, 1, 5, 0, 0, 0), 'MMMM/d/yyyy/s', 'February/5/1980/0');
|
||||
expectFilter(new Date(1955, 1, 5, 0, 0, 3), 'yyyy/MMMM/d s', '1955/February/5 3');
|
||||
expectFilter(new Date(2013, 7, 11, 0, 0, 46), 'd-MM-yy s', '11-08-13 46');
|
||||
expectFilter(new Date(2015, 2, 22, 22, 33, 4), 'd.MMMM.yy.HH:mm:s', '22.March.15.22:33:4');
|
||||
expectFilter(new Date(2015, 2, 22, 22, 3, 4), 'd.MMMM.yy.HH:m:s', '22.March.15.22:3:4');
|
||||
});
|
||||
|
||||
it('should work correctly for `a`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 10), 'd.MMMM.yy.hha', '22.March.15.10AM');
|
||||
expectFilter(new Date(2015, 2, 22, 22), 'd.MMMM.yy.hha', '22.March.15.10PM');
|
||||
expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-hha', '8-March-1991-11AM');
|
||||
expectFilter(new Date(1991, 2, 8, 23), 'd-MMMM-yyyy-hha', '8-March-1991-11PM');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/hha', 'February/5/1980/12AM');
|
||||
expectFilter(new Date(1980, 1, 5, 12), 'MMMM/d/yyyy/hha', 'February/5/1980/12PM');
|
||||
expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d hha', '1955/February/5 03AM');
|
||||
expectFilter(new Date(1955, 1, 5, 15), 'yyyy/MMMM/d hha', '1955/February/5 03PM');
|
||||
expectFilter(new Date(2013, 7, 11, 9), 'd-MM-yy hha', '11-08-13 09AM');
|
||||
expectFilter(new Date(2013, 7, 11, 21), 'd-MM-yy hha', '11-08-13 09PM');
|
||||
});
|
||||
|
||||
it('should work correctly for `ww`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.ww', '17.November.13.47');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'd-MMMM-yyyy-ww', '8-March-1991-10');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/ww', 'February/5/1980/06');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d/ww', '1955/February/5/05');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy ww', '11-08-13 33');
|
||||
expectFilter(oldDate, 'yyyy/MM/d ww', '0001/03/6 10');
|
||||
});
|
||||
|
||||
it('should work correctly for `w`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.w', '17.November.13.47');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'd-MMMM-yyyy-w', '8-March-1991-10');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/w', 'February/5/1980/6');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d/w', '1955/February/5/5');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy w', '11-08-13 33');
|
||||
expectFilter(oldDate, 'yyyy/MM/d w', '0001/03/6 10');
|
||||
});
|
||||
|
||||
it('should work correctly for `G`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.G', '17.November.13.AD');
|
||||
expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-G', '8-March-1991-BC');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/G', 'February/5/1980/AD');
|
||||
expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/G', '1955/February/5/BC');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy G', '11-08-13 AD');
|
||||
});
|
||||
|
||||
it('should work correctly for `GG`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.GG', '17.November.13.AD');
|
||||
expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-GG', '8-March-1991-BC');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/GG', 'February/5/1980/AD');
|
||||
expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/GG', '1955/February/5/BC');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy GG', '11-08-13 AD');
|
||||
});
|
||||
|
||||
it('should work correctly for `GGG`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.GGG', '17.November.13.AD');
|
||||
expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-GGG', '8-March-1991-BC');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/GGG', 'February/5/1980/AD');
|
||||
expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/GGG', '1955/February/5/BC');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy GGG', '11-08-13 AD');
|
||||
});
|
||||
|
||||
it('should work correctly for `GGGG`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.GGGG', '17.November.13.Anno Domini');
|
||||
expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-GGGG', '8-March-1991-Before Christ');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/GGGG', 'February/5/1980/Anno Domini');
|
||||
expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/GGGG', '1955/February/5/Before Christ');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy GGGG', '11-08-13 Anno Domini');
|
||||
});
|
||||
|
||||
it('should work correctly for literal text', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.yyyy foo', '17.11.2013 foo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with custom formats', function() {
|
||||
it('should work correctly for `dd`, `MM`, `yyyy`', function() {
|
||||
expectParse('17.11.2013', 'dd.MM.yyyy', new Date(2013, 10, 17, 0));
|
||||
@@ -19,16 +286,48 @@ describe('date parser', function() {
|
||||
expectParse('10.01/1983', 'dd.MM/yyyy', new Date(1983, 0, 10, 0));
|
||||
expectParse('11-09-1980', 'MM-dd-yyyy', new Date(1980, 10, 9, 0));
|
||||
expectParse('2011/02/05', 'yyyy/MM/dd', new Date(2011, 1, 5, 0));
|
||||
expectParse('0001/03/06', 'yyyy/MM/dd', oldDate);
|
||||
});
|
||||
|
||||
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));
|
||||
expectParse('08-03-1991', 'dd-MM-y', new Date(1991, 2, 8, 0));
|
||||
expectParse('03/05/1980', 'MM/dd/y', new Date(1980, 2, 5, 0));
|
||||
expectParse('10.01/1983', 'dd.MM/y', new Date(1983, 0, 10, 0));
|
||||
expectParse('11-09-1980', 'MM-dd-y', new Date(1980, 10, 9, 0));
|
||||
expectParse('2011/02/05', 'y/MM/dd', new Date(2011, 1, 5, 0));
|
||||
});
|
||||
|
||||
it('should work correctly for `MMMM`', function() {
|
||||
expectParse('17.November.13', 'dd.MMMM.yy', new Date(2013, 10, 17, 0));
|
||||
expectParse('05-March-1980', 'dd-MMMM-yyyy', new Date(1980, 2, 5, 0));
|
||||
expectParse('February/05/1980', 'MMMM/dd/yyyy', new Date(1980, 1, 5, 0));
|
||||
expectParse('1949/December/20', 'yyyy/MMMM/dd', new Date(1949, 11, 20, 0));
|
||||
expectParse('0001/March/06', 'yyyy/MMMM/dd', oldDate);
|
||||
});
|
||||
|
||||
it('should work correctly for `MMM`', function() {
|
||||
expectParse('30.Sep.10', 'dd.MMM.yy', new Date(2010, 8, 30, 0));
|
||||
expectParse('02-May-11', 'dd-MMM-yy', new Date(2011, 4, 2, 0));
|
||||
expectParse('Feb/05/1980', 'MMM/dd/yyyy', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/Feb/05', 'yyyy/MMM/dd', new Date(1955, 1, 5, 0));
|
||||
expectParse('0001/Mar/06', 'yyyy/MMM/dd', oldDate);
|
||||
});
|
||||
|
||||
it('should work correctly for `M`', function() {
|
||||
expectParse('8/11/2013', 'M/dd/yyyy', new Date(2013, 7, 11, 0));
|
||||
expectParse('07.11.05', 'dd.M.yy', new Date(2005, 10, 7, 0));
|
||||
@@ -38,18 +337,22 @@ describe('date parser', function() {
|
||||
expectParse('02-5-11', 'dd-M-yy', new Date(2011, 4, 2, 0));
|
||||
});
|
||||
|
||||
it('should work correctly for `MMM`', function() {
|
||||
expectParse('30.Sep.10', 'dd.MMM.yy', new Date(2010, 8, 30, 0));
|
||||
expectParse('02-May-11', 'dd-MMM-yy', new Date(2011, 4, 2, 0));
|
||||
expectParse('Feb/05/1980', 'MMM/dd/yyyy', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/Feb/05', 'yyyy/MMM/dd', new Date(1955, 1, 5, 0));
|
||||
});
|
||||
it('should work correctly for `M!`', function() {
|
||||
expectParse('8/11/2013', 'M!/dd/yyyy', new Date(2013, 7, 11, 0));
|
||||
expectParse('07.11.05', 'dd.M!.yy', new Date(2005, 10, 7, 0));
|
||||
expectParse('02-5-11', 'dd-M!-yy', new Date(2011, 4, 2, 0));
|
||||
expectParse('2/05/1980', 'M!/dd/yyyy', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/2/05', 'yyyy/M!/dd', new Date(1955, 1, 5, 0));
|
||||
expectParse('02-5-11', 'dd-M!-yy', new Date(2011, 4, 2, 0));
|
||||
expectParse('0001/3/06', 'yyyy/M!/dd', oldDate);
|
||||
|
||||
it('should work correctly for `MMMM`', function() {
|
||||
expectParse('17.November.13', 'dd.MMMM.yy', new Date(2013, 10, 17, 0));
|
||||
expectParse('05-March-1980', 'dd-MMMM-yyyy', new Date(1980, 2, 5, 0));
|
||||
expectParse('February/05/1980', 'MMMM/dd/yyyy', new Date(1980, 1, 5, 0));
|
||||
expectParse('1949/December/20', 'yyyy/MMMM/dd', new Date(1949, 11, 20, 0));
|
||||
expectParse('08/11/2013', 'M!/dd/yyyy', new Date(2013, 7, 11, 0));
|
||||
expectParse('07.11.05', 'dd.M!.yy', new Date(2005, 10, 7, 0));
|
||||
expectParse('02-05-11', 'dd-M!-yy', new Date(2011, 4, 2, 0));
|
||||
expectParse('02/05/1980', 'M!/dd/yyyy', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/02/05', 'yyyy/M!/dd', new Date(1955, 1, 5, 0));
|
||||
expectParse('02-05-11', 'dd-M!-yy', new Date(2011, 4, 2, 0));
|
||||
expectParse('0001/03/06', 'yyyy/M!/dd', oldDate);
|
||||
});
|
||||
|
||||
it('should work correctly for `d`', function() {
|
||||
@@ -58,6 +361,37 @@ describe('date parser', function() {
|
||||
expectParse('February/5/1980', 'MMMM/d/yyyy', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/February/5', 'yyyy/MMMM/d', new Date(1955, 1, 5, 0));
|
||||
expectParse('11-08-13', 'd-MM-yy', new Date(2013, 7, 11, 0));
|
||||
expectParse('0001/03/6', 'yyyy/MM/d', oldDate);
|
||||
});
|
||||
|
||||
it('should work correctly for `d!`', function() {
|
||||
expectParse('17.November.13', 'd!.MMMM.yy', new Date(2013, 10, 17, 0));
|
||||
expectParse('8-March-1991', 'd!-MMMM-yyyy', new Date(1991, 2, 8, 0));
|
||||
expectParse('February/5/1980', 'MMMM/d!/yyyy', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/February/5', 'yyyy/MMMM/d!', new Date(1955, 1, 5, 0));
|
||||
expectParse('11-08-13', 'd!-MM-yy', new Date(2013, 7, 11, 0));
|
||||
expectParse('0001/03/6', 'yyyy/MM/d!', oldDate);
|
||||
|
||||
expectParse('17.November.13', 'd!.MMMM.yy', new Date(2013, 10, 17, 0));
|
||||
expectParse('08-March-1991', 'd!-MMMM-yyyy', new Date(1991, 2, 8, 0));
|
||||
expectParse('February/05/1980', 'MMMM/d!/yyyy', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/February/05', 'yyyy/MMMM/d!', new Date(1955, 1, 5, 0));
|
||||
expectParse('11-08-13', 'd!-MM-yy', new Date(2013, 7, 11, 0));
|
||||
expectParse('0001/03/06', 'yyyy/MM/d!', oldDate);
|
||||
});
|
||||
|
||||
it('should work correctly for `EEEE`', function() {
|
||||
expectParse('Sunday.17.November.13', 'EEEE.d.MMMM.yy', new Date(2013, 10, 17, 0));
|
||||
expectParse('8-Friday-March-1991', 'd-EEEE-MMMM-yyyy', new Date(1991, 2, 8, 0));
|
||||
expectParse('February/5/1980/Tuesday', 'MMMM/d/yyyy/EEEE', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/Saturday/February/5', 'yyyy/EEEE/MMMM/d', new Date(1955, 1, 5, 0));
|
||||
});
|
||||
|
||||
it('should work correctly for `EEE`', function() {
|
||||
expectParse('Sun.17.November.13', 'EEE.d.MMMM.yy', new Date(2013, 10, 17, 0));
|
||||
expectParse('8-Fri-March-1991', 'd-EEE-MMMM-yyyy', new Date(1991, 2, 8, 0));
|
||||
expectParse('February/5/1980/Tue', 'MMMM/d/yyyy/EEE', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/Sat/February/5', 'yyyy/EEE/MMMM/d', new Date(1955, 1, 5, 0));
|
||||
});
|
||||
|
||||
it('should work correctly for `HH`', function() {
|
||||
@@ -68,6 +402,14 @@ describe('date parser', function() {
|
||||
expectParse('11-08-13 23', 'd-MM-yy HH', new Date(2013, 7, 11, 23));
|
||||
});
|
||||
|
||||
it('should work correctly for `H`', function() {
|
||||
expectParse('22.March.15.22', 'd.MMMM.yy.H', new Date(2015, 2, 22, 22));
|
||||
expectParse('8-March-1991-11', 'd-MMMM-yyyy-H', new Date(1991, 2, 8, 11));
|
||||
expectParse('February/5/1980/0', 'MMMM/d/yyyy/H', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/February/5 3', 'yyyy/MMMM/d H', new Date(1955, 1, 5, 3));
|
||||
expectParse('11-08-13 23', 'd-MM-yy H', new Date(2013, 7, 11, 23));
|
||||
});
|
||||
|
||||
it('should work correctly for `hh`', function() {
|
||||
expectParse('22.March.15.22', 'd.MMMM.yy.hh', undefined);
|
||||
expectParse('22.March.15.12', 'd.MMMM.yy.hh', new Date(2015, 2, 22, 12));
|
||||
@@ -78,14 +420,6 @@ describe('date parser', function() {
|
||||
expectParse('11-08-13 09', 'd-MM-yy hh', new Date(2013, 7, 11, 9));
|
||||
});
|
||||
|
||||
it('should work correctly for `H`', function() {
|
||||
expectParse('22.March.15.22', 'd.MMMM.yy.H', new Date(2015, 2, 22, 22));
|
||||
expectParse('8-March-1991-11', 'd-MMMM-yyyy-H', new Date(1991, 2, 8, 11));
|
||||
expectParse('February/5/1980/0', 'MMMM/d/yyyy/H', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/February/5 3', 'yyyy/MMMM/d H', new Date(1955, 1, 5, 3));
|
||||
expectParse('11-08-13 23', 'd-MM-yy H', new Date(2013, 7, 11, 23));
|
||||
});
|
||||
|
||||
it('should work correctly for `h`', function() {
|
||||
expectParse('22.March.15.12', 'd.MMMM.yy.h', new Date(2015, 2, 22, 12));
|
||||
expectParse('8-March-1991-11', 'd-MMMM-yyyy-h', new Date(1991, 2, 8, 11));
|
||||
@@ -156,6 +490,70 @@ describe('date parser', function() {
|
||||
expectParse('11-08-13 09AM', 'd-MM-yy hha', new Date(2013, 7, 11, 9));
|
||||
expectParse('11-08-13 09PM', 'd-MM-yy hha', new Date(2013, 7, 11, 21));
|
||||
});
|
||||
|
||||
it('should work correctly for `Z`', function() {
|
||||
expectParse('22.March.15 -0700', 'd.MMMM.yy Z', new Date(2015, 2, 21, 17, 0, 0));
|
||||
expectParse('8-March-1991 +0800', 'd-MMMM-yyyy Z', new Date(1991, 2, 8, 8, 0, 0));
|
||||
expectParse('February/5/1980 -0200', 'MMMM/d/yyyy Z', new Date(1980, 1, 4, 22, 0, 0));
|
||||
expectParse('1955/February/5 +0400', 'yyyy/MMMM/d Z', new Date(1955, 1, 5, 4, 0, 0));
|
||||
expectParse('11-08-13 -1234', 'd-MM-yy Z', new Date(2013, 7, 10, 11, 26, 0));
|
||||
expectParse('22.March.15.22:33:4 -1200', 'd.MMMM.yy.HH:mm:s Z', new Date(2015, 2, 22, 10, 33, 4));
|
||||
expectParse('22.March.15.22:3:4 +1500', 'd.MMMM.yy.HH:m:s Z', new Date(2015, 2, 23, 13, 3, 4));
|
||||
});
|
||||
|
||||
it('should work correctly for `ww`', function() {
|
||||
expectParse('17.November.13.45', 'd.MMMM.yy.ww', new Date(2013, 10, 17, 0));
|
||||
expectParse('8-March-1991-09', 'd-MMMM-yyyy-ww', new Date(1991, 2, 8, 0));
|
||||
expectParse('February/5/1980/05', 'MMMM/d/yyyy/ww', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/February/5/04', 'yyyy/MMMM/d/ww', new Date(1955, 1, 5, 0));
|
||||
expectParse('11-08-13 44', 'd-MM-yy ww', new Date(2013, 7, 11, 0));
|
||||
expectParse('0001/03/6 10', 'yyyy/MM/d ww', oldDate);
|
||||
});
|
||||
|
||||
it('should work correctly for `w`', function() {
|
||||
expectParse('17.November.13.45', 'd.MMMM.yy.w', new Date(2013, 10, 17, 0));
|
||||
expectParse('8-March-1991-9', 'd-MMMM-yyyy-w', new Date(1991, 2, 8, 0));
|
||||
expectParse('February/5/1980/5', 'MMMM/d/yyyy/w', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/February/5/4', 'yyyy/MMMM/d/w', new Date(1955, 1, 5, 0));
|
||||
expectParse('11-08-13 44', 'd-MM-yy w', new Date(2013, 7, 11, 0));
|
||||
expectParse('0001/03/6 10', 'yyyy/MM/d w', oldDate);
|
||||
});
|
||||
|
||||
it('should work correctly for `G`', function() {
|
||||
expectParse('17.November.13.AD', 'd.MMMM.yy.G', new Date(2013, 10, 17, 0));
|
||||
expectParse('8-March-1991-BC', 'd-MMMM-yyyy-G', new Date(1991, 2, 8, 0));
|
||||
expectParse('February/5/1980/AD', 'MMMM/d/yyyy/G', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/February/5/BC', 'yyyy/MMMM/d/G', new Date(1955, 1, 5, 0));
|
||||
expectParse('11-08-13 AD', 'd-MM-yy G', new Date(2013, 7, 11, 0));
|
||||
expectParse('0001/03/6 BC', 'yyyy/MM/d G', oldDate);
|
||||
});
|
||||
|
||||
it('should work correctly for `GG`', function() {
|
||||
expectParse('17.November.13.AD', 'd.MMMM.yy.GG', new Date(2013, 10, 17, 0));
|
||||
expectParse('8-March-1991-BC', 'd-MMMM-yyyy-GG', new Date(1991, 2, 8, 0));
|
||||
expectParse('February/5/1980/AD', 'MMMM/d/yyyy/GG', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/February/5/BC', 'yyyy/MMMM/d/GG', new Date(1955, 1, 5, 0));
|
||||
expectParse('11-08-13 AD', 'd-MM-yy GG', new Date(2013, 7, 11, 0));
|
||||
expectParse('0001/03/6 BC', 'yyyy/MM/d GG', oldDate);
|
||||
});
|
||||
|
||||
it('should work correctly for `GGG`', function() {
|
||||
expectParse('17.November.13.AD', 'd.MMMM.yy.GGG', new Date(2013, 10, 17, 0));
|
||||
expectParse('8-March-1991-BC', 'd-MMMM-yyyy-GGG', new Date(1991, 2, 8, 0));
|
||||
expectParse('February/5/1980/AD', 'MMMM/d/yyyy/GGG', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/February/5/BC', 'yyyy/MMMM/d/GGG', new Date(1955, 1, 5, 0));
|
||||
expectParse('11-08-13 AD', 'd-MM-yy GGG', new Date(2013, 7, 11, 0));
|
||||
expectParse('0001/03/6 BC', 'yyyy/MM/d GGG', oldDate);
|
||||
});
|
||||
|
||||
it('should work correctly for `GGGG`', function() {
|
||||
expectParse('17.November.13.Anno Domini', 'd.MMMM.yy.GGGG', new Date(2013, 10, 17, 0));
|
||||
expectParse('8-March-1991-Before Christ', 'd-MMMM-yyyy-GGGG', new Date(1991, 2, 8, 0));
|
||||
expectParse('February/5/1980/Anno Domini', 'MMMM/d/yyyy/GGGG', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/February/5/Before Christ', 'yyyy/MMMM/d/GGGG', new Date(1955, 1, 5, 0));
|
||||
expectParse('11-08-13 Anno Domini', 'd-MM-yy GGGG', new Date(2013, 7, 11, 0));
|
||||
expectParse('0001/03/6 Before Christ', 'yyyy/MM/d GGGG', oldDate);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with predefined formats', function() {
|
||||
@@ -176,13 +574,59 @@ describe('date parser', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('with value literals', function() {
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
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 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 only a single quote', function() {
|
||||
expect(dateParser.parse('22.March.15 \'', 'd.MMMM.yy \'\'\'')).toEqual(new Date(2015, 2, 22));
|
||||
});
|
||||
|
||||
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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with edge case', function() {
|
||||
it('should not work for invalid number of days in February', function() {
|
||||
expect(dateParser.parse('29.02.2013', 'dd.MM.yyyy')).toBeUndefined();
|
||||
expectParse('29.02.2013', 'dd.MM.yyyy', undefined);
|
||||
});
|
||||
|
||||
it('should not work for 0 number of days', function() {
|
||||
expect(dateParser.parse('00.02.2013', 'dd.MM.yyyy')).toBeUndefined();
|
||||
expectParse('00.02.2013', 'dd.MM.yyyy', undefined);
|
||||
});
|
||||
|
||||
it('should work for 29 days in February for leap years', function() {
|
||||
@@ -190,27 +634,40 @@ describe('date parser', function() {
|
||||
});
|
||||
|
||||
it('should not work for 31 days for some months', function() {
|
||||
expect(dateParser.parse('31-04-2013', 'dd-MM-yyyy')).toBeUndefined();
|
||||
expect(dateParser.parse('November 31, 2013', 'MMMM d, yyyy')).toBeUndefined();
|
||||
expectParse('31-04-2013', 'dd-MM-yyyy', undefined);
|
||||
expectParse('November 31, 2013', 'MMMM d, yyyy', undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('base date', function() {
|
||||
var baseDate;
|
||||
|
||||
beforeEach(function() {
|
||||
baseDate = new Date(2010, 10, 10);
|
||||
});
|
||||
|
||||
it('should work when base date is a string', function() {
|
||||
expect(dateParser.parse('01-02-2034', 'dd-MM-yyyy', '05-06-2078')).toEqual(new Date(2034, 1, 1));
|
||||
it('should pre-initialize our date with a base date', function() {
|
||||
expect(expectBaseParse('2015', 'yyyy', baseDate, new Date(2015, 10, 10)));
|
||||
expect(expectBaseParse('1', 'M', baseDate, new Date(2010, 0, 10)));
|
||||
expect(expectBaseParse('1', 'd', baseDate, new Date(2010, 10, 1)));
|
||||
});
|
||||
|
||||
it('should work when base date is an invalid date', function() {
|
||||
expect(dateParser.parse('30-12-2015', 'dd-MM-yyyy', new Date('foo'))).toEqual(new Date(2015, 11, 30));
|
||||
});
|
||||
it('should ignore the base date when it is an invalid date', inject(function($log) {
|
||||
spyOn($log, 'warn');
|
||||
expect(expectBaseParse('30-12', 'dd-MM', new Date('foo'), new Date(1900, 11, 30)));
|
||||
expect(expectBaseParse('30-2015', 'dd-yyyy', 'I am a cat', new Date(2015, 0, 30)));
|
||||
expect($log.warn).toHaveBeenCalledWith('dateparser:', 'baseDate is not a valid date');
|
||||
}));
|
||||
});
|
||||
|
||||
it('should not parse non-string inputs', function() {
|
||||
expect(dateParser.parse(123456, 'dd.MM.yyyy')).toBe(123456);
|
||||
expectParse(123456, 'dd.MM.yyyy', 123456);
|
||||
var date = new Date();
|
||||
expect(dateParser.parse(date, 'dd.MM.yyyy')).toBe(date);
|
||||
expectParse(date, 'dd.MM.yyyy', date);
|
||||
});
|
||||
|
||||
it('should not parse if no format is specified', function() {
|
||||
expect(dateParser.parse('21.08.1951', '')).toBe('21.08.1951');
|
||||
expectParse('21.08.1951', '', '21.08.1951');
|
||||
});
|
||||
|
||||
it('should reinitialize when locale changes', inject(function($locale) {
|
||||
@@ -223,35 +680,139 @@ describe('date parser', function() {
|
||||
|
||||
expect(dateParser.init).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
/* Deprecation tests below */
|
||||
describe('timezone functions', function() {
|
||||
describe('toTimezone', function() {
|
||||
it('adjusts date: PST - EST', function() {
|
||||
var date = new Date('2008-01-01T00:00:00.000Z');
|
||||
var toWestDate = dateParser.toTimezone(date, 'PST');
|
||||
var toEastDate = dateParser.toTimezone(date, 'EST');
|
||||
expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 3);
|
||||
});
|
||||
|
||||
describe('date parser deprecation', function() {
|
||||
beforeEach(module('ui.bootstrap.dateparser'));
|
||||
it('adjusts date: GMT-0500 - GMT+0500', function() {
|
||||
var date = new Date('2008-01-01T00:00:00.000Z');
|
||||
var toWestDate = dateParser.toTimezone(date, 'GMT-0500');
|
||||
var toEastDate = dateParser.toTimezone(date, 'GMT+0500');
|
||||
expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 10);
|
||||
});
|
||||
|
||||
it('should suppress warning', function() {
|
||||
module(function($provide) {
|
||||
$provide.value('$dateParserSuppressWarning', true);
|
||||
it('adjusts date: -600 - +600', function() {
|
||||
var date = new Date('2008-01-01T00:00:00.000Z');
|
||||
var toWestDate = dateParser.toTimezone(date, '-600');
|
||||
var toEastDate = dateParser.toTimezone(date, '+600');
|
||||
expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 12);
|
||||
});
|
||||
|
||||
it('tolerates null date', function() {
|
||||
var date = null;
|
||||
var toNullDate = dateParser.toTimezone(date, '-600');
|
||||
expect(toNullDate).toEqual(date);
|
||||
});
|
||||
|
||||
it('tolerates null timezone', function() {
|
||||
var date = new Date('2008-01-01T00:00:00.000Z');
|
||||
var toNullTimezoneDate = dateParser.toTimezone(date, null);
|
||||
expect(toNullTimezoneDate).toEqual(date);
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($log, dateParser) {
|
||||
spyOn($log, 'warn');
|
||||
describe('fromTimezone', function() {
|
||||
it('adjusts date: PST - EST', function() {
|
||||
var date = new Date('2008-01-01T00:00:00.000Z');
|
||||
var fromWestDate = dateParser.fromTimezone(date, 'PST');
|
||||
var fromEastDate = dateParser.fromTimezone(date, 'EST');
|
||||
expect(fromWestDate.getTime() - fromEastDate.getTime()).toEqual(1000 * 60 * 60 * -3);
|
||||
});
|
||||
|
||||
dateParser.parse('01.10.2015', 'dd.MM.yyyy');
|
||||
it('adjusts date: GMT-0500 - GMT+0500', function() {
|
||||
var date = new Date('2008-01-01T00:00:00.000Z');
|
||||
var fromWestDate = dateParser.fromTimezone(date, 'GMT-0500');
|
||||
var fromEastDate = dateParser.fromTimezone(date, 'GMT+0500');
|
||||
expect(fromWestDate.getTime() - fromEastDate.getTime()).toEqual(1000 * 60 * 60 * -10);
|
||||
});
|
||||
|
||||
expect($log.warn.calls.count()).toBe(0);
|
||||
it('adjusts date: -600 - +600', function() {
|
||||
var date = new Date('2008-01-01T00:00:00.000Z');
|
||||
var fromWestDate = dateParser.fromTimezone(date, '-600');
|
||||
var fromEastDate = dateParser.fromTimezone(date, '+600');
|
||||
expect(fromWestDate.getTime() - fromEastDate.getTime()).toEqual(1000 * 60 * 60 * -12);
|
||||
});
|
||||
|
||||
it('tolerates null date', function() {
|
||||
var date = null;
|
||||
var toNullDate = dateParser.fromTimezone(date, '-600');
|
||||
expect(toNullDate).toEqual(date);
|
||||
});
|
||||
|
||||
it('tolerates null timezone', function() {
|
||||
var date = new Date('2008-01-01T00:00:00.000Z');
|
||||
var toNullTimezoneDate = dateParser.fromTimezone(date, null);
|
||||
expect(toNullTimezoneDate).toEqual(date);
|
||||
});
|
||||
});
|
||||
|
||||
describe('timezoneToOffset', function() {
|
||||
it('calculates minutes off from current timezone', function() {
|
||||
var offsetMinutesUtc = dateParser.timezoneToOffset('UTC');
|
||||
var offsetMinutesUtcPlus1 = dateParser.timezoneToOffset('GMT+0100');
|
||||
expect(offsetMinutesUtc - offsetMinutesUtcPlus1).toEqual(60);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addDateMinutes', function() {
|
||||
it('adds minutes to a date', function() {
|
||||
var date = new Date('2008-01-01T00:00:00.000Z');
|
||||
var oneHourMore = dateParser.addDateMinutes(date, 60);
|
||||
expect(oneHourMore).toEqual(new Date('2008-01-01T01:00:00.000Z'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertTimezoneToLocal', function() {
|
||||
it('adjusts date: PST - EST', function() {
|
||||
var date = new Date('2008-01-01T00:00:00.000Z');
|
||||
var toWestDate = dateParser.convertTimezoneToLocal(date, 'PST');
|
||||
var toEastDate = dateParser.convertTimezoneToLocal(date, 'EST');
|
||||
expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 3);
|
||||
});
|
||||
|
||||
it('adjusts date: GMT-0500 - GMT+0500', function() {
|
||||
var date = new Date('2008-01-01T00:00:00.000Z');
|
||||
var toWestDate = dateParser.convertTimezoneToLocal(date, 'GMT-0500');
|
||||
var toEastDate = dateParser.convertTimezoneToLocal(date, 'GMT+0500');
|
||||
expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 10);
|
||||
});
|
||||
|
||||
it('adjusts date: -600 - +600', function() {
|
||||
var date = new Date('2008-01-01T00:00:00.000Z');
|
||||
var toWestDate = dateParser.convertTimezoneToLocal(date, '-600');
|
||||
var toEastDate = dateParser.convertTimezoneToLocal(date, '+600');
|
||||
expect(toWestDate.getTime() - toEastDate.getTime()).toEqual(1000 * 60 * 60 * 12);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should give warning by default', inject(function($log) {
|
||||
spyOn($log, 'warn');
|
||||
describe('overrideParser', function() {
|
||||
var twoDigitYearParser = function (value) {
|
||||
this.year = +value + (+value > 30 ? 1900 : 2000);
|
||||
};
|
||||
|
||||
inject(function(dateParser) {
|
||||
dateParser.parse('01.10.2015', 'dd.MM.yyyy');
|
||||
|
||||
expect($log.warn.calls.count()).toBe(1);
|
||||
expect($log.warn.calls.argsFor(0)).toEqual(['dateParser is now deprecated. Use uibDateParser instead.']);
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
.uib-datepicker .uib-title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.uib-day button, .uib-month button, .uib-year button {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.uib-left, .uib-right {
|
||||
width: 100%
|
||||
}
|
||||
+289
-790
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" min-date="minDate" show-weeks="true" class="well well-sm" custom-class="getDayClass(date, mode)"></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="status.opened" min-date="minDate" max-date="maxDate" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" />
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<p class="input-group">
|
||||
<input type="date" class="form-control" uib-datepicker-popup ng-model="dt" is-open="status.opened" min-date="minDate" max-date="maxDate" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" />
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label>Format:</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 />
|
||||
|
||||
+31
-38
@@ -4,62 +4,55 @@ angular.module('ui.bootstrap.demo').controller('DatepickerDemoCtrl', function ($
|
||||
};
|
||||
$scope.today();
|
||||
|
||||
$scope.clear = function () {
|
||||
$scope.clear = function() {
|
||||
$scope.dt = null;
|
||||
};
|
||||
|
||||
// Disable weekend selection
|
||||
$scope.disabled = function(date, mode) {
|
||||
return ( mode === 'day' && ( date.getDay() === 0 || date.getDay() === 6 ) );
|
||||
$scope.options = {
|
||||
customClass: getDayClass,
|
||||
minDate: new Date(),
|
||||
showWeeks: true
|
||||
};
|
||||
|
||||
// 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.minDate = $scope.minDate ? null : new Date();
|
||||
$scope.options.minDate = $scope.options.minDate ? null : new Date();
|
||||
};
|
||||
$scope.toggleMin();
|
||||
$scope.maxDate = new Date(2020, 5, 22);
|
||||
|
||||
$scope.open = function($event) {
|
||||
$scope.status.opened = true;
|
||||
};
|
||||
$scope.toggleMin();
|
||||
|
||||
$scope.setDate = function(year, month, day) {
|
||||
$scope.dt = new Date(year, month, day);
|
||||
};
|
||||
|
||||
$scope.dateOptions = {
|
||||
formatYear: 'yy',
|
||||
startingDay: 1
|
||||
};
|
||||
|
||||
$scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
|
||||
$scope.format = $scope.formats[0];
|
||||
|
||||
$scope.status = {
|
||||
opened: false
|
||||
};
|
||||
|
||||
var tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
var afterTomorrow = new Date();
|
||||
afterTomorrow.setDate(tomorrow.getDate() + 2);
|
||||
$scope.events =
|
||||
[
|
||||
{
|
||||
date: tomorrow,
|
||||
status: 'full'
|
||||
},
|
||||
{
|
||||
date: afterTomorrow,
|
||||
status: 'partially'
|
||||
}
|
||||
];
|
||||
var afterTomorrow = new Date(tomorrow);
|
||||
afterTomorrow.setDate(tomorrow.getDate() + 1);
|
||||
$scope.events = [
|
||||
{
|
||||
date: tomorrow,
|
||||
status: 'full'
|
||||
},
|
||||
{
|
||||
date: afterTomorrow,
|
||||
status: 'partially'
|
||||
}
|
||||
];
|
||||
|
||||
$scope.getDayClass = function(date, mode) {
|
||||
function getDayClass(data) {
|
||||
var date = data.date,
|
||||
mode = data.mode;
|
||||
if (mode === 'day') {
|
||||
var dayToCheck = new Date(date).setHours(0,0,0,0);
|
||||
|
||||
for (var i=0;i<$scope.events.length;i++){
|
||||
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) {
|
||||
@@ -69,5 +62,5 @@ angular.module('ui.bootstrap.demo').controller('DatepickerDemoCtrl', function ($
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
+118
-114
@@ -1,145 +1,145 @@
|
||||
A clean, flexible, and fully customizable date picker.
|
||||
Our datepicker is flexible and fully customizable.
|
||||
|
||||
User can navigate through months and years.
|
||||
The datepicker shows dates that come from other than the main month being displayed. These other dates are also selectable.
|
||||
You can navigate through days, months and years.
|
||||
|
||||
Everything is formatted using the [date filter](http://docs.angularjs.org/api/ng.filter:date) and thus is also localized.
|
||||
The datepicker has 3 modes:
|
||||
|
||||
### Datepicker Settings ###
|
||||
* `day` - In this mode you're presented with a 6-week calendar for a specified month.
|
||||
* `month` - In this mode you can select a month within a selected year.
|
||||
* `year` - In this mode you are presented with a range of years (20 by default).
|
||||
|
||||
All settings can be provided as attributes in the `uib-datepicker` or globally configured through the `uibDatepickerConfig`.
|
||||
### uib-datepicker settings
|
||||
|
||||
* `ng-model` <i class="glyphicon glyphicon-eye-open"></i>
|
||||
:
|
||||
The date object.
|
||||
* `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.
|
||||
|
||||
* `datepicker-mode` <i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Defaults: 'day')_ :
|
||||
Current mode of the datepicker _(day|month|year)_. Can be used to initialize datepicker to specific mode.
|
||||
* `ng-model-options`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
_(Default: `{}`)_ -
|
||||
Supported [angular ngModelOptions](https://docs.angularjs.org/api/ng/directive/ngModelOptions):
|
||||
* allowInvalid
|
||||
* timezone
|
||||
|
||||
* `min-date` <i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: null)_ :
|
||||
Defines the minimum available date.
|
||||
* `template-url`
|
||||
_(Default: `uib/template/datepicker/datepicker.html`)_ -
|
||||
Add the ability to override the template used on the component.
|
||||
|
||||
* `max-date` <i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: null)_ :
|
||||
Defines the maximum available date.
|
||||
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:
|
||||
|
||||
* `date-disabled (date, mode)`
|
||||
_(Default: null)_ :
|
||||
An optional expression to disable visible options based on passing date and current mode _(day|month|year)_.
|
||||
|
||||
* `custom-class (date, mode)`
|
||||
_(Default: null)_ :
|
||||
An optional expression to add classes based on passing date and current mode _(day|month|year)_.
|
||||
* `datepicker-options`
|
||||
<small class="badge">$</small> -
|
||||
An object to configure the datepicker in one place.
|
||||
|
||||
* `show-weeks`
|
||||
_(Defaults: true)_ :
|
||||
Whether to display week numbers.
|
||||
* `customClass ({date: date, mode: mode})` -
|
||||
An optional expression to add classes based on passing an object with date and current mode properties.
|
||||
|
||||
* `starting-day`
|
||||
_(Defaults: 0)_ :
|
||||
Starting day of the week from 0-6 (0=Sunday, ..., 6=Saturday).
|
||||
* `dateDisabled ({date: date, mode: mode})` -
|
||||
An optional expression to disable visible options based on passing an object with date and current mode properties.
|
||||
|
||||
* `init-date`
|
||||
:
|
||||
The initial date view when no model value is specified.
|
||||
* `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.
|
||||
|
||||
* `min-mode`
|
||||
_(Defaults: 'day')_ :
|
||||
Set a lower limit for mode.
|
||||
* `formatDay`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `dd`)_ -
|
||||
Format of day in month.
|
||||
|
||||
* `max-mode`
|
||||
_(Defaults: 'year')_ :
|
||||
Set an upper limit for mode.
|
||||
* `formatMonth`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `MMMM`)_ -
|
||||
Format of month in year.
|
||||
|
||||
* `format-day`
|
||||
_(Default: 'dd')_ :
|
||||
Format of day in month.
|
||||
* `formatYear`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `yyyy`)_ -
|
||||
Format of year in year range.
|
||||
|
||||
* `format-month`
|
||||
_(Default: 'MMMM')_ :
|
||||
Format of month in year.
|
||||
* `formatDayHeader`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `EEE`)_ -
|
||||
Format of day in week header.
|
||||
|
||||
* `format-year`
|
||||
_(Default: 'yyyy')_ :
|
||||
Format of year in year range.
|
||||
* `formatDayTitle`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `MMMM yyyy`)_ -
|
||||
Format of title when selecting day.
|
||||
|
||||
* `format-day-header`
|
||||
_(Default: 'EEE')_ :
|
||||
Format of day in week header.
|
||||
* `formatMonthTitle`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `yyyy`)_ -
|
||||
Format of title when selecting month.
|
||||
|
||||
* `format-day-title`
|
||||
_(Default: 'MMMM yyyy')_ :
|
||||
Format of title when selecting day.
|
||||
* `initDate`
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `null`)_ -
|
||||
The initial date view when no model value is specified.
|
||||
|
||||
* `format-month-title`
|
||||
_(Default: 'yyyy')_ :
|
||||
Format of title when selecting month.
|
||||
* `maxDate`
|
||||
<small class="badge">C</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `null`)_ -
|
||||
Defines the maximum available date. Requires a Javascript Date object.
|
||||
|
||||
* `year-range`
|
||||
_(Default: 20)_ :
|
||||
Number of years displayed in year selection.
|
||||
* `maxMode`
|
||||
<small class="badge">C</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `year`)_ -
|
||||
Sets an upper limit for mode.
|
||||
|
||||
* `shortcut-propagation`
|
||||
_(Default: false)_ :
|
||||
An option to disable or enable shortcut's event propagation.
|
||||
* `minDate`
|
||||
<small class="badge">C</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `null`)_ -
|
||||
Defines the minimum available date. Requires a Javascript Date object.
|
||||
|
||||
* `template-url`
|
||||
_(Default: 'template/datepicker/datepicker.html')_ :
|
||||
Allows overriding of default template of the datepicker
|
||||
* `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
|
||||
|
||||
### Popup Settings ###
|
||||
* `shortcutPropagation`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `false`)_ -
|
||||
An option to disable the propagation of the keydown event.
|
||||
|
||||
Options for datepicker can be passed as JSON using the `datepicker-options` attribute.
|
||||
Specific settings for the `uib-datepicker-popup`, that can globally configured through the `uibDatepickerPopupConfig`, are:
|
||||
* `showWeeks`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `true`)_ -
|
||||
Whether to display week numbers.
|
||||
|
||||
* `uib-datepicker-popup`
|
||||
_(Default: 'yyyy-MM-dd')_ :
|
||||
The format for displayed dates.
|
||||
* `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.
|
||||
|
||||
* `show-button-bar`
|
||||
_(Default: true)_ :
|
||||
Whether to display a button bar underneath the datepicker.
|
||||
* `yearColumns`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `5`)_ -
|
||||
Number of columns displayed in year selection.
|
||||
|
||||
* `current-text`
|
||||
_(Default: 'Today')_ :
|
||||
The text to display for the current day button.
|
||||
|
||||
* `clear-text`
|
||||
_(Default: 'Clear')_ :
|
||||
The text to display for the clear button.
|
||||
|
||||
* `close-text`
|
||||
_(Default: 'Done')_ :
|
||||
The text to display for the close button.
|
||||
|
||||
* `close-on-date-selection`
|
||||
_(Default: true)_ :
|
||||
Whether to close calendar when a date is chosen.
|
||||
|
||||
* `datepicker-popup-template-url`
|
||||
_(Default: 'template/datepicker/popup.html')_ :
|
||||
Allows overriding of default template of the popup
|
||||
|
||||
* `datepicker-template-url`
|
||||
_(Default: 'template/datepicker/datepicker.html')_ :
|
||||
Allows overriding of default template of the datepicker used in popup
|
||||
|
||||
* `datepicker-append-to-body`
|
||||
_(Default: false)_:
|
||||
Append the datepicker popup element to `body`, rather than inserting after `datepicker-popup`. For global configuration, use `uibDatepickerPopupConfig.appendToBody`.
|
||||
|
||||
* `is-open` <i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: false)_:
|
||||
Whether to show the datepicker.
|
||||
|
||||
* `on-open-focus`
|
||||
_(Default: true)_:
|
||||
Whether to focus the datepicker popup upon opening.
|
||||
|
||||
### Keyboard Support ###
|
||||
### 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.
|
||||
|
||||
@@ -154,4 +154,8 @@ Depending on datepicker's current mode, the date may refer either to day, month
|
||||
* `Enter`/`Space`: Select date.
|
||||
* `Ctrl`+`Up`: Move to an upper mode.
|
||||
* `Ctrl`+`Down`: Move to a lower mode.
|
||||
* `Esc`: Will close popup, and move focus to the input.
|
||||
* `Esc`: Will close popup, and move focus to the input.
|
||||
|
||||
**Notes**
|
||||
|
||||
If the date a user enters falls outside of the min-/max-date range, a `dateDisabled` validation error will show on the form.
|
||||
|
||||
@@ -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;
|
||||
@@ -0,0 +1,2 @@
|
||||
require('./datepicker.css');
|
||||
module.exports = require('./index-nocss.js');
|
||||
+625
-1384
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
@@ -0,0 +1,21 @@
|
||||
angular.module('ui.bootstrap.debounce', [])
|
||||
/**
|
||||
* A helper, internal service that debounces a function
|
||||
*/
|
||||
.factory('$$debounce', ['$timeout', function($timeout) {
|
||||
return function(callback, debounceTime) {
|
||||
var timeoutPromise;
|
||||
|
||||
return function() {
|
||||
var self = this;
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
if (timeoutPromise) {
|
||||
$timeout.cancel(timeoutPromise);
|
||||
}
|
||||
|
||||
timeoutPromise = $timeout(function() {
|
||||
callback.apply(self, args);
|
||||
}, debounceTime);
|
||||
};
|
||||
};
|
||||
}]);
|
||||
@@ -0,0 +1,7 @@
|
||||
require('./debounce');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.debounce';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.debounce']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
@@ -0,0 +1,48 @@
|
||||
describe('$$debounce', function() {
|
||||
var $$debounce, $timeout, debouncedFunction, i, args;
|
||||
|
||||
beforeEach(module('ui.bootstrap.debounce'));
|
||||
beforeEach(inject(function(_$$debounce_, _$timeout_) {
|
||||
$$debounce = _$$debounce_;
|
||||
$timeout = _$timeout_;
|
||||
i = 0;
|
||||
debouncedFunction = $$debounce(function() {
|
||||
args = Array.prototype.slice.call(arguments);
|
||||
i++;
|
||||
}, 100);
|
||||
}));
|
||||
|
||||
it('should function like a $timeout when called once during timeout', function() {
|
||||
debouncedFunction();
|
||||
$timeout.flush(50);
|
||||
|
||||
expect(i).toBe(0);
|
||||
|
||||
$timeout.flush(50);
|
||||
|
||||
expect(i).toBe(1);
|
||||
});
|
||||
|
||||
it('should only execute 100ms after last call when called twice', function() {
|
||||
debouncedFunction();
|
||||
$timeout.flush(50);
|
||||
|
||||
expect(i).toBe(0);
|
||||
|
||||
debouncedFunction();
|
||||
$timeout.flush(50);
|
||||
|
||||
expect(i).toBe(0);
|
||||
|
||||
$timeout.flush(50);
|
||||
|
||||
expect(i).toBe(1);
|
||||
});
|
||||
|
||||
it('should properly pass arguments to debounced function', function() {
|
||||
debouncedFunction(1, 2, 3);
|
||||
$timeout.flush(100);
|
||||
|
||||
expect(args).toEqual([1, 2, 3]);
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,7 @@
|
||||
<a href id="simple-dropdown" uib-dropdown-toggle>
|
||||
Click me for a dropdown, yo!
|
||||
</a>
|
||||
<ul class="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 class="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 class="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 class="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>
|
||||
@@ -55,13 +55,13 @@
|
||||
<li role="menuitem"><a href="#">Separated link</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Single button using template-url -->
|
||||
<div class="btn-group" uib-dropdown>
|
||||
<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 class="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>
|
||||
|
||||
@@ -73,11 +73,11 @@
|
||||
|
||||
<hr>
|
||||
<!-- Single button with keyboard nav -->
|
||||
<div class="btn-group" uib-dropdown uib-keyboard-nav>
|
||||
<div class="btn-group" uib-dropdown keyboard-nav>
|
||||
<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 class="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>
|
||||
@@ -86,8 +86,54 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<!-- AppendTo use case -->
|
||||
<h4>append-to vs. append-to-body vs. inline example</h4>
|
||||
<div id="dropdown-scrollable-container" style="height: 15em; overflow: auto;">
|
||||
<div id="dropdown-long-content">
|
||||
<div id="dropdown-hidden-container">
|
||||
<div class="btn-group" uib-dropdown keyboard-nav dropdown-append-to="appendToEl">
|
||||
<button id="btn-append-to" type="button" class="btn btn-primary" uib-dropdown-toggle>
|
||||
Dropdown in Container <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="btn-append-to">
|
||||
<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>
|
||||
<li class="divider"></li>
|
||||
<li role="menuitem"><a href="#">Separated link</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="btn-group" uib-dropdown dropdown-append-to-body>
|
||||
<button id="btn-append-to-to-body" type="button" class="btn btn-primary" uib-dropdown-toggle>
|
||||
Dropdown on Body <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="btn-append-to-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>
|
||||
<li class="divider"></li>
|
||||
<li role="menuitem"><a href="#">Separated link</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="btn-group" uib-dropdown>
|
||||
<button id="btn-append-to-single-button" type="button" class="btn btn-primary" uib-dropdown-toggle>
|
||||
Inline Dropdown <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="btn-append-to-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>
|
||||
<li class="divider"></li>
|
||||
<li role="menuitem"><a href="#">Separated link</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/ng-template" id="dropdown.html">
|
||||
<ul class="uib-dropdown-menu" role="menu" aria-labelledby="button-template-url">
|
||||
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="button-template-url">
|
||||
<li role="menuitem"><a href="#">Action in Template</a></li>
|
||||
<li role="menuitem"><a href="#">Another action in Template</a></li>
|
||||
<li role="menuitem"><a href="#">Something else here</a></li>
|
||||
|
||||
@@ -18,4 +18,6 @@ angular.module('ui.bootstrap.demo').controller('DropdownCtrl', function ($scope,
|
||||
$event.stopPropagation();
|
||||
$scope.status.isopen = !$scope.status.isopen;
|
||||
};
|
||||
|
||||
$scope.appendToEl = angular.element(document.querySelector('#dropdown-long-content'));
|
||||
});
|
||||
|
||||
+56
-12
@@ -1,19 +1,63 @@
|
||||
|
||||
Dropdown is a simple directive which will toggle a dropdown menu on click or programmatically.
|
||||
You can either use `is-open` to toggle or add inside a `<a uib-dropdown-toggle>` element to toggle it when is clicked.
|
||||
There is also the `on-toggle(open)` optional expression fired when dropdown changes state.
|
||||
|
||||
Add `dropdown-append-to-body` to the `uib-dropdown` element to append to the inner `dropdown-menu` to the body.
|
||||
This is useful when the dropdown button is inside a div with `overflow: hidden`, and the menu would otherwise be hidden.
|
||||
This directive is composed by three parts:
|
||||
|
||||
Add `uib-keyboard-nav` to the `uib-dropdown` element to enable navigation of dropdown list elements with the arrow keys.
|
||||
* `uib-dropdown` which transforms a node into a dropdown.
|
||||
* `uib-dropdown-toggle` which allows the dropdown to be toggled via click. This directive is optional.
|
||||
* `uib-dropdown-menu` which transforms a node into the popup menu.
|
||||
|
||||
By default the dropdown will automatically close if any of its elements is clicked, you can change this behavior by setting the `auto-close` option as follows:
|
||||
Each of these parts need to be used as attribute directives.
|
||||
|
||||
* `always` - (Default) automatically closes the dropdown when any of its elements is clicked.
|
||||
* `outsideClick` - closes the dropdown automatically only when the user clicks any element outside the dropdown.
|
||||
* `disabled` - disables the auto close. You can then control the open/close status of the dropdown manually, by using `is-open`. Please notice that the dropdown will still close if the toggle is clicked, the `esc` key is pressed or another dropdown is open. The dropdown will no longer close on `$locationChangeSuccess` events.
|
||||
### uib-dropdown settings
|
||||
|
||||
Optionally, you may specify a template for the dropdown menu using the `template-url` attribute. This is especially useful when you have multiple similar dropdowns in a repeater and you want to keep your HTML output lean and your number of scopes to a minimum. The template has full access to the scope in which the dropdown lies.
|
||||
* `auto-close`
|
||||
_(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.
|
||||
* `outsideClick` - Closes the dropdown automatically only when the user clicks any element outside the dropdown.
|
||||
|
||||
Example: `<ul class="uib-dropdown-menu" template-url="custom-dropdown.html"></ul>`.
|
||||
* `dropdown-append-to`
|
||||
<small class="badge">$</small>
|
||||
_(Default: `null`)_ -
|
||||
Appends the inner dropdown-menu to an arbitrary DOM element.
|
||||
|
||||
* `dropdown-append-to-body`
|
||||
<small class="badge">B</small>
|
||||
_(Default: `false`)_ -
|
||||
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>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `false`)_ -
|
||||
Defines whether or not the dropdown-menu is open. The `uib-dropdown-toggle` will toggle this attribute on click.
|
||||
|
||||
* `keyboard-nav`:
|
||||
<small class="badge">B</small>
|
||||
_(Default: `false`)_ -
|
||||
Enables navigation of dropdown list elements with the arrow keys.
|
||||
|
||||
* `on-toggle(open)`
|
||||
<small class="badge">$</small> -
|
||||
An optional expression called when the dropdown menu is opened or closed.
|
||||
|
||||
### uib-dropdown-menu settings
|
||||
|
||||
* `template-url`
|
||||
_(Default: `none`)_ -
|
||||
You may specify a template for the dropdown menu. Check the demos for an example.
|
||||
|
||||
### Additional settings `uibDropdownConfig`
|
||||
|
||||
* `appendToOpenClass`
|
||||
_(Default: `uib-dropdown-open`)_ -
|
||||
Class to apply when the dropdown is open and appended to a different DOM element.
|
||||
|
||||
* `openClass`
|
||||
_(Default: `open`)_ -
|
||||
Class to apply when the dropdown is open.
|
||||
|
||||
### Known issues
|
||||
|
||||
For usage with ngTouch, it is recommended to use the programmatic `is-open` trigger with ng-click - this is due to ngTouch decorating ng-click to prevent propagation of the event.
|
||||
|
||||
+191
-409
@@ -1,16 +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.bind('click', closeDropdown);
|
||||
$document.bind('keydown', keybindFilter);
|
||||
$document.on('click', closeDropdown);
|
||||
}
|
||||
|
||||
if (openScope && openScope !== dropdownScope) {
|
||||
@@ -18,22 +37,62 @@ 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) {
|
||||
$document.off('click', closeDropdown);
|
||||
$document.off('keydown', this.keybindFilter);
|
||||
openScope = null;
|
||||
$document.unbind('click', closeDropdown);
|
||||
$document.unbind('keydown', keybindFilter);
|
||||
}
|
||||
|
||||
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 ; }
|
||||
if (evt && openScope.getAutoClose() === 'disabled') { return; }
|
||||
|
||||
if (evt && evt.which === 3) { return; }
|
||||
|
||||
var toggleElement = openScope.getToggleElement();
|
||||
if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
|
||||
@@ -46,6 +105,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
return;
|
||||
}
|
||||
|
||||
openScope.focusToggleElement();
|
||||
openScope.isOpen = false;
|
||||
|
||||
if (!$rootScope.$$phase) {
|
||||
@@ -53,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)/.test(evt.which) && openScope.isOpen) {
|
||||
} else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen && (dropdownElementTargeted || toggleElementTargeted)) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
openScope.focusDropdownEntry(evt.which);
|
||||
@@ -69,14 +139,14 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
var self = this,
|
||||
scope = $scope.$new(), // create a child scope so we are not polluting original one
|
||||
templateScope,
|
||||
appendToOpenClass = dropdownConfig.appendToOpenClass,
|
||||
openClass = dropdownConfig.openClass,
|
||||
getIsOpen,
|
||||
setIsOpen = angular.noop,
|
||||
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
|
||||
appendToBody = false,
|
||||
keynavEnabled =false,
|
||||
selectedOption = null;
|
||||
|
||||
keynavEnabled = false,
|
||||
selectedOption = null,
|
||||
body = $document.find('body');
|
||||
|
||||
$element.addClass('dropdown');
|
||||
|
||||
@@ -90,19 +160,16 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
});
|
||||
}
|
||||
|
||||
appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
|
||||
keynavEnabled = angular.isDefined($attrs.uibKeyboardNav);
|
||||
|
||||
if (appendToBody && self.dropdownMenu) {
|
||||
$document.find('body').append(self.dropdownMenu);
|
||||
$element.on('$destroy', function handleDestroyEvent() {
|
||||
self.dropdownMenu.remove();
|
||||
});
|
||||
}
|
||||
keynavEnabled = angular.isDefined($attrs.keyboardNav);
|
||||
};
|
||||
|
||||
this.toggle = function(open) {
|
||||
return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
|
||||
scope.isOpen = arguments.length ? !!open : !scope.isOpen;
|
||||
if (angular.isFunction(setIsOpen)) {
|
||||
setIsOpen(scope, scope.isOpen);
|
||||
}
|
||||
|
||||
return scope.isOpen;
|
||||
};
|
||||
|
||||
// Allow other directives to watch status
|
||||
@@ -128,21 +195,21 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
|
||||
scope.focusDropdownEntry = function(keyCode) {
|
||||
var elems = self.dropdownMenu ? //If append to body is used.
|
||||
(angular.element(self.dropdownMenu).find('a')) :
|
||||
(angular.element($element).find('ul').eq(0).find('a'));
|
||||
angular.element(self.dropdownMenu).find('a') :
|
||||
$element.find('ul').eq(0).find('a');
|
||||
|
||||
switch (keyCode) {
|
||||
case (40): {
|
||||
case 40: {
|
||||
if (!angular.isNumber(self.selectedOption)) {
|
||||
self.selectedOption = 0;
|
||||
} else {
|
||||
self.selectedOption = (self.selectedOption === elems.length - 1 ?
|
||||
self.selectedOption = self.selectedOption === elems.length - 1 ?
|
||||
self.selectedOption :
|
||||
self.selectedOption + 1);
|
||||
self.selectedOption + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case (38): {
|
||||
case 38: {
|
||||
if (!angular.isNumber(self.selectedOption)) {
|
||||
self.selectedOption = elems.length - 1;
|
||||
} else {
|
||||
@@ -165,31 +232,106 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
}
|
||||
};
|
||||
|
||||
function removeDropdownMenu() {
|
||||
$element.append(self.dropdownMenu);
|
||||
}
|
||||
|
||||
scope.$watch('isOpen', function(isOpen, wasOpen) {
|
||||
if (appendToBody && self.dropdownMenu) {
|
||||
var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true);
|
||||
var css = {
|
||||
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,
|
||||
scrollbarPadding,
|
||||
scrollbarWidth = 0;
|
||||
|
||||
css = {
|
||||
top: pos.top + 'px',
|
||||
display: isOpen ? 'block' : 'none'
|
||||
};
|
||||
|
||||
var rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
|
||||
rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
|
||||
if (!rightalign) {
|
||||
css.left = pos.left + 'px';
|
||||
css.right = 'auto';
|
||||
} else {
|
||||
css.left = 'auto';
|
||||
css.right = (window.innerWidth - (pos.left + $element.prop('offsetWidth'))) + 'px';
|
||||
scrollbarPadding = $position.scrollbarPadding(appendTo);
|
||||
|
||||
if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
|
||||
scrollbarWidth = scrollbarPadding.scrollbarWidth;
|
||||
}
|
||||
|
||||
css.right = window.innerWidth - scrollbarWidth -
|
||||
(pos.left + $element.prop('offsetWidth')) + 'px';
|
||||
}
|
||||
|
||||
// Need to adjust our positioning to be relative to the appendTo container
|
||||
// if it's not the body element
|
||||
if (!appendToBody) {
|
||||
var appendOffset = $position.offset(appendTo);
|
||||
|
||||
css.top = pos.top - appendOffset.top + 'px';
|
||||
|
||||
if (!rightalign) {
|
||||
css.left = pos.left - appendOffset.left + 'px';
|
||||
} else {
|
||||
css.right = window.innerWidth -
|
||||
(pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
self.dropdownMenu.css(css);
|
||||
}
|
||||
|
||||
$animate[isOpen ? 'addClass' : 'removeClass']($element, openClass).then(function() {
|
||||
if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
|
||||
toggleInvoker($scope, { open: !!isOpen });
|
||||
var openContainer = appendTo ? appendTo : $element;
|
||||
var dropdownOpenClass = appendTo ? appendToOpenClass : openClass;
|
||||
var hasOpenClass = openContainer.hasClass(dropdownOpenClass);
|
||||
var isOnlyOpen = uibDropdownService.isOnlyOpen($scope, appendTo);
|
||||
|
||||
if (hasOpenClass === !isOpen) {
|
||||
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 });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isOpen) {
|
||||
if (self.dropdownMenuTemplateUrl) {
|
||||
@@ -199,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();
|
||||
@@ -215,7 +361,6 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
self.dropdownMenu = newEl;
|
||||
}
|
||||
|
||||
uibDropdownService.close(scope);
|
||||
self.selectedOption = null;
|
||||
}
|
||||
|
||||
@@ -223,17 +368,6 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
setIsOpen($scope, isOpen);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function() {
|
||||
if (scope.getAutoClose() !== 'disabled') {
|
||||
scope.isOpen = false;
|
||||
}
|
||||
});
|
||||
|
||||
var offDestroy = $scope.$on('$destroy', function() {
|
||||
scope.$destroy();
|
||||
});
|
||||
scope.$on('$destroy', offDestroy);
|
||||
}])
|
||||
|
||||
.directive('uibDropdown', function() {
|
||||
@@ -247,7 +381,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
|
||||
.directive('uibDropdownMenu', function() {
|
||||
return {
|
||||
restrict: 'AC',
|
||||
restrict: 'A',
|
||||
require: '?^uibDropdown',
|
||||
link: function(scope, element, attrs, dropdownCtrl) {
|
||||
if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
|
||||
@@ -268,45 +402,6 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
};
|
||||
})
|
||||
|
||||
.directive('uibKeyboardNav', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?^uibDropdown',
|
||||
link: function(scope, element, attrs, dropdownCtrl) {
|
||||
element.bind('keydown', function(e) {
|
||||
if ([38, 40].indexOf(e.which) !== -1) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var elems = dropdownCtrl.dropdownMenu.find('a');
|
||||
|
||||
switch (e.which) {
|
||||
case (40): { // Down
|
||||
if (!angular.isNumber(dropdownCtrl.selectedOption)) {
|
||||
dropdownCtrl.selectedOption = 0;
|
||||
} else {
|
||||
dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === elems.length -1 ?
|
||||
dropdownCtrl.selectedOption : dropdownCtrl.selectedOption + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case (38): { // Up
|
||||
if (!angular.isNumber(dropdownCtrl.selectedOption)) {
|
||||
dropdownCtrl.selectedOption = elems.length - 1;
|
||||
} else {
|
||||
dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === 0 ?
|
||||
0 : dropdownCtrl.selectedOption - 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
elems[dropdownCtrl.selectedOption].focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
.directive('uibDropdownToggle', function() {
|
||||
return {
|
||||
require: '?^uibDropdown',
|
||||
@@ -329,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 });
|
||||
@@ -338,321 +433,8 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
element.unbind('click', toggleDropdown);
|
||||
element.off('click', toggleDropdown);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/* Deprecated dropdown below */
|
||||
|
||||
angular.module('ui.bootstrap.dropdown')
|
||||
|
||||
.value('$dropdownSuppressWarning', false)
|
||||
|
||||
.service('dropdownService', ['$log', '$dropdownSuppressWarning', 'uibDropdownService', function($log, $dropdownSuppressWarning, uibDropdownService) {
|
||||
if (!$dropdownSuppressWarning) {
|
||||
$log.warn('dropdownService is now deprecated. Use uibDropdownService instead.');
|
||||
}
|
||||
|
||||
angular.extend(this, uibDropdownService);
|
||||
}])
|
||||
|
||||
.controller('DropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', '$log', '$dropdownSuppressWarning', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest, $log, $dropdownSuppressWarning) {
|
||||
if (!$dropdownSuppressWarning) {
|
||||
$log.warn('DropdownController is now deprecated. Use UibDropdownController instead.');
|
||||
}
|
||||
|
||||
var self = this,
|
||||
scope = $scope.$new(), // create a child scope so we are not polluting original one
|
||||
templateScope,
|
||||
openClass = dropdownConfig.openClass,
|
||||
getIsOpen,
|
||||
setIsOpen = angular.noop,
|
||||
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
|
||||
appendToBody = false,
|
||||
keynavEnabled =false,
|
||||
selectedOption = null;
|
||||
|
||||
|
||||
$element.addClass('dropdown');
|
||||
|
||||
this.init = function() {
|
||||
if ($attrs.isOpen) {
|
||||
getIsOpen = $parse($attrs.isOpen);
|
||||
setIsOpen = getIsOpen.assign;
|
||||
|
||||
$scope.$watch(getIsOpen, function(value) {
|
||||
scope.isOpen = !!value;
|
||||
});
|
||||
}
|
||||
|
||||
appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
|
||||
keynavEnabled = angular.isDefined($attrs.uibKeyboardNav);
|
||||
|
||||
if (appendToBody && self.dropdownMenu) {
|
||||
$document.find('body').append(self.dropdownMenu);
|
||||
$element.on('$destroy', function handleDestroyEvent() {
|
||||
self.dropdownMenu.remove();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.toggle = function(open) {
|
||||
return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
|
||||
};
|
||||
|
||||
// Allow other directives to watch status
|
||||
this.isOpen = function() {
|
||||
return scope.isOpen;
|
||||
};
|
||||
|
||||
scope.getToggleElement = function() {
|
||||
return self.toggleElement;
|
||||
};
|
||||
|
||||
scope.getAutoClose = function() {
|
||||
return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
|
||||
};
|
||||
|
||||
scope.getElement = function() {
|
||||
return $element;
|
||||
};
|
||||
|
||||
scope.isKeynavEnabled = function() {
|
||||
return keynavEnabled;
|
||||
};
|
||||
|
||||
scope.focusDropdownEntry = function(keyCode) {
|
||||
var elems = self.dropdownMenu ? //If append to body is used.
|
||||
(angular.element(self.dropdownMenu).find('a')) :
|
||||
(angular.element($element).find('ul').eq(0).find('a'));
|
||||
|
||||
switch (keyCode) {
|
||||
case (40): {
|
||||
if (!angular.isNumber(self.selectedOption)) {
|
||||
self.selectedOption = 0;
|
||||
} else {
|
||||
self.selectedOption = (self.selectedOption === elems.length -1 ?
|
||||
self.selectedOption :
|
||||
self.selectedOption + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case (38): {
|
||||
if (!angular.isNumber(self.selectedOption)) {
|
||||
self.selectedOption = elems.length - 1;
|
||||
} else {
|
||||
self.selectedOption = self.selectedOption === 0 ?
|
||||
0 : self.selectedOption - 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
elems[self.selectedOption].focus();
|
||||
};
|
||||
|
||||
scope.getDropdownElement = function() {
|
||||
return self.dropdownMenu;
|
||||
};
|
||||
|
||||
scope.focusToggleElement = function() {
|
||||
if (self.toggleElement) {
|
||||
self.toggleElement[0].focus();
|
||||
}
|
||||
};
|
||||
|
||||
scope.$watch('isOpen', function(isOpen, wasOpen) {
|
||||
if (appendToBody && self.dropdownMenu) {
|
||||
var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true);
|
||||
var css = {
|
||||
top: pos.top + 'px',
|
||||
display: isOpen ? 'block' : 'none'
|
||||
};
|
||||
|
||||
var rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
|
||||
if (!rightalign) {
|
||||
css.left = pos.left + 'px';
|
||||
css.right = 'auto';
|
||||
} else {
|
||||
css.left = 'auto';
|
||||
css.right = (window.innerWidth - (pos.left + $element.prop('offsetWidth'))) + 'px';
|
||||
}
|
||||
|
||||
self.dropdownMenu.css(css);
|
||||
}
|
||||
|
||||
$animate[isOpen ? 'addClass' : 'removeClass']($element, openClass).then(function() {
|
||||
if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
|
||||
toggleInvoker($scope, { open: !!isOpen });
|
||||
}
|
||||
});
|
||||
|
||||
if (isOpen) {
|
||||
if (self.dropdownMenuTemplateUrl) {
|
||||
$templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
|
||||
templateScope = scope.$new();
|
||||
$compile(tplContent.trim())(templateScope, function(dropdownElement) {
|
||||
var newEl = dropdownElement;
|
||||
self.dropdownMenu.replaceWith(newEl);
|
||||
self.dropdownMenu = newEl;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
scope.focusToggleElement();
|
||||
uibDropdownService.open(scope);
|
||||
} else {
|
||||
if (self.dropdownMenuTemplateUrl) {
|
||||
if (templateScope) {
|
||||
templateScope.$destroy();
|
||||
}
|
||||
var newEl = angular.element('<ul class="dropdown-menu"></ul>');
|
||||
self.dropdownMenu.replaceWith(newEl);
|
||||
self.dropdownMenu = newEl;
|
||||
}
|
||||
|
||||
uibDropdownService.close(scope);
|
||||
self.selectedOption = null;
|
||||
}
|
||||
|
||||
if (angular.isFunction(setIsOpen)) {
|
||||
setIsOpen($scope, isOpen);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function() {
|
||||
if (scope.getAutoClose() !== 'disabled') {
|
||||
scope.isOpen = false;
|
||||
}
|
||||
});
|
||||
|
||||
var offDestroy = $scope.$on('$destroy', function() {
|
||||
scope.$destroy();
|
||||
});
|
||||
scope.$on('$destroy', offDestroy);
|
||||
}])
|
||||
|
||||
.directive('dropdown', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) {
|
||||
return {
|
||||
controller: 'DropdownController',
|
||||
link: function(scope, element, attrs, dropdownCtrl) {
|
||||
if (!$dropdownSuppressWarning) {
|
||||
$log.warn('dropdown is now deprecated. Use uib-dropdown instead.');
|
||||
}
|
||||
|
||||
dropdownCtrl.init();
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('dropdownMenu', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) {
|
||||
return {
|
||||
restrict: 'AC',
|
||||
require: '?^dropdown',
|
||||
link: function(scope, element, attrs, dropdownCtrl) {
|
||||
if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$dropdownSuppressWarning) {
|
||||
$log.warn('dropdown-menu is now deprecated. Use uib-dropdown-menu instead.');
|
||||
}
|
||||
|
||||
element.addClass('dropdown-menu');
|
||||
|
||||
var tplUrl = attrs.templateUrl;
|
||||
if (tplUrl) {
|
||||
dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
|
||||
}
|
||||
|
||||
if (!dropdownCtrl.dropdownMenu) {
|
||||
dropdownCtrl.dropdownMenu = element;
|
||||
}
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('keyboardNav', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?^dropdown',
|
||||
link: function(scope, element, attrs, dropdownCtrl) {
|
||||
if (!$dropdownSuppressWarning) {
|
||||
$log.warn('keyboard-nav is now deprecated. Use uib-keyboard-nav instead.');
|
||||
}
|
||||
|
||||
element.bind('keydown', function(e) {
|
||||
if ([38, 40].indexOf(e.which) !== -1) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var elems = dropdownCtrl.dropdownMenu.find('a');
|
||||
|
||||
switch (e.which) {
|
||||
case (40): { // Down
|
||||
if (!angular.isNumber(dropdownCtrl.selectedOption)) {
|
||||
dropdownCtrl.selectedOption = 0;
|
||||
} else {
|
||||
dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === elems.length -1 ?
|
||||
dropdownCtrl.selectedOption : dropdownCtrl.selectedOption + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case (38): { // Up
|
||||
if (!angular.isNumber(dropdownCtrl.selectedOption)) {
|
||||
dropdownCtrl.selectedOption = elems.length - 1;
|
||||
} else {
|
||||
dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === 0 ?
|
||||
0 : dropdownCtrl.selectedOption - 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
elems[dropdownCtrl.selectedOption].focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('dropdownToggle', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) {
|
||||
return {
|
||||
require: '?^dropdown',
|
||||
link: function(scope, element, attrs, dropdownCtrl) {
|
||||
if (!$dropdownSuppressWarning) {
|
||||
$log.warn('dropdown-toggle is now deprecated. Use uib-dropdown-toggle instead.');
|
||||
}
|
||||
|
||||
if (!dropdownCtrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.addClass('dropdown-toggle');
|
||||
|
||||
dropdownCtrl.toggleElement = element;
|
||||
|
||||
var toggleDropdown = function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!element.hasClass('disabled') && !attrs.disabled) {
|
||||
scope.$apply(function() {
|
||||
dropdownCtrl.toggle();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
element.bind('click', toggleDropdown);
|
||||
|
||||
// WAI-ARIA
|
||||
element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
|
||||
scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
|
||||
element.attr('aria-expanded', !!isOpen);
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
element.unbind('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;
|
||||
@@ -0,0 +1,2 @@
|
||||
require('../position/position.css');
|
||||
module.exports = require('./index-nocss.js');
|
||||
+632
-413
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
require('./isClass');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.isClass';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.isClass']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
@@ -0,0 +1,97 @@
|
||||
// Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
|
||||
// at most one element.
|
||||
angular.module('ui.bootstrap.isClass', [])
|
||||
.directive('uibIsClass', [
|
||||
'$animate',
|
||||
function ($animate) {
|
||||
// 11111111 22222222
|
||||
var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
|
||||
// 11111111 22222222
|
||||
var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;
|
||||
|
||||
var dataPerTracked = {};
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function(tElement, tAttrs) {
|
||||
var linkedScopes = [];
|
||||
var instances = [];
|
||||
var expToData = {};
|
||||
var lastActivated = null;
|
||||
var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);
|
||||
var onExp = onExpMatches[2];
|
||||
var expsStr = onExpMatches[1];
|
||||
var exps = expsStr.split(',');
|
||||
|
||||
return linkFn;
|
||||
|
||||
function linkFn(scope, element, attrs) {
|
||||
linkedScopes.push(scope);
|
||||
instances.push({
|
||||
scope: scope,
|
||||
element: element
|
||||
});
|
||||
|
||||
exps.forEach(function(exp, k) {
|
||||
addForExp(exp, scope);
|
||||
});
|
||||
|
||||
scope.$on('$destroy', removeScope);
|
||||
}
|
||||
|
||||
function addForExp(exp, scope) {
|
||||
var matches = exp.match(IS_REGEXP);
|
||||
var clazz = scope.$eval(matches[1]);
|
||||
var compareWithExp = matches[2];
|
||||
var data = expToData[exp];
|
||||
if (!data) {
|
||||
var watchFn = function(compareWithVal) {
|
||||
var newActivated = null;
|
||||
instances.some(function(instance) {
|
||||
var thisVal = instance.scope.$eval(onExp);
|
||||
if (thisVal === compareWithVal) {
|
||||
newActivated = instance;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (data.lastActivated !== newActivated) {
|
||||
if (data.lastActivated) {
|
||||
$animate.removeClass(data.lastActivated.element, clazz);
|
||||
}
|
||||
if (newActivated) {
|
||||
$animate.addClass(newActivated.element, clazz);
|
||||
}
|
||||
data.lastActivated = newActivated;
|
||||
}
|
||||
};
|
||||
expToData[exp] = data = {
|
||||
lastActivated: null,
|
||||
scope: scope,
|
||||
watchFn: watchFn,
|
||||
compareWithExp: compareWithExp,
|
||||
watcher: scope.$watch(compareWithExp, watchFn)
|
||||
};
|
||||
}
|
||||
data.watchFn(scope.$eval(compareWithExp));
|
||||
}
|
||||
|
||||
function removeScope(e) {
|
||||
var removedScope = e.targetScope;
|
||||
var index = linkedScopes.indexOf(removedScope);
|
||||
linkedScopes.splice(index, 1);
|
||||
instances.splice(index, 1);
|
||||
if (linkedScopes.length) {
|
||||
var newWatchScope = linkedScopes[0];
|
||||
angular.forEach(expToData, function(data) {
|
||||
if (data.scope === removedScope) {
|
||||
data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
|
||||
data.scope = newWatchScope;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
expToData = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}]);
|
||||
@@ -0,0 +1,80 @@
|
||||
describe('uibIsClass', function() {
|
||||
var $rootScope;
|
||||
|
||||
beforeEach(module('ui.bootstrap.isClass'));
|
||||
beforeEach(inject(function($compile, _$rootScope_) {
|
||||
$rootScope = _$rootScope_;
|
||||
$rootScope.activeClass = 'active';
|
||||
$rootScope.items = [1, 2, 3];
|
||||
element = $compile('<div><div ng-repeat="item in items" ' +
|
||||
'uib-is-class="activeClass for activeItem on item">{{ item }}</div></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
}));
|
||||
|
||||
it('initializes classes correctly', function() {
|
||||
expect(element.find('.active').length).toEqual(0);
|
||||
});
|
||||
|
||||
it('sets classes correctly', function() {
|
||||
$rootScope.activeItem = 2;
|
||||
$rootScope.$digest();
|
||||
expect(element.find('.active').text()).toEqual('2');
|
||||
|
||||
$rootScope.items.splice(1, 1);
|
||||
$rootScope.$digest();
|
||||
expect(element.find('.active').length).toEqual(0);
|
||||
});
|
||||
|
||||
it('handles removal of items correctly', function() {
|
||||
$rootScope.activeItem = 2;
|
||||
$rootScope.$digest();
|
||||
expect(element.find('.active').text()).toEqual('2');
|
||||
|
||||
$rootScope.items.splice(1, 1);
|
||||
$rootScope.$digest();
|
||||
expect(element.find('.active').length).toEqual(0);
|
||||
|
||||
$rootScope.activeItem = 1;
|
||||
$rootScope.$digest();
|
||||
expect(element.find('.active').text()).toEqual('1');
|
||||
});
|
||||
|
||||
it('handles moving of items', function() {
|
||||
$rootScope.activeItem = 2;
|
||||
$rootScope.items = [2, 1, 3];
|
||||
$rootScope.$digest();
|
||||
expect(element.find('.active').text()).toEqual('2');
|
||||
expect(element.find('.active').length).toEqual(1);
|
||||
expect(element.find('.active').index()).toEqual(0);
|
||||
|
||||
$rootScope.items = [4, 3, 2];
|
||||
$rootScope.$digest();
|
||||
expect(element.find('.active').text()).toEqual('2');
|
||||
expect(element.find('.active').length).toEqual(1);
|
||||
expect(element.find('.active').index()).toEqual(2);
|
||||
});
|
||||
|
||||
it('handles emptying and re-adding the items', function() {
|
||||
$rootScope.activeItem = 2;
|
||||
$rootScope.items = [];
|
||||
$rootScope.$digest();
|
||||
expect(element.find('.active').length).toEqual(0);
|
||||
|
||||
$rootScope.items = [4, 3, 2];
|
||||
$rootScope.$digest();
|
||||
expect(element.find('.active').text()).toEqual('2');
|
||||
expect(element.find('.active').index()).toEqual(2);
|
||||
});
|
||||
|
||||
it('handles undefined items', function() {
|
||||
$rootScope.activeItem = undefined;
|
||||
$rootScope.items = [];
|
||||
$rootScope.$digest();
|
||||
expect(element.find('.active').length).toEqual(0);
|
||||
|
||||
$rootScope.items = [4, 3, undefined];
|
||||
$rootScope.$digest();
|
||||
expect(element.find('.active').length).toEqual(1);
|
||||
expect(element.find('.active').text()).toEqual('');
|
||||
});
|
||||
});
|
||||
+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>
|
||||
|
||||
+95
-20
@@ -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 $modalInstance represents a modal window (instance) dependency.
|
||||
// Please note that $uibModalInstance represents a modal window (instance) dependency.
|
||||
// It is not the same as the $uibModal service used above.
|
||||
|
||||
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'});
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
+146
-30
@@ -1,45 +1,161 @@
|
||||
`$uibModal` is a service to quickly create AngularJS-powered modal windows.
|
||||
Creating custom modals is straightforward: create a partial view, its controller and reference them when using the service.
|
||||
`$uibModal` is a service to create modal windows.
|
||||
Creating modals is straightforward: create a template and controller, and reference them when using `$uibModal`.
|
||||
|
||||
The `$uibModal` service has only one method: `open(options)` where available options are like follows:
|
||||
The `$uibModal` service has only one method: `open(options)`.
|
||||
|
||||
* `templateUrl` - a path to a template representing modal's content
|
||||
* `template` - inline template representing the modal's content
|
||||
* `scope` - a scope instance to be used for the modal's content (actually the `$uibModal` service is going to create a child scope of a provided scope). Defaults to `$rootScope`
|
||||
* `controller` - a controller for a modal instance - it can initialize scope used by modal. Accepts the "controller-as" syntax in the form 'SomeCtrl as myctrl'; can be injected with `$uibModalInstance`
|
||||
* `controllerAs` - an alternative to the controller-as syntax, matching the API of directive definitions. Requires the `controller` option to be provided as well
|
||||
* `bindToController` - when used with `controllerAs` & set to `true`, it will bind the $scope properties onto the controller directly
|
||||
* `resolve` - members that will be resolved and passed to the controller as locals; it is equivalent of the `resolve` property for AngularJS routes
|
||||
* `animation` - set to false to disable animations on new modal/backdrop. Does not toggle animations for modals/backdrops that are already displayed.
|
||||
* `backdrop` - controls presence of a backdrop. Allowed values: true (default), false (no backdrop), `'static'` - backdrop is present but modal window is not closed when clicking outside of the modal window.
|
||||
* `keyboard` - indicates whether the dialog should be closable by hitting the ESC key, defaults to true
|
||||
* `backdropClass` - additional CSS class(es) to be added to a modal backdrop template
|
||||
* `windowClass` - additional CSS class(es) to be added to a modal window template
|
||||
* `windowTopClass` - CSS class(es) to be added to the top modal window
|
||||
* `windowTemplateUrl` - a path to a template overriding modal's window template
|
||||
* `size` - optional suffix of modal window class. The value used is appended to the `modal-` class, i.e. a value of `sm` gives `modal-sm`
|
||||
* `openedClass` - class added to the `body` element when the modal is opened. Defaults to `modal-open`
|
||||
### $uibModal's open function
|
||||
|
||||
#### options parameter
|
||||
|
||||
* `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`
|
||||
_(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.
|
||||
|
||||
* `bindToController`
|
||||
_(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`)_ -
|
||||
An alternative to the controller-as syntax. Requires the `controller` option to be provided as well.
|
||||
|
||||
* `keyboard` -
|
||||
_(Type: `boolean`, Default: `true`)_ -
|
||||
Indicates whether the dialog should be closable by hitting the ESC key.
|
||||
|
||||
* `openedClass`
|
||||
_(Type: `string`, Default: `modal-open`)_ -
|
||||
Class added to the `body` element when the modal is opened.
|
||||
|
||||
* `resolve`
|
||||
_(Type: `Object`)_ -
|
||||
Members that will be resolved and passed to the controller as locals; it is equivalent of the `resolve` property in the router.
|
||||
|
||||
* `scope`
|
||||
_(Type: `$scope`)_ -
|
||||
The parent scope instance to be used for the modal's content. Defaults to `$rootScope`.
|
||||
|
||||
* `size`
|
||||
_(Type: `string`, Example: `lg`)_ -
|
||||
Optional suffix of modal window class. The value used is appended to the `modal-` class, i.e. a value of `sm` gives `modal-sm`.
|
||||
|
||||
* `template`
|
||||
_(Type: `string`)_ -
|
||||
Inline template representing the modal's content.
|
||||
|
||||
* `templateUrl`
|
||||
_(Type: `string`)_ -
|
||||
A path to a template representing modal's content. You need either a `template` or `templateUrl`.
|
||||
|
||||
* `windowClass`
|
||||
_(Type: `string`)_ -
|
||||
Additional CSS class(es) to be added to a modal window template.
|
||||
|
||||
* `windowTemplateUrl`
|
||||
_(Type: `string`, Default: `uib/template/modal/window.html`)_ -
|
||||
A path to a template overriding modal's window template.
|
||||
|
||||
* `windowTopClass`
|
||||
_(Type: `string`)_ -
|
||||
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:
|
||||
|
||||
* `close(result)` - a method that can be used to close a modal, passing a result
|
||||
* `dismiss(reason)` - a method that can be used to dismiss a modal, passing a reason
|
||||
* `result` - a promise that is resolved when a modal is closed and rejected when a modal is dismissed
|
||||
* `opened` - a promise that is resolved when a modal gets opened after downloading content's template and resolving all variables
|
||||
* `rendered` - a promise that is resolved when a modal is rendered.
|
||||
* `close(result)`
|
||||
_(Type: `function`)_ -
|
||||
Can be used to close a modal, passing a result.
|
||||
|
||||
In addition the scope associated with modal's content is augmented with 2 methods:
|
||||
* `dismiss(reason)`
|
||||
_(Type: `function`)_ -
|
||||
Can be used to dismiss a modal, passing a reason.
|
||||
|
||||
* `result`
|
||||
_(Type: `promise`)_ -
|
||||
Is resolved when a modal is closed and rejected when a modal is dismissed.
|
||||
|
||||
* `opened`
|
||||
_(Type: `promise`)_ -
|
||||
Is resolved when a modal gets opened after downloading content's template and resolving all variables.
|
||||
|
||||
* `closed`
|
||||
_(Type: `promise`)_ -
|
||||
Is resolved when a modal is closed and the animation completes.
|
||||
|
||||
* `rendered`
|
||||
_(Type: `promise`)_ -
|
||||
Is resolved when a modal is rendered.
|
||||
|
||||
---
|
||||
|
||||
The scope associated with modal's content is augmented with:
|
||||
|
||||
* `$close(result)`
|
||||
_(Type: `function`)_ -
|
||||
A method that can be used to close a modal, passing a result.
|
||||
|
||||
* `$dismiss(reason)`
|
||||
_(Type: `function`)_ -
|
||||
A method that can be used to dismiss a modal, passing a reason.
|
||||
|
||||
Those methods make it easy to close a modal window without a need to create a dedicated controller.
|
||||
|
||||
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 be dismissed with the value `$uibUnscheduledDestruction`.
|
||||
Also, when using `bindToController`, you can define an `$onInit` method in the controller that will fire upon initialization.
|
||||
|
||||
Finally, a `modal.closing` 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. The $close and $dismiss methods return true if the
|
||||
event was allowed. The event itself includes a parameter for the result/reason and a boolean parameter that indicates
|
||||
whether the modal is being closed (true) or dismissed.
|
||||
---
|
||||
|
||||
Events fired:
|
||||
|
||||
* `$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.
|
||||
|
||||
##### 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;
|
||||
@@ -0,0 +1,2 @@
|
||||
require('../position/position.css');
|
||||
module.exports = require('./index-nocss.js');
|
||||
+414
-517
File diff suppressed because it is too large
Load Diff
+993
-129
File diff suppressed because it is too large
Load Diff
@@ -1,46 +0,0 @@
|
||||
describe('modal window', function() {
|
||||
var $rootScope, $compile;
|
||||
|
||||
beforeEach(module('ui.bootstrap.modal'));
|
||||
beforeEach(module('template/modal/window.html'));
|
||||
beforeEach(inject(function (_$rootScope_, _$compile_) {
|
||||
$rootScope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
}));
|
||||
|
||||
it('should not use transclusion scope for modals content - issue 2110', function() {
|
||||
$rootScope.animate = false;
|
||||
$compile('<div uib-modal-window animate="animate"><span ng-init="foo=true"></span></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
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);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(windowEl).toHaveClass('test');
|
||||
expect(windowEl).toHaveClass('foo');
|
||||
});
|
||||
|
||||
it('should support custom template url', inject(function($templateCache) {
|
||||
$templateCache.put('window.html', '<div class="mywindow" ng-transclude></div>');
|
||||
|
||||
var windowEl = $compile('<div uib-modal-window template-url="window.html" window-class="test">content</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(windowEl).toHaveClass('mywindow');
|
||||
expect(windowEl).toHaveClass('test');
|
||||
}));
|
||||
});
|
||||
@@ -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();
|
||||
}));
|
||||
@@ -0,0 +1,5 @@
|
||||
<div ng-controller="PagerDemoCtrl">
|
||||
<h4>Pager</h4>
|
||||
<pre>You are currently on page {{currentPage}}</pre>
|
||||
<ul uib-pager total-items="totalItems" ng-model="currentPage"></ul>
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
angular.module('ui.bootstrap.demo').controller('PagerDemoCtrl', function($scope) {
|
||||
$scope.totalItems = 64;
|
||||
$scope.currentPage = 4;
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
A lightweight pager directive that is focused on providing previous/next paging functionality
|
||||
|
||||
### uib-pager settings
|
||||
|
||||
* `align`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `true`)_ -
|
||||
Whether to align each link to the sides.
|
||||
|
||||
* `items-per-page`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">C</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `10`)_ -
|
||||
Maximum number of items per page. A value less than one indicates all items on one page.
|
||||
|
||||
* `next-text`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `Next »`)_ -
|
||||
Text for Next button.
|
||||
|
||||
* `ng-disabled`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `false`)_ -
|
||||
Used to disable the pager component.
|
||||
|
||||
* `ng-model`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i> -
|
||||
Current page number. First page is 1.
|
||||
|
||||
* `num-pages`
|
||||
<small class="badge">$</small>
|
||||
<small class="badge">readonly</small>
|
||||
_(Default: `angular.noop`)_ -
|
||||
An optional expression assigned the total number of pages to display.
|
||||
|
||||
* `previous-text`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `« Previous`)_ -
|
||||
Text for Previous button.
|
||||
|
||||
* `template-url`
|
||||
_(Default: `uib/template/pager/pager.html`)_ -
|
||||
Override the template for the component with a custom provided template.
|
||||
|
||||
* `total-items`
|
||||
<small class="badge">$</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i> -
|
||||
Total number of items in all pages.
|
||||
@@ -0,0 +1,10 @@
|
||||
require('../paging');
|
||||
require('../tabindex');
|
||||
require('../../template/pager/pager.html.js');
|
||||
require('./pager');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.pager';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.pager', 'uib/template/pager/pager.html']);
|
||||
|
||||
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