Compare commits
1369 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 | |||
| e9d6ec2a29 | |||
| 1f94104b69 | |||
| 1599cfff9b | |||
| ac62276943 | |||
| 8417b2d525 | |||
| 0d64aad4dd | |||
| d3056c7605 | |||
| 134086a6b0 | |||
| a6cb8d37dd | |||
| 74f9945171 | |||
| ff9092ec84 | |||
| de24f4647f | |||
| 563410c3cb | |||
| 312dc3e82a | |||
| 1cba27c1f2 | |||
| 964aa16831 | |||
| 94fb282481 | |||
| 97fd37e226 | |||
| 7c3c631075 | |||
| 6d1cd0f409 | |||
| 82cd2d41d1 | |||
| 13be7b78c6 | |||
| 31446331e1 | |||
| 7985a46ead | |||
| 550ca0f9a7 | |||
| c18cb16baa | |||
| 59f87fcc95 | |||
| e8808d3a67 | |||
| 02425b807e | |||
| 6c82b2b213 | |||
| 1f65d87fc3 | |||
| 16dafd5f1b | |||
| 539b4723e7 | |||
| 6daf871863 | |||
| be5802a5da | |||
| 8ba3466048 | |||
| ebb5e18761 | |||
| feb689cf8e | |||
| 0b1e206141 | |||
| 5646cbe63f | |||
| 68f9530565 | |||
| 1a1cd35d80 | |||
| a1340d72b6 | |||
| f9a73a08bd | |||
| 4ebecbcca8 | |||
| 18371ab1df | |||
| a09275afde | |||
| 70be195959 | |||
| 1cb4f631b2 | |||
| ae1a87c6d2 | |||
| 00f60eeb27 | |||
| 685bd6a461 | |||
| ce1114a4ee | |||
| 1c5e4798dd | |||
| d50e8d22ac | |||
| f6c7931e85 | |||
| 1e3cbd82ed | |||
| e8c8ee6cd2 | |||
| c5e6042bf2 | |||
| 9e71da8d1a | |||
| d668ce14f6 | |||
| 6bb2f550eb | |||
| c7cc9eab88 | |||
| 0ad7cb9ed1 | |||
| 32729600c6 | |||
| 22f1a4b6c4 | |||
| 01b9624ef0 | |||
| c9ab5f63bf | |||
| 7675457a06 | |||
| 53bb69ee62 | |||
| adf68ebc35 | |||
| f900fdd321 | |||
| 305dc2e1f8 | |||
| b1862d32dc | |||
| 694a0116e3 | |||
| 44354f67e5 | |||
| 9e5e1a2eab | |||
| 038157dd57 | |||
| 001574e5ef | |||
| 4db0f95c95 | |||
| dc8379a014 | |||
| a76fd08dc3 | |||
| 795b7cf736 | |||
| 18590f065f | |||
| adebde9044 | |||
| 5f7051b38f | |||
| d4adef7834 | |||
| f8bc038db6 | |||
| 9aea856f11 | |||
| 4e60e22c49 | |||
| 8c7b9e4c78 | |||
| 30e8aa71e2 | |||
| 6b4267b2d3 | |||
| 0b3d5bd38c | |||
| 3aa984182e | |||
| 435924f201 | |||
| aede646189 | |||
| 5604e59586 | |||
| 92ab48ece5 | |||
| c0dbf793ba | |||
| f561aa9e95 | |||
| 5382226a54 | |||
| db36c62f99 | |||
| e7c58791af | |||
| 8fa158769d | |||
| 3a3b383f2f | |||
| 8d19c608e0 | |||
| d1555dcdb6 | |||
| 7b7039b4d9 | |||
| 85d7748162 | |||
| 2e268151e2 | |||
| 6f9f1fcf6b | |||
| 5bc0851c08 | |||
| 615809169b | |||
| 504e653f6b | |||
| 66819c2ad0 | |||
| 2e5bfaca3b | |||
| 0fa851fb80 | |||
| 298ec8cf20 | |||
| 377b4b7962 | |||
| fdf53e6461 | |||
| 8919b0a08e | |||
| e432059aed | |||
| d25a8c245b | |||
| 23a312d5c1 | |||
| da711594f8 | |||
| 19118f162e | |||
| ec6c0bca1b | |||
| 81a38d7261 | |||
| a59caf183d | |||
| 5a1c2c9a27 | |||
| 4031e29cae | |||
| 2c0ad03f8a | |||
| bba0e686dc | |||
| d9a521a4e5 | |||
| 868c0e248d | |||
| b74c263fd5 | |||
| fa1cdfccd3 | |||
| 7ba252787c | |||
| fb90152c7e | |||
| b10a48b5ed | |||
| cbf2c0d1e4 | |||
| fabf0161ce | |||
| 36ecf60c23 | |||
| 5e3a87a91e | |||
| a04b4ff284 | |||
| d1babb64e9 | |||
| 641cdb32d8 | |||
| b1fdca5da2 | |||
| fcb08e9dfc | |||
| 47076029f1 | |||
| 252837158b | |||
| 9d6ab9665b | |||
| 65814f1e95 | |||
| 53f5f477ef | |||
| 4d01387a1b | |||
| c08509a071 | |||
| f26998303d | |||
| 365cdd6acb | |||
| bd38e8f355 | |||
| 0328a7630a | |||
| eb2366fe11 | |||
| d6cda93aff | |||
| 8ae664fa2e | |||
| ff52f52a8d | |||
| 5f68280ad0 | |||
| c064748536 | |||
| b864aa9c81 | |||
| cf92eae985 | |||
| e6f3b874cb | |||
| 9bdb32ed8f | |||
| 79e38cbce7 | |||
| b413a226a4 | |||
| 9d80066321 | |||
| c4b7e04b7c | |||
| d11c3d0ef0 | |||
| ddfca61360 | |||
| 8fee75d517 | |||
| 72de2d80e7 | |||
| af2d1ef4e5 | |||
| 50ebfc3db0 | |||
| 508acebe8e | |||
| 745c57d41b | |||
| 1e039e86b6 | |||
| f7cb8bc014 | |||
| 5482db2edd | |||
| 31e04a85a9 | |||
| 1583dde5e2 | |||
| 533a9f03c6 | |||
| 0a69580432 | |||
| b6635e68e3 | |||
| da6924eba2 | |||
| 8b809b2831 | |||
| dfa4c97cb6 | |||
| 833e7ad765 | |||
| 20739e4a2e | |||
| eec160855f | |||
| 86e35a7740 | |||
| defcbbb1b6 | |||
| c7d669fd38 | |||
| 7556beda48 | |||
| d891c8da3f | |||
| ae6583d54c | |||
| 4b026481c9 | |||
| aa5a991ea5 | |||
| 9b2f7ac315 | |||
| 3b35349320 | |||
| 44fd16cd13 | |||
| ccaa6272eb | |||
| 56bdb1c104 | |||
| 457f10c92b | |||
| b1e98b1f79 | |||
| bf63cef0aa | |||
| 49a0858a80 | |||
| 0be8778b67 | |||
| 695db9d53e | |||
| 1bba8b4bf5 | |||
| 937a1f3f78 | |||
| 5a44eb2f38 | |||
| 9b094a0d31 | |||
| ad760434e8 | |||
| 1914955a10 | |||
| 5229c204df | |||
| a06afe6bc1 | |||
| ef668f67f3 | |||
| 639d5117b7 | |||
| 53c94be934 | |||
| afa8cdda5d | |||
| 1b76695c46 | |||
| c31a7cce97 | |||
| f50a4d6138 | |||
| 5ee6f0e39e | |||
| 6af48e18d5 | |||
| d0653c81b2 | |||
| bdfb28916e | |||
| 5ee23a4c26 | |||
| 07a938d124 | |||
| 7d3ba1e10e | |||
| 790d6b97f1 | |||
| 3492c26259 | |||
| dac087eb89 | |||
| 3d01c597b9 | |||
| 550fe20d65 | |||
| 734bd6a566 | |||
| ba734b4e63 | |||
| 0b03a01faf | |||
| 432b4eb93e | |||
| bb9fa1a2a5 | |||
| aaec2f4d84 | |||
| 42dab29a3e | |||
| 86a91da916 | |||
| 30dd34f9dc | |||
| aaf65bbda6 | |||
| 773af482c2 | |||
| 16ff411b81 | |||
| b5ecda30c3 | |||
| f8eab5594b | |||
| 71e0b8a981 | |||
| 8e84567555 | |||
| cd4822dcf0 | |||
| 575eb8593a | |||
| dccd619755 | |||
| f36b61d7a2 | |||
| a366648c38 | |||
| 07f51356f4 | |||
| bb460c380d | |||
| 401c2d127c | |||
| 0e32664ca0 | |||
| fba7bfb5ef | |||
| 5b0c10922b | |||
| f9b761d0eb | |||
| da8821fc58 | |||
| 8213e7b5aa | |||
| 17cc39f4b7 | |||
| 581161d577 | |||
| f3b68b3810 | |||
| 20f525ba0e | |||
| 94acfda024 | |||
| ed236b318c | |||
| e7761b4485 | |||
| 0d6b897e91 | |||
| 71360800a3 | |||
| 467292ad6c | |||
| 99b87cc07b | |||
| dbd6f737cd | |||
| ba2ce243b0 | |||
| ace4ae19e2 | |||
| 1abfd057ee | |||
| 84cc5f0656 | |||
| 32f002dbfc | |||
| 311c97f868 | |||
| 818f7e53ca | |||
| 11ecfd5d62 | |||
| a75d5e6b34 | |||
| 64b52896fc | |||
| 3c06832d76 | |||
| 215abd01c2 | |||
| 876b9769e7 | |||
| 0ba4184d02 | |||
| 739b1d19f5 | |||
| 3a036de799 | |||
| 4a69d00815 | |||
| 54f4d735c3 | |||
| e04b06d765 | |||
| 68afc4c63e | |||
| 991c91d404 | |||
| 901a7c6656 | |||
| ead15e37f6 | |||
| 22b16f01be | |||
| 895a228107 | |||
| b21c9abdf9 | |||
| a29c8f2070 | |||
| 02872dc1fe | |||
| 88a885ca73 | |||
| 9865ee8e1c | |||
| a0e1c91cb1 | |||
| 5a28ff76ff | |||
| f081fabe1a | |||
| bfec07e45e | |||
| 893688565f | |||
| 3c58a7323c | |||
| feee292de0 | |||
| f3d263e1b9 | |||
| abfd26c025 | |||
| bab1d3754a | |||
| 87fc242d80 | |||
| 6c0010be41 | |||
| a05b9c1ac5 | |||
| 1ecd82ced1 | |||
| e633ca80f4 | |||
| c7f19d5834 | |||
| 265d429b67 | |||
| e60c3ff6e2 | |||
| 8853bd8325 | |||
| 0b37f088f1 | |||
| 12c527afb3 | |||
| 8359d73fd3 | |||
| 3584061f19 | |||
| 5ae5be6625 | |||
| 3b5b3bd24b | |||
| 328146db6e | |||
| f777c320cc | |||
| b18dc8f9bc | |||
| 394fcc2576 | |||
| fbba5d3853 | |||
| c9b0d0b05f | |||
| 707fbf55c0 | |||
| 758860dbca | |||
| 74a8be4c5a | |||
| ca9196fa8f | |||
| 8dc13be924 | |||
| 5245ccad3c | |||
| c7d3a66095 | |||
| cf65a6357c | |||
| a8a22cff06 | |||
| 8056368e03 | |||
| ff37fb68ba | |||
| 76b21f5973 | |||
| 95d22cbb6f | |||
| 5e372c021b | |||
| 13df1c9305 | |||
| 632aa8204b | |||
| 1e8297be43 | |||
| 647cdd931c | |||
| 9f73d240ab | |||
| 6b90e9edf2 | |||
| 25704838d2 | |||
| 7af9cdf804 | |||
| 97e077e115 | |||
| ab4580fd81 | |||
| 20047577b0 | |||
| a9d3d253eb | |||
| 4f1e03f114 | |||
| 713c8487cb | |||
| a69f66f127 | |||
| 0643fd3e6c | |||
| 577b2a2ace | |||
| 811bf96eb1 | |||
| 277b30ca8b | |||
| d0bdd2ade6 | |||
| 27f7ca26d2 | |||
| 797804cacc | |||
| f45b4a4cdf | |||
| 42e1af5c60 | |||
| 1b04599b15 | |||
| 32e73280a6 | |||
| c55ee4f527 | |||
| 1afc5d1d7c | |||
| 9f593baf0c | |||
| fe0d954a54 | |||
| 2e9177e5f6 | |||
| a028d2aa16 | |||
| 60e431608f | |||
| 0c78026b6c | |||
| a65a5fa196 | |||
| c727da815b | |||
| feb8a8642c | |||
| 7bf3a2bed0 | |||
| 4a29947a5a | |||
| fbc655614c | |||
| 960b5e29d4 | |||
| a722395bec | |||
| ece8a36cba | |||
| 5b37bb8b4e | |||
| 7d1c46009f | |||
| 8ae1a76dc1 | |||
| cc66a06872 | |||
| 6173664ff7 | |||
| 091f0f609d | |||
| 8adfc833f7 | |||
| a861a2c6b0 | |||
| 5567c43251 | |||
| aef8957307 | |||
| 41ebd9846a | |||
| 4af83ade14 | |||
| 2332f14d66 | |||
| 14689e058c | |||
| 9c2a8db9f2 | |||
| 493510d059 | |||
| f14120149b | |||
| 68cac59ac0 | |||
| 054341b715 | |||
| b72efed56f | |||
| dbceec76d0 | |||
| 86bfec1919 | |||
| f6edfa5ddf | |||
| 2b27dcbf99 | |||
| 92551342cc | |||
| ede9ea4606 | |||
| fb302c6086 | |||
| 158d267659 | |||
| 13a0354f66 | |||
| 0286828be3 | |||
| dd4f3cc872 | |||
| 3316d3e1ca | |||
| 7fb3840f2f | |||
| eb3b32ecd6 | |||
| 44e0642562 | |||
| 36e0f0ea36 | |||
| 62359370e9 | |||
| 49e73a89f3 | |||
| ca07ad7c72 | |||
| a5a25141fc | |||
| 988336ccff | |||
| 0d96221fa3 | |||
| 115d490a25 | |||
| f02a24d232 | |||
| ef45ecf894 | |||
| 83c4266cd8 | |||
| 1f760eb35f | |||
| d631af5a05 | |||
| 8297244aba | |||
| 10c67e7b5d | |||
| c946509bda | |||
| 195e520ec4 | |||
| e5a1e88fd7 | |||
| 9247f15b30 | |||
| a469fc36f2 | |||
| 13b2f9b17a | |||
| ed10899d6b | |||
| 4683996246 | |||
| 2a2e5de7d8 | |||
| 21b2002461 | |||
| ce3ef4c74d | |||
| 8824532f26 | |||
| e9b9eb00b0 | |||
| 4c6e0b3b2e | |||
| fcbc07c689 | |||
| 88a41dce86 | |||
| 6af627a8ed | |||
| 34044a7707 | |||
| 422c823460 | |||
| d867f8302d | |||
| aef8953c79 | |||
| 1a9e88fe9e | |||
| e909b922a2 | |||
| f02c1bbbf7 | |||
| f0cadb1f1a | |||
| 0de8c866ad | |||
| 39e5fd3e49 | |||
| 82cb637dd1 | |||
| ec0f3db314 | |||
| 3264cd0904 | |||
| 8bbc463b78 | |||
| 9f5d389c30 | |||
| 6324486d70 | |||
| 31a68021f5 | |||
| 09cf058498 | |||
| da4208acbb | |||
| 4b7c21be6d | |||
| c8156c7e21 | |||
| 22a21448cb | |||
| c2ace47225 | |||
| e31fcf0fcb | |||
| 9ca9d7f526 | |||
| 4bfae2238d | |||
| 461087b5ff | |||
| 8a55244374 | |||
| d0cc72841b | |||
| d024dd77ed | |||
| 971a1b5739 | |||
| b4bbc01982 | |||
| 33269bb6b4 | |||
| e6b105ae39 | |||
| 26b40903ab | |||
| 7de7a8ea28 | |||
| dfe9854be3 | |||
| a643f96d67 | |||
| 6355d8adc7 | |||
| 5536e9660e | |||
| a1e50d37a5 | |||
| 0ad208d6a0 | |||
| 489961e1a0 | |||
| ac6a0f4e6d | |||
| a50f11201e | |||
| dcdce69fc6 | |||
| 5e661d47d6 | |||
| 4a06adbac6 | |||
| 31c2694007 | |||
| 2c1a4cc09c | |||
| a1695114a2 | |||
| 7e3179ab9f | |||
| bf4b5ec985 | |||
| f908c2b1aa | |||
| dcc340f7f4 | |||
| dcb44b10c9 | |||
| fa8e52c406 | |||
| 8821d12eb2 | |||
| 7ccff028ff | |||
| f13ed0fb84 | |||
| 853fa4578a | |||
| a726b7cd47 | |||
| 7c1c75b46c | |||
| df5bda6574 | |||
| 8cf810baee | |||
| 5cb2f78f78 | |||
| 58e74af071 | |||
| 85eeb95428 | |||
| 9468d7239d | |||
| 35b8512ac7 | |||
| 8e89440ba8 | |||
| 13bd516cfc | |||
| 89ab4580cd | |||
| 98e2bdfc8b | |||
| c19b8879e9 | |||
| b5e534b893 | |||
| df0bc00522 | |||
| 570f2e1f2a | |||
| bed788eb58 | |||
| 7940fd05f0 | |||
| b076483caa | |||
| c0a9c70790 | |||
| 405dab65f7 | |||
| f7b44286aa | |||
| 950c22cdab | |||
| 36e6363765 | |||
| c5b63ded0a | |||
| 22961157ad | |||
| 0d8820b9aa | |||
| 0bcd30c4a6 | |||
| b004443371 | |||
| 03446c5633 | |||
| e5f5f75b37 | |||
| 2c2dba6d14 | |||
| b5f220fa84 | |||
| d253208bd7 | |||
| d784354a53 | |||
| 0ecf7faad3 | |||
| f9a9b979c5 | |||
| 20a01cefcc | |||
| 431b9c76b0 | |||
| d03543ea61 | |||
| 8e4860f8f0 | |||
| 9a26b36803 | |||
| f74b9583e6 | |||
| 907c55ff3e | |||
| 8c584cd057 | |||
| 30a34196d7 | |||
| 8dc3cffa6c | |||
| 91b5fb62ee | |||
| a5a82d9be7 | |||
| ab919f9f89 | |||
| 99af5f8a36 | |||
| b5a80c08f2 | |||
| 87a3607632 | |||
| aa0b6392db | |||
| 9ca4ec399b | |||
| 6abad509cd | |||
| db570e36df | |||
| c505546abb | |||
| c2e5b284a3 | |||
| 2724b46282 | |||
| cb41538eea | |||
| 001c9effa4 | |||
| 692268185d | |||
| f1dd46a07a | |||
| 868798ed2e | |||
| f38ad2ba5e | |||
| 23936f9f6e | |||
| 5f9afe5a86 | |||
| ed5013b331 | |||
| fe33ca9848 | |||
| b7e68347ad | |||
| 5726e3efae | |||
| da31a4fcda | |||
| 821c1ab0b1 | |||
| b8bfd364fe | |||
| 5dc30504a8 | |||
| ee829af310 | |||
| a89fc720be | |||
| 18c6acf7ad | |||
| b7beaae53a | |||
| 60aec2e88d | |||
| c237757c9a | |||
| ea4460434c | |||
| 384bf6061c | |||
| 2e62733c7a | |||
| ec5d73ee48 | |||
| 721e288a91 | |||
| 905db179f4 | |||
| 9935e6c7a2 | |||
| 4ed94b566b | |||
| 0938d3fec3 | |||
| f671e02e97 | |||
| 1ac964600a | |||
| a6b9d5390d | |||
| 722036c77c | |||
| eec68d81f4 | |||
| ac68c046f6 | |||
| 1cebcbc387 | |||
| 390f2bf6b0 | |||
| 32c4704b74 | |||
| b4832c4b55 | |||
| 35d0cc1d57 | |||
| 992b23297c | |||
| a5e114da73 | |||
| 392c0ad13c | |||
| faf38d20a4 | |||
| 8204c80881 | |||
| e10d561f92 | |||
| 5df524b771 | |||
| a65bea9533 | |||
| 13b5cd95a7 | |||
| eab6daf64b | |||
| 3232b4e8bf | |||
| 34273ff010 | |||
| 42011f0665 | |||
| ca6fad675b | |||
| c9baf00762 | |||
| 1f6c3c92af | |||
| 689c4d017d | |||
| 6df92c0618 | |||
| 460fbec776 | |||
| 1706757587 | |||
| b9db670ede | |||
| 5cb8d38ad1 | |||
| b94bdbb8f8 | |||
| 03b7c69ed7 | |||
| 4fc68b88e7 | |||
| ef09517061 | |||
| f15bfcfa27 | |||
| 9939867aba | |||
| 192768e109 | |||
| 758edbb4d7 |
@@ -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
|
||||
|
||||
@@ -8,7 +8,23 @@
|
||||
"eqnull": true,
|
||||
"quotmark": "single",
|
||||
"trailing": true,
|
||||
"undef": true,
|
||||
"browser": true,
|
||||
"jquery": true,
|
||||
"globals": {
|
||||
"angular": true
|
||||
"angular": false,
|
||||
|
||||
// For Jasmine
|
||||
"after" : false,
|
||||
"afterEach" : false,
|
||||
"before" : false,
|
||||
"beforeEach" : false,
|
||||
"describe" : false,
|
||||
"expect" : false,
|
||||
"jasmine" : false,
|
||||
"module" : false,
|
||||
"spyOn" : false,
|
||||
"inject" : false,
|
||||
"it" : false
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
lib-cov
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
|
||||
pids
|
||||
logs
|
||||
results
|
||||
# test coverage files
|
||||
.coverage/
|
||||
|
||||
node_modules
|
||||
npm-debug.log
|
||||
|
||||
.git
|
||||
docs
|
||||
misc
|
||||
.editorconfig
|
||||
.gitattributes
|
||||
.gitignore
|
||||
.jshintrc
|
||||
.travis.yml
|
||||
CONTRIBUTING.md
|
||||
Gruntfile.js
|
||||
karma.conf.js
|
||||
ROADMAP.md
|
||||
|
||||
dist/assets
|
||||
dist/index.html
|
||||
dist/versions-mapping.json
|
||||
dist/*-SNAPSHOT*
|
||||
+15
-7
@@ -1,11 +1,19 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
language: node_js
|
||||
node_js:
|
||||
- "5.9"
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
|
||||
before_script:
|
||||
before_install:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- npm install --quiet -g grunt-cli karma
|
||||
- npm install
|
||||
- npm install --quiet -g karma
|
||||
|
||||
script: grunt
|
||||
script: grunt
|
||||
sudo: false
|
||||
|
||||
+1712
-382
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
|
||||
+8
-5
@@ -1,13 +1,15 @@
|
||||
## Got a question or problem?
|
||||
|
||||
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`.
|
||||
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 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
|
||||
* questions and answers stay available for public viewing so your question / answer might help someone else
|
||||
* SO voting system assures that the best answers are prominently visible.
|
||||
|
||||
To save your and our time we will be systematically closing all the issues that are request for general support and redirecting people to StackOverflow.
|
||||
To save your and our time we will be systematically closing all the issues that are requests for general support and redirecting people to StackOverflow.
|
||||
|
||||
## You think you've found a bug?
|
||||
|
||||
@@ -15,13 +17,13 @@ Oh, we are ashamed and want to fix it asap! But before fixing a bug we need to r
|
||||
* version of AngularJS used
|
||||
* version of this library that you are using
|
||||
* 3rd-party libraries used, if any
|
||||
* and most importantly - a use-case that fails
|
||||
* and most importantly - a use-case that fails
|
||||
|
||||
A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem.
|
||||
A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem.
|
||||
|
||||
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
|
||||
|
||||
The best part is that you don't need to create plunks from scratch - you can for one from our [demo page](http://angular-ui.github.io/bootstrap/).
|
||||
The best part is that you don't need to create plunks from scratch - you can use one from our [demo page](http://angular-ui.github.io/bootstrap/).
|
||||
|
||||
Unfortunately we are not able to investigate / fix bugs without a minimal reproduce scenario using http://plnkr.co/, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced.
|
||||
|
||||
@@ -40,3 +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/stevemao/conventional-changelog-angular/blob/master/convention.md)
|
||||
|
||||
+170
-78
@@ -1,24 +1,16 @@
|
||||
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-ngdocs');
|
||||
grunt.loadNpmTasks('grunt-ddescribe-iit');
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
// Project configuration.
|
||||
grunt.util.linefeed = '\n';
|
||||
|
||||
grunt.initConfig({
|
||||
ngversion: '1.2.16',
|
||||
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',
|
||||
@@ -28,12 +20,17 @@ module.exports = function(grunt) {
|
||||
modules: 'angular.module("ui.bootstrap", [<%= srcModules %>]);',
|
||||
tplmodules: 'angular.module("ui.bootstrap.tpls", [<%= tplModules %>]);',
|
||||
all: 'angular.module("ui.bootstrap", ["ui.bootstrap.tpls", <%= srcModules %>]);',
|
||||
banner: ['/*',
|
||||
' * <%= pkg.name %>',
|
||||
' * <%= pkg.homepage %>\n',
|
||||
' * Version: <%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>',
|
||||
' * License: <%= pkg.license %>',
|
||||
' */\n'].join('\n')
|
||||
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 %>',
|
||||
' */'
|
||||
].join('\n')
|
||||
},
|
||||
delta: {
|
||||
docs: {
|
||||
@@ -45,22 +42,23 @@ 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']
|
||||
}
|
||||
},
|
||||
concat: {
|
||||
dist: {
|
||||
options: {
|
||||
banner: '<%= meta.banner %><%= meta.modules %>\n'
|
||||
banner: '<%= meta.banner %><%= meta.modules %>\n',
|
||||
footer: '<%= meta.cssInclude %>'
|
||||
},
|
||||
src: [], //src filled in by build task
|
||||
dest: '<%= dist %>/<%= filename %>-<%= pkg.version %>.js'
|
||||
},
|
||||
dist_tpls: {
|
||||
options: {
|
||||
banner: '<%= meta.banner %><%= meta.all %>\n<%= meta.tplmodules %>\n'
|
||||
banner: '<%= meta.banner %><%= meta.all %>\n<%= meta.tplmodules %>\n',
|
||||
footer: '<%= meta.cssInclude %>'
|
||||
},
|
||||
src: [], //src filled in by build task
|
||||
dest: '<%= dist %>/<%= filename %>-tpls-<%= pkg.version %>.js'
|
||||
@@ -106,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,
|
||||
@@ -115,11 +116,8 @@ module.exports = function(grunt) {
|
||||
}]
|
||||
}
|
||||
},
|
||||
jshint: {
|
||||
files: ['Gruntfile.js','src/**/*.js'],
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
}
|
||||
eslint: {
|
||||
files: ['Gruntfile.js','src/**/*.js']
|
||||
},
|
||||
karma: {
|
||||
options: {
|
||||
@@ -133,12 +131,14 @@ module.exports = function(grunt) {
|
||||
},
|
||||
jenkins: {
|
||||
singleRun: true,
|
||||
autoWatch: false,
|
||||
colors: false,
|
||||
reporters: ['dots', 'junit'],
|
||||
browsers: ['Chrome', 'ChromeCanary', 'Firefox', 'Opera', '/Users/jenkins/bin/safari.sh']
|
||||
},
|
||||
travis: {
|
||||
singleRun: true,
|
||||
autoWatch: false,
|
||||
reporters: ['dots'],
|
||||
browsers: ['Firefox']
|
||||
},
|
||||
@@ -149,20 +149,24 @@ module.exports = function(grunt) {
|
||||
reporters: ['progress', 'coverage']
|
||||
}
|
||||
},
|
||||
changelog: {
|
||||
conventionalChangelog: {
|
||||
options: {
|
||||
dest: 'CHANGELOG.md',
|
||||
templateFile: 'misc/changelog.tpl.md',
|
||||
github: 'angular-ui/bootstrap'
|
||||
changelogOpts: {
|
||||
preset: 'angular'
|
||||
},
|
||||
templateFile: 'misc/changelog.tpl.md'
|
||||
},
|
||||
release: {
|
||||
src: 'CHANGELOG.md'
|
||||
}
|
||||
},
|
||||
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',
|
||||
'grunt version', //remove "-SNAPSHOT"
|
||||
'grunt changelog'
|
||||
'grunt conventionalChangelog'
|
||||
],
|
||||
'release-complete': [
|
||||
'git commit CHANGELOG.md package.json -m "chore(release): v%version%"',
|
||||
@@ -173,25 +177,6 @@ module.exports = function(grunt) {
|
||||
'git commit package.json -m "chore(release): Starting v%version%"'
|
||||
]
|
||||
},
|
||||
ngdocs: {
|
||||
options: {
|
||||
dest: 'dist/docs',
|
||||
scripts: [
|
||||
'angular.js',
|
||||
'<%= concat.dist_tpls.dest %>'
|
||||
],
|
||||
styles: [
|
||||
'docs/css/style.css'
|
||||
],
|
||||
navTemplate: 'docs/nav.html',
|
||||
title: 'ui-bootstrap',
|
||||
html5Mode: false
|
||||
},
|
||||
api: {
|
||||
src: ['src/**/*.js', 'src/**/*.ngdoc'],
|
||||
title: 'API Documentation'
|
||||
}
|
||||
},
|
||||
'ddescribe-iit': {
|
||||
files: [
|
||||
'src/**/*.spec.js'
|
||||
@@ -200,8 +185,8 @@ module.exports = function(grunt) {
|
||||
});
|
||||
|
||||
//register before and after test tasks so we've don't have to change cli
|
||||
//options on the goole's CI server
|
||||
grunt.registerTask('before-test', ['enforce', 'ddescribe-iit', 'jshint', 'html2js']);
|
||||
//options on the google's CI server
|
||||
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'
|
||||
@@ -212,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');
|
||||
@@ -237,34 +222,49 @@ 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'),
|
||||
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')
|
||||
}
|
||||
};
|
||||
|
||||
var styles = {
|
||||
css: [],
|
||||
js: []
|
||||
};
|
||||
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');
|
||||
}
|
||||
|
||||
module.dependencies.forEach(findModule);
|
||||
grunt.config('modules', grunt.config('modules').concat(module));
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -302,25 +302,39 @@ 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;
|
||||
})
|
||||
);
|
||||
|
||||
var cssStrings = _.flatten(_.compact(_.pluck(modules, 'css')));
|
||||
var cssJsStrings = _.flatten(_.compact(_.pluck(modules, 'cssJs')));
|
||||
if (cssStrings.length) {
|
||||
grunt.config('meta.cssInclude', cssJsStrings.join('\n'));
|
||||
|
||||
grunt.file.write(grunt.config('meta.cssFileDest'), grunt.config('meta.cssFileBanner') +
|
||||
cssStrings.join('\n'));
|
||||
|
||||
grunt.log.writeln('File ' + grunt.config('meta.cssFileDest') + ' created');
|
||||
}
|
||||
|
||||
var moduleFileMapping = _.clone(modules, true);
|
||||
moduleFileMapping.forEach((module) => delete module.docs);
|
||||
|
||||
grunt.config('moduleFileMapping', moduleFileMapping);
|
||||
|
||||
var srcFiles = _.pluck(modules, 'srcFiles');
|
||||
var tpljsFiles = _.pluck(modules, 'tpljsFiles');
|
||||
//Set the concat task to concatenate the given src modules
|
||||
@@ -330,17 +344,17 @@ module.exports = function(grunt) {
|
||||
grunt.config('concat.dist_tpls.src', grunt.config('concat.dist_tpls.src')
|
||||
.concat(srcFiles).concat(tpljsFiles));
|
||||
|
||||
grunt.task.run(['concat', 'uglify']);
|
||||
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);
|
||||
@@ -350,6 +364,84 @@ module.exports = function(grunt) {
|
||||
}
|
||||
});
|
||||
|
||||
grunt.registerTask('makeModuleMappingFile', function() {
|
||||
var _ = grunt.util._;
|
||||
var moduleMappingJs = 'dist/assets/module-mapping.json';
|
||||
var moduleMappings = grunt.config('moduleFileMapping');
|
||||
var moduleMappingsMap = _.object(_.pluck(moduleMappings, 'name'), moduleMappings);
|
||||
var jsContent = JSON.stringify(moduleMappingsMap);
|
||||
grunt.file.write(moduleMappingJs, jsContent);
|
||||
grunt.log.writeln('File ' + moduleMappingJs.cyan + ' created.');
|
||||
});
|
||||
|
||||
grunt.registerTask('makeRawFilesJs', function() {
|
||||
var _ = grunt.util._;
|
||||
var jsFilename = 'dist/assets/raw-files.json';
|
||||
var genRawFilesJs = require('./misc/raw-files-generator');
|
||||
|
||||
genRawFilesJs(grunt, jsFilename, _.flatten(grunt.config('concat.dist_tpls.src')),
|
||||
grunt.config('meta.banner'), grunt.config('meta.cssFileBanner'));
|
||||
});
|
||||
|
||||
grunt.registerTask('makeVersionsMappingFile', function() {
|
||||
var done = this.async();
|
||||
|
||||
var exec = require('child_process').exec;
|
||||
|
||||
var versionsMappingFile = 'dist/versions-mapping.json';
|
||||
|
||||
exec('git tag --sort -version:refname', function(error, stdout, stderr) {
|
||||
// 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}`
|
||||
};
|
||||
});
|
||||
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.`);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Logic from AngularJS
|
||||
* https://github.com/angular/angular.js/blob/36831eccd1da37c089f2141a2c073a6db69f3e1d/lib/grunt/utils.js#L121-L145
|
||||
*/
|
||||
function processCSS(moduleName, state, minify, file) {
|
||||
var css = fs.readFileSync(file).toString(),
|
||||
js;
|
||||
state.css.push(css);
|
||||
|
||||
if (minify) {
|
||||
css = css
|
||||
.replace(/\r?\n/g, '')
|
||||
.replace(/\/\*.*?\*\//g, '')
|
||||
.replace(/:\s+/g, ':')
|
||||
.replace(/\s*\{\s*/g, '{')
|
||||
.replace(/\s*\}\s*/g, '}')
|
||||
.replace(/\s*\,\s*/g, ',')
|
||||
.replace(/\s*\;\s*/g, ';');
|
||||
}
|
||||
//escape for js
|
||||
css = css
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/'/g, "\\'")
|
||||
.replace(/\r?\n/g, '\\n');
|
||||
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;
|
||||
}
|
||||
|
||||
function setVersion(type, suffix) {
|
||||
var file = 'package.json';
|
||||
var VERSION_REGEX = /([\'|\"]version[\'|\"][ ]*:[ ]*[\'|\"])([\d|.]*)(-\w+)*([\'|\"])/;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2012-2014 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
|
||||
|
||||
@@ -1,30 +1,157 @@
|
||||
# bootstrap - [AngularJS](http://angularjs.org/) directives specific to [Bootstrap](http://getbootstrap.com)
|
||||
|
||||
***
|
||||
### UI Bootstrap - [AngularJS](http://angularjs.org/) directives specific to [Bootstrap](http://getbootstrap.com)
|
||||
|
||||
[](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/)
|
||||
|
||||
## Demo
|
||||
### Quick links
|
||||
- [Demo](#demo)
|
||||
- [Angular 2](#angular-2)
|
||||
- [Installation](#installation)
|
||||
- [NPM](#install-with-npm)
|
||||
- [Bower](#install-with-bower)
|
||||
- [NuGet](#install-with-nuget)
|
||||
- [Custom](#custom-build)
|
||||
- [Manual](#manual-download)
|
||||
- [Webpack / JSPM](#webpack--jspm)
|
||||
- [Support](#support)
|
||||
- [FAQ](#faq)
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
- [PREFIX MIGRATION GUIDE](#prefix-migration-guide)
|
||||
- [Supported browsers](#supported-browsers)
|
||||
- [Need help?](#need-help)
|
||||
- [Found a bug?](#found-a-bug)
|
||||
- [Contributing to the project](#contributing-to-the-project)
|
||||
- [Development, meeting minutes, roadmap and more.](#development-meeting-minutes-roadmap-and-more)
|
||||
|
||||
Do you want to see directives in action? Visit http://angular-ui.github.io/bootstrap/!
|
||||
|
||||
## Installation
|
||||
# Demo
|
||||
|
||||
Installation is easy as angular-ui-bootstrap has minimal dependencies - only the AngularJS and Bootstrap's CSS are required.
|
||||
After downloading dependencies (or better yet, referencing them from your favourite CDN) you need to download build version of this project. All the files and their purposes are described here:
|
||||
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).
|
||||
|
||||
# Installation
|
||||
|
||||
Installation is easy as UI Bootstrap has minimal dependencies - only the AngularJS and Twitter Bootstrap's CSS are required.
|
||||
*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.
|
||||
|
||||
## 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
|
||||
|
||||
```sh
|
||||
$ npm install angular-ui-bootstrap
|
||||
```
|
||||
|
||||
This will install AngularJS and Bootstrap NPM packages.
|
||||
|
||||
#### Install with Bower
|
||||
```sh
|
||||
$ bower install angular-bootstrap
|
||||
```
|
||||
|
||||
Note: do not install 'angular-ui-bootstrap'. A separate repository - [bootstrap-bower](https://github.com/angular-ui/bootstrap-bower) - hosts the compiled javascript file and bower.json.
|
||||
|
||||
#### Install with NuGet
|
||||
To install AngularJS UI Bootstrap, run the following command in the Package Manager Console
|
||||
|
||||
```sh
|
||||
PM> Install-Package Angular.UI.Bootstrap
|
||||
```
|
||||
|
||||
#### Custom build
|
||||
|
||||
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
|
||||
|
||||
After downloading dependencies (or better yet, referencing them from your favorite CDN) you need to download build version of this project. All the files and their purposes are described here:
|
||||
https://github.com/angular-ui/bootstrap/tree/gh-pages#build-files
|
||||
Don't worry, if you are not sure which file to take, opt for `ui-bootstrap-tpls-[version].min.js`.
|
||||
|
||||
### Adding dependency to your project
|
||||
|
||||
When you are done downloading all the dependencies and project files the only remaining part is to add dependencies on the `ui.bootstrap` AngularJS module:
|
||||
|
||||
```javascript
|
||||
```js
|
||||
angular.module('myModule', ['ui.bootstrap']);
|
||||
```
|
||||
|
||||
Project files are also available through your favourite package manager:
|
||||
* **Bower**: `bower install angular-bootstrap`
|
||||
* **NuGet**: https://nuget.org/packages/Angular.UI.Bootstrap/
|
||||
# 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
|
||||
import accordion from 'angular-ui-bootstrap/src/accordion';
|
||||
|
||||
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
|
||||
|
||||
https://github.com/angular-ui/bootstrap/wiki/FAQ
|
||||
|
||||
# Code of Conduct
|
||||
|
||||
Take a moment to read our [Code of Conduct](CODE_OF_CONDUCT.md)
|
||||
|
||||
## PREFIX MIGRATION GUIDE
|
||||
|
||||
If you're updating your application to use prefixes, please check the [migration guide](https://github.com/angular-ui/bootstrap/wiki/Migration-guide-for-prefixes).
|
||||
|
||||
## Supported browsers
|
||||
|
||||
@@ -37,143 +164,25 @@ Directives from this repository are automatically tested with the following brow
|
||||
|
||||
Modern mobile browsers should work without problems.
|
||||
|
||||
**IE 8 is not officially supported at the moment**. This project is run by volunteers and with the current number of commiters
|
||||
we are not in the position to guarantee IE8 support. If you need support for IE8 we would welcome a contributor who would like to take care about IE8.
|
||||
Alternatively you could sponsor this project to guarantee IE8 support.
|
||||
|
||||
We believe that most of the directives would work OK after:
|
||||
* including relevant shims (for ES5 we recommend https://github.com/kriskowal/es5-shim)
|
||||
* taking care of the steps described in http://docs.angularjs.org/guide/ie
|
||||
|
||||
We are simply not regularly testing against IE8.
|
||||
|
||||
## Project philosophy
|
||||
|
||||
### Native, lightweight directives
|
||||
|
||||
We are aiming at providing a set of AngularJS directives based on Bootstrap's markup and CSS. The goal is to provide **native AngularJS directives** without any dependency on jQuery or Bootstrap's JavaScript.
|
||||
It is often better to rewrite an existing JavaScript code and create a new, pure AngularJS directive. Most of the time the resulting directive is smaller as compared to the original JavaScript code size and better integrated into the AngularJS ecosystem.
|
||||
|
||||
### Customizability
|
||||
|
||||
All the directives in this repository should have their markup externalized as templates (loaded via `templateUrl`). In practice it means that you can **customize directive's markup at will**. One could even imagine providing a non-Bootstrap version of the templates!
|
||||
|
||||
### Take what you need and not more
|
||||
|
||||
Each directive has its own AngularJS module without any dependencies on other modules or third-party JavaScript code. In practice it means that you can **just grab the code for the directives you need** and you are not obliged to drag the whole repository.
|
||||
|
||||
### Quality and stability
|
||||
|
||||
Directives should work. All the time and in all browsers. This is why all the directives have a comprehensive suite of unit tests. All the automated tests are executed on each checkin in several browsers: Chrome, ChromeCanary, Firefox, Opera, Safari, IE9.
|
||||
In fact we are fortunate enough to **benefit from the same testing infrastructure as AngularJS**!
|
||||
|
||||
## Support
|
||||
|
||||
If you are having problems making some directives work, there are several ways to get help:
|
||||
## Need help?
|
||||
Need help using UI Bootstrap?
|
||||
|
||||
* Live help in the IRC (`#angularjs` channel at the `freenode` network). Use this [webchat](https://webchat.freenode.net/) or your own IRC client.
|
||||
* Ask a question in [stackoverflow](http://stackoverflow.com/) under the [angular-ui-bootstrap](http://stackoverflow.com/questions/tagged/angular-ui-bootstrap) tag.
|
||||
* Write your question in our [mailing list](https://groups.google.com/forum/#!categories/angular-ui/bootstrap).
|
||||
* Ask a question in [StackOverflow](http://stackoverflow.com/) under the [angular-ui-bootstrap](http://stackoverflow.com/questions/tagged/angular-ui-bootstrap) tag.
|
||||
|
||||
Project's issue on GitHub should be used discuss bugs and features.
|
||||
**Please do not create new issues in this repository to ask questions about using UI Bootstrap**
|
||||
|
||||
## Contributing to the project
|
||||
## Found a bug?
|
||||
Please take a look at [CONTRIBUTING.md](CONTRIBUTING.md#you-think-youve-found-a-bug) and submit your issue [here](https://github.com/angular-ui/bootstrap/issues/new).
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
# Contributing to the project
|
||||
|
||||
We are always looking for the quality contributions! Please check the [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution guidelines.
|
||||
|
||||
### Development
|
||||
#### Prepare your environment
|
||||
* Install [Node.js](http://nodejs.org/) and NPM (should come with)
|
||||
* Install global dev dependencies: `npm install -g grunt-cli karma`
|
||||
* Install local dev dependencies: `npm install` while current directory is bootstrap repo
|
||||
# Development, meeting minutes, roadmap and more.
|
||||
|
||||
#### Build
|
||||
* Build the whole project: `grunt` - this will run `lint`, `test`, and `concat` targets
|
||||
* To build modules, first run `grunt html2js` then `grunt build:module1:module2...:moduleN`
|
||||
|
||||
You can generate a custom build, containing only needed modules, from the project's homepage.
|
||||
Alternatively you can run local Grunt build from the command line and list needed modules as shown below:
|
||||
|
||||
```javascript
|
||||
grunt build:modal:tabs:alert:popover:dropdownToggle:buttons:progressbar
|
||||
```
|
||||
|
||||
Check the Grunt build file for other tasks that are defined for this project.
|
||||
|
||||
#### TDD
|
||||
* Run test: `grunt watch`
|
||||
|
||||
This will start Karma server and will continuously watch files in the project, executing tests upon every change.
|
||||
|
||||
#### Test coverage
|
||||
Add the `--coverage` option (e.g. `grunt test --coverage`, `grunt watch --coverage`) to see reports on the test coverage. These coverage reports are found in the coverage folder.
|
||||
|
||||
### Customize templates
|
||||
|
||||
As mentioned directives from this repository have all the markup externalized in templates. You might want to customize default
|
||||
templates to match your desired look & feel, add new functionality etc.
|
||||
|
||||
The easiest way to override an individual template is to use the `<script>` directive:
|
||||
|
||||
```html
|
||||
<script id="template/alert/alert.html" type="text/ng-template">
|
||||
<div class='alert' ng-class='type && "alert-" + type'>
|
||||
<button ng-show='closeable' type='button' class='close' ng-click='close()'>Close</button>
|
||||
<div ng-transclude></div>
|
||||
</div>
|
||||
</script>
|
||||
```
|
||||
|
||||
If you want to override more templates it makes sense to store them as individual files and feed the `$templateCache` from those partials.
|
||||
For people using Grunt as the build tool it can be easily done using the `grunt-html2js` plugin. You can also configure your own template url.
|
||||
Let's have a look:
|
||||
|
||||
Your own template url is `views/partials/ui-bootstrap-tpls/alert/alert.html`.
|
||||
|
||||
Add "html2js" task to your Gruntfile
|
||||
```javascript
|
||||
html2js: {
|
||||
options: {
|
||||
base: '.',
|
||||
module: 'ui-templates',
|
||||
rename: function (modulePath) {
|
||||
var moduleName = modulePath.replace('app/views/partials/ui-bootstrap-tpls/', '').replace('.html', '');
|
||||
return 'template' + '/' + moduleName + '.html';
|
||||
}
|
||||
},
|
||||
main: {
|
||||
src: ['app/views/partials/ui-bootstrap-tpls/**/*.html'],
|
||||
dest: '.tmp/ui-templates.js'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to load your template.js file
|
||||
`<script src="/ui-templates.js"></script>`
|
||||
|
||||
Inject the `ui-templates` module in your `app.js`
|
||||
```javascript
|
||||
angular.module('myApp', [
|
||||
'ui.bootstrap',
|
||||
'ui-templates'
|
||||
]);
|
||||
```
|
||||
|
||||
Then it will work fine!
|
||||
|
||||
For more information visit: https://github.com/karlgoldstein/grunt-html2js
|
||||
|
||||
### Release
|
||||
* Bump up version number in `package.json`
|
||||
* Commit the version change with the following message: `chore(release): [version number]`
|
||||
* tag
|
||||
* push changes and a tag (`git push --tags`)
|
||||
* switch to the `gh-pages` branch: `git checkout gh-pages`
|
||||
* copy content of the dist folder to the main folder
|
||||
* Commit the version change with the following message: `chore(release): [version number]`
|
||||
* push changes
|
||||
* switch back to the `main branch` and modify `package.json` to bump up version for the next iteration
|
||||
* commit (`chore(release): starting [version number]`) and push
|
||||
* publish Bower and NuGet packages
|
||||
|
||||
Well done! (If you don't like repeating yourself open a PR with a grunt task taking care of the above!)
|
||||
Head over to the [Wiki](https://github.com/angular-ui/bootstrap/wiki) for notes on development for UI Bootstrap, meeting minutes from the UI Bootstrap team, roadmap plans, project philosophy and more.
|
||||
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
## Roadmap
|
||||
|
||||
#### Directive Maintainers
|
||||
|
||||
Who will take the lead regarding any pull requests or decisions for a a directive?
|
||||
|
||||
<table width="100%">
|
||||
<th>Component</th><th>Maintainer</th>
|
||||
<tr>
|
||||
<td>accordion</td><td>@ajoslin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>alert</td><td>@pkozlowski</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>bindHtml</td><td>frozen, use $sce?</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>buttons</td><td> @pkozlowski</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>carousel</td><td>@ajoslin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>collapse</td><td>$animate (@chrisirhc)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>datepicker</td><td>@bekos</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>dropdownToggle</td><td>@bekos</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>modal</td><td>@pkozlowski</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>pagination</td><td>@bekos</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>popover/tooltip</td><td>@chrisirhc</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>position</td><td>@ajoslin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>progressbar</td><td>@bekos</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>rating</td><td>@bekos</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>tabs</td><td>@ajoslin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>timepicker</td><td>@bekos</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>transition</td><td>@frozen, remove (@chrisirhc)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>typeahead</td><td>@pkozlowski, @chrisirhc</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
#### Attribute Prefix
|
||||
|
||||
Each directive should make its own two-letter prefix
|
||||
|
||||
`<tab tb-active=”true” tb-select=”doThis()”>`
|
||||
|
||||
#### Use $animate
|
||||
|
||||
* @chrisirhc is leading this
|
||||
|
||||
#### New Build system
|
||||
|
||||
* @ajoslin is leading this
|
||||
* Building everything on travis commit
|
||||
* Push to bower, nuget, cdnjs, etc
|
||||
|
||||
#### Switch to ngdocs
|
||||
|
||||
* http://github.com/petebacondarwin/angular-doc-gen
|
||||
|
||||
### Conventions for whether attributes/options should be watched/evaluated-once
|
||||
|
||||
- Boolean attributes
|
||||
- Stick AngularJS conventions rather than Bootstrap conventions
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
.bs-docs-social {
|
||||
margin-top: 1em;
|
||||
padding: 15px 0;
|
||||
text-align: center;
|
||||
background-color: rgba(245,245,245,0.3);
|
||||
border-top: 1px solid rgba(255,255,255,0.3);
|
||||
border-bottom: 1px solid rgba(221,221,221,0.3);
|
||||
}
|
||||
.bs-docs-social-buttons {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
margin-left: 0;
|
||||
margin-bottom: 0;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.bs-docs-social-buttons li {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<div class="btn-toolbar btn-group pull-right">
|
||||
<a class="btn btn-small btn-primary" href="https://github.com/angular-ui/bootstrap/tree/gh-pages">
|
||||
<i class="icon-download-alt icon-white"></i> Download <small>(<%= pkg.version%>)</small>
|
||||
</a>
|
||||
</div>
|
||||
<div class="margin: 10px;"></div>
|
||||
<ul class="nav pull-right bs-docs-social-buttons">
|
||||
<li>
|
||||
<iframe src="http://ghbtns.com/github-btn.html?user=angular-ui&repo=bootstrap&type=watch&count=true"
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="110" height="20"></iframe>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
require('./dist/ui-bootstrap-tpls');
|
||||
|
||||
module.exports = 'ui.bootstrap';
|
||||
+68
-45
@@ -1,58 +1,81 @@
|
||||
// Karma configuration
|
||||
// Generated on Sat Mar 28 2015 11:17:34 GMT-0700 (PDT)
|
||||
|
||||
// base path, that will be used to resolve files and exclude
|
||||
basePath = '.';
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files = [
|
||||
JASMINE,
|
||||
JASMINE_ADAPTER,
|
||||
'misc/test-lib/jquery-1.8.2.min.js',
|
||||
'misc/test-lib/angular.js',
|
||||
'misc/test-lib/angular-mocks.js',
|
||||
'misc/test-lib/helpers.js',
|
||||
'src/**/*.js',
|
||||
'template/**/*.js'
|
||||
];
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: '',
|
||||
|
||||
// list of files to exclude
|
||||
exclude = [
|
||||
'src/**/docs/*'
|
||||
];
|
||||
|
||||
// Start these browsers, currently available:
|
||||
// - Chrome
|
||||
// - ChromeCanary
|
||||
// - Firefox
|
||||
// - Opera
|
||||
// - Safari
|
||||
// - PhantomJS
|
||||
browsers = [
|
||||
'Chrome'
|
||||
];
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ['jasmine'],
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: dots || progress
|
||||
reporters = ['progress'];
|
||||
|
||||
reportSlowerThan = 100;
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'misc/test-lib/jquery-1.8.2.min.js',
|
||||
'node_modules/angular/angular.js',
|
||||
'node_modules/angular-mocks/angular-mocks.js',
|
||||
'node_modules/angular-sanitize/angular-sanitize.js',
|
||||
'misc/test-lib/helpers.js',
|
||||
'src/**/*.js',
|
||||
'template/**/*.js'
|
||||
],
|
||||
|
||||
// web server port
|
||||
port = 9018;
|
||||
|
||||
// cli runner port
|
||||
runnerPort = 9100;
|
||||
// list of files to exclude
|
||||
exclude: [
|
||||
'src/**/index.js',
|
||||
'src/**/index-nocss.js',
|
||||
'src/**/docs/*'
|
||||
],
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors = true;
|
||||
|
||||
// level of logging
|
||||
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
|
||||
logLevel = LOG_INFO;
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
preprocessors: {
|
||||
'src/*/{*.js,!(test)/**/*.js}': ['coverage']
|
||||
},
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch = false;
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, it capture browsers, run tests and exit
|
||||
singleRun = false;
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ['progress', 'coverage'],
|
||||
|
||||
coverageReporter: {
|
||||
dir: '.coverage/',
|
||||
type: 'html'
|
||||
},
|
||||
|
||||
reportSlowerThan: 100,
|
||||
|
||||
// web server port
|
||||
port: 9876,
|
||||
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: true,
|
||||
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: ['Chrome'],
|
||||
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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) { %>
|
||||
|
||||
+250
-25
@@ -1,41 +1,84 @@
|
||||
angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'plunker', 'ngTouch'], function($httpProvider){
|
||||
FastClick.attach(document.body);
|
||||
/* global FastClick, smoothScroll */
|
||||
angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'plunker', 'ngTouch', 'ngAnimate', 'ngSanitize'], function($httpProvider){
|
||||
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
|
||||
if ($location.path() !== '' && $location.path() !== '/') {
|
||||
smoothScroll(document.getElementById($location.path().substring(1)), 500, function(el) {
|
||||
location.replace(el.id);
|
||||
location.replace('#' + el.id);
|
||||
});
|
||||
}
|
||||
}]);
|
||||
}]).factory('buildFilesService', function ($http, $q) {
|
||||
|
||||
var builderUrl = "http://50.116.42.77:3001";
|
||||
var moduleMap;
|
||||
var rawFiles;
|
||||
|
||||
return {
|
||||
getModuleMap: getModuleMap,
|
||||
getRawFiles: getRawFiles,
|
||||
get: function () {
|
||||
return $q.all({
|
||||
moduleMap: getModuleMap(),
|
||||
rawFiles: getRawFiles()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function getModuleMap() {
|
||||
return moduleMap ? $q.when(moduleMap) : $http.get('assets/module-mapping.json')
|
||||
.then(function (result) {
|
||||
moduleMap = result.data;
|
||||
return moduleMap;
|
||||
});
|
||||
}
|
||||
|
||||
function getRawFiles() {
|
||||
return rawFiles ? $q.when(rawFiles) : $http.get('assets/raw-files.json')
|
||||
.then(function (result) {
|
||||
rawFiles = result.data;
|
||||
return rawFiles;
|
||||
});
|
||||
}
|
||||
|
||||
})
|
||||
.controller('MainCtrl', MainCtrl)
|
||||
.controller('SelectModulesCtrl', SelectModulesCtrl)
|
||||
.controller('DownloadCtrl', DownloadCtrl);
|
||||
|
||||
function MainCtrl($scope, $http, $document, $uibModal, orderByFilter) {
|
||||
// Grab old version docs
|
||||
$http.get('/bootstrap/versions-mapping.json')
|
||||
.then(function(result) {
|
||||
$scope.oldDocs = result.data;
|
||||
});
|
||||
|
||||
function MainCtrl($scope, $http, $document, $modal, orderByFilter) {
|
||||
$scope.showBuildModal = function() {
|
||||
var modalInstance = $modal.open({
|
||||
var modalInstance = $uibModal.open({
|
||||
templateUrl: 'buildModal.html',
|
||||
controller: 'SelectModulesCtrl',
|
||||
resolve: {
|
||||
modules: function() {
|
||||
return $http.get(builderUrl + "/api/bootstrap").then(function(response) {
|
||||
return response.data.modules;
|
||||
});
|
||||
modules: function(buildFilesService) {
|
||||
return buildFilesService.getModuleMap()
|
||||
.then(function (moduleMap) {
|
||||
return Object.keys(moduleMap);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showDownloadModal = function() {
|
||||
var modalInstance = $modal.open({
|
||||
var modalInstance = $uibModal.open({
|
||||
templateUrl: 'downloadModal.html',
|
||||
controller: 'DownloadCtrl'
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
var SelectModulesCtrl = function($scope, $modalInstance, modules) {
|
||||
function SelectModulesCtrl($scope, $uibModalInstance, modules, buildFilesService) {
|
||||
$scope.selectedModules = [];
|
||||
$scope.modules = modules;
|
||||
|
||||
@@ -48,23 +91,160 @@ var SelectModulesCtrl = function($scope, $modalInstance, modules) {
|
||||
};
|
||||
|
||||
$scope.downloadBuild = function () {
|
||||
$modalInstance.close($scope.selectedModules);
|
||||
$uibModalInstance.close($scope.selectedModules);
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$modalInstance.dismiss();
|
||||
$uibModalInstance.dismiss();
|
||||
};
|
||||
|
||||
$scope.download = function (selectedModules) {
|
||||
var downloadUrl = builderUrl + "/api/bootstrap/download?";
|
||||
angular.forEach(selectedModules, function(module) {
|
||||
downloadUrl += "modules=" + module + "&";
|
||||
$scope.isOldBrowser = function () {
|
||||
return isOldBrowser;
|
||||
};
|
||||
|
||||
$scope.build = function (selectedModules, version) {
|
||||
/* global JSZip, saveAs */
|
||||
var moduleMap, rawFiles;
|
||||
|
||||
buildFilesService.get().then(function (buildFiles) {
|
||||
moduleMap = buildFiles.moduleMap;
|
||||
rawFiles = buildFiles.rawFiles;
|
||||
|
||||
generateBuild();
|
||||
});
|
||||
return downloadUrl;
|
||||
};
|
||||
};
|
||||
|
||||
var DownloadCtrl = function($scope, $modalInstance) {
|
||||
function generateBuild() {
|
||||
var srcModuleNames = selectedModules
|
||||
.map(function (module) {
|
||||
return moduleMap[module];
|
||||
})
|
||||
.reduce(function (toBuild, module) {
|
||||
addIfNotExists(toBuild, module.name);
|
||||
|
||||
module.dependencies.forEach(function (depName) {
|
||||
addIfNotExists(toBuild, depName);
|
||||
});
|
||||
return toBuild;
|
||||
}, []);
|
||||
|
||||
var srcModules = srcModuleNames
|
||||
.map(function (moduleName) {
|
||||
return moduleMap[moduleName];
|
||||
});
|
||||
|
||||
var srcModuleFullNames = srcModules
|
||||
.map(function (module) {
|
||||
return module.moduleName;
|
||||
});
|
||||
|
||||
var srcJsContent = srcModules
|
||||
.reduce(function (buildFiles, module) {
|
||||
return buildFiles.concat(module.srcFiles);
|
||||
}, [])
|
||||
.map(getFileContent)
|
||||
.join('\n')
|
||||
;
|
||||
|
||||
var jsFile = createNoTplFile(srcModuleFullNames, srcJsContent);
|
||||
|
||||
var tplModuleNames = srcModules
|
||||
.reduce(function (tplModuleNames, module) {
|
||||
return tplModuleNames.concat(module.tplModules);
|
||||
}, []);
|
||||
|
||||
var tplJsContent = srcModules
|
||||
.reduce(function (buildFiles, module) {
|
||||
return buildFiles.concat(module.tpljsFiles);
|
||||
}, [])
|
||||
.map(getFileContent)
|
||||
.join('\n')
|
||||
;
|
||||
|
||||
var jsTplFile = createWithTplFile(srcModuleFullNames, srcJsContent, tplModuleNames, tplJsContent);
|
||||
|
||||
var cssContent = srcModules
|
||||
.map(function (module) {
|
||||
return module.css;
|
||||
})
|
||||
.filter(function (css) {
|
||||
return css;
|
||||
})
|
||||
.join('\n')
|
||||
;
|
||||
|
||||
var cssJsContent = srcModules
|
||||
.map(function (module) {
|
||||
return module.cssJs;
|
||||
})
|
||||
.filter(function (cssJs) {
|
||||
return cssJs;
|
||||
})
|
||||
.join('\n')
|
||||
;
|
||||
|
||||
var footer = cssJsContent;
|
||||
|
||||
var zip = new JSZip();
|
||||
zip.file('ui-bootstrap-custom-' + version + '.js', rawFiles.banner + jsFile + footer);
|
||||
zip.file('ui-bootstrap-custom-' + version + '.min.js', rawFiles.banner + uglify(jsFile + footer));
|
||||
zip.file('ui-bootstrap-custom-tpls-' + version + '.js', rawFiles.banner + jsTplFile + footer);
|
||||
zip.file('ui-bootstrap-custom-tpls-' + version + '.min.js', rawFiles.banner + uglify(jsTplFile + footer));
|
||||
zip.file('ui-bootstrap-custom-tpls-' + version + '.min.js', rawFiles.banner + uglify(jsTplFile + footer));
|
||||
|
||||
if (cssContent) {
|
||||
zip.file('ui-bootstrap-custom-' + version + '-csp.css', rawFiles.cssBanner + cssContent);
|
||||
}
|
||||
|
||||
saveAs(zip.generate({type: 'blob'}), 'ui-bootstrap-custom-build.zip');
|
||||
}
|
||||
|
||||
function createNoTplFile(srcModuleNames, srcJsContent) {
|
||||
return 'angular.module("ui.bootstrap", [' + srcModuleNames.join(',') + ']);\n' +
|
||||
srcJsContent;
|
||||
}
|
||||
|
||||
function createWithTplFile(srcModuleNames, srcJsContent, tplModuleNames, tplJsContent) {
|
||||
var depModuleNames = srcModuleNames.slice();
|
||||
depModuleNames.unshift('"ui.bootstrap.tpls"');
|
||||
|
||||
return 'angular.module("ui.bootstrap", [' + depModuleNames.join(',') + ']);\n' +
|
||||
'angular.module("ui.bootstrap.tpls", [' + tplModuleNames.join(',') + ']);\n' +
|
||||
srcJsContent + '\n' + tplJsContent;
|
||||
|
||||
}
|
||||
|
||||
function addIfNotExists(array, element) {
|
||||
if (array.indexOf(element) == -1) {
|
||||
array.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
function getFileContent(fileName) {
|
||||
return rawFiles.files[fileName];
|
||||
}
|
||||
|
||||
function uglify(js) {
|
||||
/* global UglifyJS */
|
||||
|
||||
var ast = UglifyJS.parse(js);
|
||||
ast.figure_out_scope();
|
||||
|
||||
var compressor = UglifyJS.Compressor();
|
||||
var compressedAst = ast.transform(compressor);
|
||||
|
||||
compressedAst.figure_out_scope();
|
||||
compressedAst.compute_char_frequency();
|
||||
compressedAst.mangle_names();
|
||||
|
||||
var stream = UglifyJS.OutputStream();
|
||||
compressedAst.print(stream);
|
||||
|
||||
return stream.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function DownloadCtrl($scope, $uibModalInstance) {
|
||||
$scope.options = {
|
||||
minified: true,
|
||||
tpls: true
|
||||
@@ -87,6 +267,51 @@ var DownloadCtrl = function($scope, $modalInstance) {
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$modalInstance.dismiss();
|
||||
$uibModalInstance.dismiss();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* The following compatibility check is from:
|
||||
*
|
||||
* Bootstrap Customizer (http://getbootstrap.com/customize/)
|
||||
* Copyright 2011-2014 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Creative Commons Attribution 3.0 Unported License. For
|
||||
* details, see http://creativecommons.org/licenses/by/3.0/.
|
||||
*/
|
||||
var isOldBrowser;
|
||||
(function () {
|
||||
|
||||
var supportsFile = (window.File && window.FileReader && window.FileList && window.Blob);
|
||||
function failback() {
|
||||
isOldBrowser = true;
|
||||
}
|
||||
/**
|
||||
* Based on:
|
||||
* Blob Feature Check v1.1.0
|
||||
* https://github.com/ssorallen/blob-feature-check/
|
||||
* License: Public domain (http://unlicense.org)
|
||||
*/
|
||||
var url = window.URL;
|
||||
var svg = new Blob(
|
||||
['<svg xmlns=\'http://www.w3.org/2000/svg\'></svg>'],
|
||||
{ type: 'image/svg+xml;charset=utf-8' }
|
||||
);
|
||||
var objectUrl = url.createObjectURL(svg);
|
||||
|
||||
if (/^blob:/.exec(objectUrl) === null || !supportsFile) {
|
||||
// `URL.createObjectURL` created a URL that started with something other
|
||||
// than "blob:", which means it has been polyfilled and is not supported by
|
||||
// this browser.
|
||||
failback();
|
||||
} else {
|
||||
angular.element('<img/>')
|
||||
.on('load', function () {
|
||||
isOldBrowser = false;
|
||||
})
|
||||
.on('error', failback)
|
||||
.attr('src', objectUrl);
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@@ -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.
|
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);
|
||||
@@ -16,6 +16,8 @@ angular.module('plunker', [])
|
||||
'<html ng-app="ui.bootstrap.demo">\n' +
|
||||
' <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' +
|
||||
@@ -27,7 +29,7 @@ angular.module('plunker', [])
|
||||
};
|
||||
|
||||
var scriptContent = function(content) {
|
||||
return "angular.module('ui.bootstrap.demo', ['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
|
||||
|
||||
File diff suppressed because one or more lines are too long
+116
-58
@@ -4,13 +4,17 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<link rel="icon" href="assets/favicon.ico" />
|
||||
<title>Angular directives for Bootstrap</title>
|
||||
<meta name="description" content="AngularJS (Angular) native directives for Bootstrap. Small footprint (5kB gzipped!), no 3rd party JS dependencies (jQuery, bootstrap JS) required! Widgets: <% demoModules.forEach(function(module) { %><%= module.displayName %>, <% }); %>">
|
||||
<meta name="google-site-verification" content="7lc5HyceLDqpV_6oNHteYFfxDJH7-S3DwnJKtNUKcRg" />
|
||||
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/fastclick/0.6.7/fastclick.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.0.0/FileSaver.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/jszip/2.4.0/jszip.min.js"></script>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular.min.js"></script>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular-animate.min.js"></script>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular-touch.min.js"></script>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular-sanitize.js"></script>
|
||||
<script src="ui-bootstrap-tpls-<%= pkg.version%>.min.js"></script>
|
||||
<script src="assets/plunker.js"></script>
|
||||
<script src="assets/app.js"></script>
|
||||
@@ -21,41 +25,49 @@
|
||||
<link rel="author" href="https://github.com/angular-ui/bootstrap/graphs/contributors">
|
||||
</head>
|
||||
<body class="ng-cloak" ng-controller="MainCtrl">
|
||||
<header class="navbar navbar-default navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-3" ng-click="isCollapsed = !isCollapsed">
|
||||
<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 visible-xs" href="#">UI Bootstrap</a>
|
||||
</div>
|
||||
<nav class="hidden-xs">
|
||||
<ul class="nav navbar-nav">
|
||||
<a href="#top" role="button" class="navbar-brand">
|
||||
UI Bootstrap
|
||||
</a>
|
||||
<li class="dropdown">
|
||||
<a role="button" class="dropdown-toggle">
|
||||
Directives <b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<% demoModules.forEach(function(module) { %><li><a href="#<%= module.name %>"><%= module.displayName %></a></li><% }); %>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#getting_started">Getting started</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav class="visible-xs" collapse="!isCollapsed">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="#getting_started" ng-click="isCollapsed = !isCollapsed">Getting started</a></li>
|
||||
<li><a href="#directives_small" ng-click="isCollapsed = !isCollapsed">Directives</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<header class="navbar navbar-default navbar-fixed-top navbar-inner">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-3" ng-click="isCollapsed = !isCollapsed">
|
||||
<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 visible-xs" href="#">UI Bootstrap</a>
|
||||
</div>
|
||||
<nav class="hidden-xs">
|
||||
<ul class="nav navbar-nav">
|
||||
<a href="#top" role="button" class="navbar-brand">
|
||||
UI Bootstrap
|
||||
</a>
|
||||
<li class="dropdown" uib-dropdown>
|
||||
<a role="button" class="dropdown-toggle" uib-dropdown-toggle>
|
||||
Directives <b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<% demoModules.forEach(function(module) { %><li><a href="#<%= module.name %>"><%= module.displayName %></a></li><% }); %>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#getting_started">Getting started</a></li>
|
||||
<li class="dropdown" uib-dropdown>
|
||||
<a role="button" class="dropdown-toggle" uib-dropdown-toggle>
|
||||
Previous docs <b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="version in oldDocs">
|
||||
<a ng-href="{{version.url}}">{{version.version}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav class="visible-xs" uib-collapse="!isCollapsed">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="#getting_started" ng-click="isCollapsed = !isCollapsed">Getting started</a></li>
|
||||
<li><a href="#directives_small" ng-click="isCollapsed = !isCollapsed">Directives</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<div class="header-placeholder"></div>
|
||||
@@ -135,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
|
||||
@@ -142,7 +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.2.x, tested with <%= ngversion %>)</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.
|
||||
@@ -152,23 +175,51 @@
|
||||
<p>
|
||||
Build files for all directives are distributed in several flavours: minified for production usage, un-minified
|
||||
for development, with or without templates. All the options are described and can be
|
||||
<a href="https://github.com/angular-ui/bootstrap/tree/gh-pages">downloaded from here</a>.
|
||||
<a href="https://github.com/angular-ui/bootstrap/tree/gh-pages">downloaded from here</a>. It should be noted that the <code>-tpls</code> files contain the templates bundled in JavaScript, while the regular version does not contain the bundled templates. For more information, check out the FAQ <a href="https://github.com/angular-ui/bootstrap/wiki/FAQ#full-explanation">here</a> and the README <a href="https://github.com/angular-ui/bootstrap/tree/gh-pages#build-files">here</a>.
|
||||
</p>
|
||||
<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:
|
||||
<20kB for all directives (~5kB 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>
|
||||
</p>
|
||||
<h3>FAQ</h3>
|
||||
<p>Please check <a href="https://github.com/angular-ui/bootstrap/wiki/FAQ" target="_blank">our FAQ section</a> for common problems / solutions.</p>
|
||||
<h3>Reading the documentation</h3>
|
||||
<p>
|
||||
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. 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>
|
||||
* 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) { %>
|
||||
<section id="<%= module.name %>">
|
||||
@@ -189,20 +240,20 @@
|
||||
<div class="row code">
|
||||
<div class="col-md-12" ng-controller="PlunkerCtrl">
|
||||
<div class="pull-right">
|
||||
<button class="btn btn-info" id="plunk-btn" ng-click="edit('<%= ngversion%>', '<%= bsversion %>', '<%= pkg.version%>', '<%= module.name %>')"><i class="glyphicon glyphicon-edit"></i> Edit in plunker</button>
|
||||
<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>
|
||||
<tabset>
|
||||
<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>
|
||||
</tab>
|
||||
<tab heading="JavaScript">
|
||||
</uib-tab>
|
||||
<uib-tab index="1" heading="JavaScript">
|
||||
<div plunker-content="javascript">
|
||||
<pre ng-non-bindable><code data-language="javascript"><%- module.docs.js %></code></pre>
|
||||
</div>
|
||||
</tab>
|
||||
</tabset>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -214,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>
|
||||
@@ -231,8 +282,8 @@
|
||||
<label class="col-sm-3 control-label"><strong>Build</strong></label>
|
||||
<div class="col-sm-9">
|
||||
<span class="btn-group">
|
||||
<button type="button" class="btn btn-default" ng-model="options.minified" btn-radio="true">Minified</button>
|
||||
<button type="button" class="btn btn-default" ng-model="options.minified" btn-radio="false">Uncompressed</button>
|
||||
<button type="button" class="btn btn-default" ng-model="options.minified" uib-btn-radio="true">Minified</button>
|
||||
<button type="button" class="btn btn-default" ng-model="options.minified" uib-btn-radio="false">Uncompressed</button>
|
||||
</span>
|
||||
<small class="help-block">Use <b>Minified</b> version in your deployed application. <b>Uncompressed</b> source code is useful only for debugging and development purpose.</small>
|
||||
</div>
|
||||
@@ -241,18 +292,17 @@
|
||||
<label class="col-sm-3 control-label"><strong>Include Templates</strong></label>
|
||||
<div class="col-sm-9">
|
||||
<span class="btn-group">
|
||||
<button type="button" class="btn btn-default" ng-model="options.tpls" btn-radio="true">Yes</button>
|
||||
<button type="button" class="btn btn-default" ng-model="options.tpls" btn-radio="false">No</button>
|
||||
<button type="button" class="btn btn-default" ng-model="options.tpls" uib-btn-radio="true">Yes</button>
|
||||
<button type="button" class="btn btn-default" ng-model="options.tpls" uib-btn-radio="false">No</button>
|
||||
</span>
|
||||
<small class="help-block">Whether you want to include the <i>default templates</i>, bundled with most of the directives. These templates are based on Bootstrap's markup and CSS.</small>
|
||||
</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>
|
||||
@@ -271,10 +321,15 @@
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div ng-show="isOldBrowser()">
|
||||
Your current browser doesn't support creating custom builds.
|
||||
Please take a second to <a href="http://browsehappy.com/">upgrade to a
|
||||
more modern browser</a> (other than Safari).
|
||||
</div>
|
||||
<div ng-show="buildErrorText">
|
||||
<h4 style="text-align: center;">{{buildErrorText}}</h4>
|
||||
</div>
|
||||
<div ng-hide="buildErrorText">
|
||||
<div ng-hide="buildErrorText || isOldBrowser()">
|
||||
<% modules.forEach(function(module,i) { %>
|
||||
<% if (i % 3 === 0) {%>
|
||||
<div class="btn-group" style="width: 100%;">
|
||||
@@ -283,7 +338,7 @@
|
||||
style="width: 33%; border-radius: 0;"
|
||||
ng-class="{'btn-primary': module.<%= module.name %>}"
|
||||
ng-model="module.<%= module.name %>"
|
||||
btn-checkbox
|
||||
uib-btn-checkbox
|
||||
ng-change="selectedChanged('<%= module.name %>', module.<%= module.name %>)">
|
||||
<%= module.displayName %>
|
||||
</button>
|
||||
@@ -295,7 +350,9 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-default" ng-click="cancel()">Close</a>
|
||||
<a class="btn btn-primary" ng-disabled="!selectedModules.length" ng-href="{{selectedModules.length ? download(selectedModules) : ''}}">
|
||||
<a class="btn btn-primary" ng-hide="isOldBrowser()"
|
||||
ng-disabled="isOldBrowser() !== false && !selectedModules.length"
|
||||
ng-click="selectedModules.length && build(selectedModules, '<%= pkg.version %>')">
|
||||
<i class="glyphicon glyphicon-download-alt"></i> Download {{selectedModules.length}} Modules
|
||||
</a>
|
||||
</div>
|
||||
@@ -315,5 +372,6 @@
|
||||
|
||||
</script>
|
||||
<script src="assets/smoothscroll-angular-custom.js"></script>
|
||||
<script src="assets/uglifyjs.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*!
|
||||
* Forked from:
|
||||
* Bootstrap Grunt task for generating raw-files.min.js for the Customizer
|
||||
* http://getbootstrap.com
|
||||
* Copyright 2014 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
/* jshint node: true */
|
||||
|
||||
'use strict';
|
||||
var fs = require('fs');
|
||||
|
||||
function getFiles(filePaths) {
|
||||
var files = {};
|
||||
filePaths
|
||||
.forEach(function (path) {
|
||||
files[path] = fs.readFileSync(path, 'utf8');
|
||||
});
|
||||
return files;
|
||||
}
|
||||
|
||||
module.exports = function generateRawFilesJs(grunt, jsFilename, files, banner, cssBanner) {
|
||||
if (!banner) {
|
||||
banner = '';
|
||||
}
|
||||
|
||||
if (!cssBanner) {
|
||||
cssBanner = '';
|
||||
}
|
||||
|
||||
var filesJsObject = {
|
||||
banner: banner,
|
||||
cssBanner: cssBanner,
|
||||
files: getFiles(files),
|
||||
};
|
||||
|
||||
var filesJsContent = JSON.stringify(filesJsObject);
|
||||
try {
|
||||
fs.writeFileSync(jsFilename, filesJsContent);
|
||||
}
|
||||
catch (err) {
|
||||
grunt.fail.warn(err);
|
||||
}
|
||||
grunt.log.writeln('File ' + jsFilename.cyan + ' created.');
|
||||
};
|
||||
Vendored
-2163
File diff suppressed because it is too large
Load Diff
Vendored
-21464
File diff suppressed because it is too large
Load Diff
+49
-10
@@ -1,18 +1,57 @@
|
||||
// jasmine matcher for expecting an element to have a css class
|
||||
// https://github.com/angular/angular.js/blob/master/test/matchers.js
|
||||
beforeEach(function() {
|
||||
this.addMatchers({
|
||||
toHaveClass: function(cls) {
|
||||
this.message = function() {
|
||||
return "Expected '" + this.actual + "'" + (this.isNot ? ' not ' : ' ') + "to have class '" + cls + "'.";
|
||||
};
|
||||
jasmine.addMatchers({
|
||||
toHaveClass: function(util, customEqualityTesters) {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
var result = {
|
||||
pass: actual.hasClass(expected)
|
||||
};
|
||||
|
||||
return this.actual.hasClass(cls);
|
||||
if (result.pass) {
|
||||
result.message = 'Expected "' + actual + '" not to have the "' + expected + '" class.';
|
||||
} else {
|
||||
result.message = 'Expected "' + actual + '" to have the "' + expected + '" class.';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
},
|
||||
toBeHidden: function () {
|
||||
var element = angular.element(this.actual);
|
||||
return element.hasClass('ng-hide') ||
|
||||
element.css('display') == 'none';
|
||||
toBeHidden: function(util, customEqualityTesters) {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
var result = {
|
||||
pass: actual.hasClass('ng-hide') || actual.css('display') === 'none'
|
||||
};
|
||||
|
||||
if (result.pass) {
|
||||
result.message = 'Expected "' + actual + '" not to be hidden';
|
||||
} else {
|
||||
result.message = 'Expected "' + actual + '" to be hidden';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
},
|
||||
toHaveFocus: function(util, customEqualityTesters) {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
var result = {
|
||||
pass: document.activeElement === actual[0]
|
||||
};
|
||||
|
||||
if (result.pass) {
|
||||
result.message = 'Expected "' + actual + '" not to have focus';
|
||||
} else {
|
||||
result.message = 'Expected "' + actual + '" to have focus';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+52
-15
@@ -1,24 +1,61 @@
|
||||
{
|
||||
"author": "https://github.com/angular-ui/bootstrap/graphs/contributors",
|
||||
"name": "angular-ui-bootstrap",
|
||||
"version": "0.11.2",
|
||||
"version": "2.5.1-SNAPSHOT",
|
||||
"description": "Native AngularJS (Angular) directives for Bootstrap",
|
||||
"homepage": "http://angular-ui.github.io/bootstrap/",
|
||||
"keywords": [
|
||||
"angularjs",
|
||||
"angular",
|
||||
"bootstrap",
|
||||
"ui"
|
||||
],
|
||||
"dependencies": {},
|
||||
"directories": {
|
||||
"lib": "src/"
|
||||
},
|
||||
"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": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular-ui/bootstrap.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-ngdocs": "~0.1.1",
|
||||
"grunt-conventional-changelog": "~0.1.2",
|
||||
"grunt-contrib-concat": "~0.3.0",
|
||||
"grunt-contrib-copy": "~0.5.0",
|
||||
"grunt-contrib-uglify": "~0.3.0",
|
||||
"grunt-contrib-watch": "~0.5.0",
|
||||
"grunt-contrib-jshint": "~0.8.0",
|
||||
"grunt-html2js": "~0.2.0",
|
||||
"grunt-karma": "~0.4.4",
|
||||
"node-markdown": "0.1.1",
|
||||
"semver": "~2.2.0",
|
||||
"shelljs": "~0.2.0",
|
||||
"grunt-ddescribe-iit": "0.0.4"
|
||||
"angular": "1.6.1",
|
||||
"angular-mocks": "1.6.1",
|
||||
"angular-sanitize": "1.6.1",
|
||||
"grunt": "^0.4.5",
|
||||
"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.22",
|
||||
"karma-chrome-launcher": "^0.2.0",
|
||||
"karma-coverage": "^0.5.0",
|
||||
"karma-firefox-launcher": "^0.1.4",
|
||||
"karma-jasmine": "^0.3.5",
|
||||
"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.6.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
+60
-45
@@ -1,20 +1,20 @@
|
||||
angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
||||
angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse', 'ui.bootstrap.tabindex'])
|
||||
|
||||
.constant('accordionConfig', {
|
||||
.constant('uibAccordionConfig', {
|
||||
closeOthers: true
|
||||
})
|
||||
|
||||
.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {
|
||||
|
||||
.controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
|
||||
// This array keeps track of the accordion groups
|
||||
this.groups = [];
|
||||
|
||||
// Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
|
||||
this.closeOthers = function(openGroup) {
|
||||
var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
|
||||
if ( closeOthers ) {
|
||||
angular.forEach(this.groups, function (group) {
|
||||
if ( group !== openGroup ) {
|
||||
var closeOthers = angular.isDefined($attrs.closeOthers) ?
|
||||
$scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
|
||||
if (closeOthers) {
|
||||
angular.forEach(this.groups, function(group) {
|
||||
if (group !== openGroup) {
|
||||
group.isOpen = false;
|
||||
}
|
||||
});
|
||||
@@ -26,7 +26,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
||||
var that = this;
|
||||
this.groups.push(groupScope);
|
||||
|
||||
groupScope.$on('$destroy', function (event) {
|
||||
groupScope.$on('$destroy', function(event) {
|
||||
that.removeGroup(groupScope);
|
||||
});
|
||||
};
|
||||
@@ -34,35 +34,37 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
||||
// This is called from the accordion-group directive when to remove itself
|
||||
this.removeGroup = function(group) {
|
||||
var index = this.groups.indexOf(group);
|
||||
if ( index !== -1 ) {
|
||||
if (index !== -1) {
|
||||
this.groups.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
}])
|
||||
|
||||
// The accordion directive simply sets up the directive controller
|
||||
// and adds an accordion CSS class to itself element.
|
||||
.directive('accordion', function () {
|
||||
.directive('uibAccordion', function() {
|
||||
return {
|
||||
restrict:'EA',
|
||||
controller:'AccordionController',
|
||||
controller: 'UibAccordionController',
|
||||
controllerAs: 'accordion',
|
||||
transclude: true,
|
||||
replace: false,
|
||||
templateUrl: 'template/accordion/accordion.html'
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/accordion/accordion.html';
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
|
||||
.directive('accordionGroup', function() {
|
||||
.directive('uibAccordionGroup', function() {
|
||||
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:'template/accordion/accordion-group.html',
|
||||
require: '^uibAccordion', // We need this directive to be inside an accordion
|
||||
transclude: true, // It transcludes the contents of the directive into the template
|
||||
restrict: 'A',
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
|
||||
},
|
||||
scope: {
|
||||
heading: '@', // Interpolate the heading attribute onto this scope
|
||||
panelClass: '@?', // Ditto with panelClass
|
||||
isOpen: '=?',
|
||||
isDisabled: '=?'
|
||||
},
|
||||
@@ -72,59 +74,72 @@ 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 || 'panel-default';
|
||||
scope.$watch('isOpen', function(value) {
|
||||
if ( value ) {
|
||||
element.toggleClass(scope.openClass, !!value);
|
||||
if (value) {
|
||||
accordionCtrl.closeOthers(scope);
|
||||
}
|
||||
});
|
||||
|
||||
scope.toggleOpen = function() {
|
||||
if ( !scope.isDisabled ) {
|
||||
scope.isOpen = !scope.isOpen;
|
||||
scope.toggleOpen = function($event) {
|
||||
if (!scope.isDisabled) {
|
||||
if (!$event || $event.which === 32) {
|
||||
scope.isOpen = !scope.isOpen;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
|
||||
scope.headingId = id + '-tab';
|
||||
scope.panelId = id + '-panel';
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
// Use accordion-heading below an accordion-group to provide a heading containing HTML
|
||||
// <accordion-group>
|
||||
// <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading>
|
||||
// </accordion-group>
|
||||
.directive('accordionHeading', function() {
|
||||
.directive('uibAccordionHeading', function() {
|
||||
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) {
|
||||
require: '^uibAccordionGroup',
|
||||
link: function(scope, element, attrs, accordionGroupCtrl, transclude) {
|
||||
// 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, function() {}));
|
||||
accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
// Use in the accordion-group template to indicate where you want the heading to be transcluded
|
||||
// You must provide the property on the accordion-group controller that will hold the transcluded element
|
||||
// <div class="accordion-group">
|
||||
// <div class="accordion-heading" ><a ... accordion-transclude="heading">...</a></div>
|
||||
// ...
|
||||
// </div>
|
||||
.directive('accordionTransclude', function() {
|
||||
.directive('uibAccordionTransclude', function() {
|
||||
return {
|
||||
require: '^accordionGroup',
|
||||
link: function(scope, element, attr, controller) {
|
||||
scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
|
||||
if ( heading ) {
|
||||
element.html('');
|
||||
element.append(heading);
|
||||
require: '^uibAccordionGroup',
|
||||
link: function(scope, element, attrs, controller) {
|
||||
scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
|
||||
if (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]';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,30 +1,60 @@
|
||||
<div ng-controller="AccordionDemoCtrl">
|
||||
<script type="text/ng-template" id="group-template.html">
|
||||
<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>
|
||||
|
||||
<p>
|
||||
<button class="btn btn-default btn-sm" ng-click="status.open = !status.open">Toggle last panel</button>
|
||||
<button class="btn btn-default btn-sm" ng-click="status.isFirstDisabled = ! status.isFirstDisabled">Enable / Disable first panel</button>
|
||||
<button type="button" class="btn btn-default btn-sm" ng-click="status.open = !status.open">Toggle last panel</button>
|
||||
<button type="button" class="btn btn-default btn-sm" ng-click="status.isFirstDisabled = ! status.isFirstDisabled">Enable / Disable first panel</button>
|
||||
</p>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" ng-model="oneAtATime">
|
||||
Open only one at a time
|
||||
</label>
|
||||
<accordion close-others="oneAtATime">
|
||||
<accordion-group heading="Static Header, initially expanded" is-open="status.isFirstOpen" is-disabled="status.isFirstDisabled">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="oneAtATime">
|
||||
Open only one at a time
|
||||
</label>
|
||||
</div>
|
||||
<uib-accordion close-others="oneAtATime">
|
||||
<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.
|
||||
</accordion-group>
|
||||
<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}}
|
||||
</accordion-group>
|
||||
<accordion-group heading="Dynamic Body Content">
|
||||
<p>The body of the accordion group grows to fit the contents</p>
|
||||
<button class="btn btn-default btn-sm" ng-click="addItem()">Add Item</button>
|
||||
<div ng-repeat="item in items">{{item}}</div>
|
||||
</accordion-group>
|
||||
<accordion-group is-open="status.open">
|
||||
<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>
|
||||
</accordion-heading>
|
||||
This is just some content to illustrate fancy headings.
|
||||
</accordion-group>
|
||||
</accordion>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
<div uib-accordion-group class="panel-default" heading="Custom template" template-url="group-template.html">
|
||||
Hello
|
||||
</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>
|
||||
</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.
|
||||
</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
|
||||
};
|
||||
|
||||
@@ -1,5 +1,49 @@
|
||||
The **accordion directive** builds on top of the collapse directive to provide a list of items, with collapsible bodies that are collapsed or expanded by clicking on the item's header.
|
||||
|
||||
We can control whether expanding an item will cause the other items to close, using the `close-others` attribute on accordion.
|
||||
The body of each accordion group is transcluded into the body of the collapsible element.
|
||||
|
||||
The body of each accordion group is transcluded in to the body of the collapsible element.
|
||||
### 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.
|
||||
|
||||
* `template-url`
|
||||
_(Default: `uib/template/accordion/accordion-group.html`)_ -
|
||||
Add the ability to override the template used on the component.
|
||||
|
||||
### 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;
|
||||
@@ -1,20 +1,21 @@
|
||||
describe('accordion', function () {
|
||||
var $scope;
|
||||
describe('uib-accordion', function() {
|
||||
var $animate, $scope;
|
||||
|
||||
beforeEach(module('ui.bootstrap.accordion'));
|
||||
beforeEach(module('template/accordion/accordion.html'));
|
||||
beforeEach(module('template/accordion/accordion-group.html'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
beforeEach(module('uib/template/accordion/accordion.html'));
|
||||
beforeEach(module('uib/template/accordion/accordion-group.html'));
|
||||
|
||||
beforeEach(inject(function ($rootScope) {
|
||||
beforeEach(inject(function(_$animate_, $rootScope) {
|
||||
$animate = _$animate_;
|
||||
$scope = $rootScope;
|
||||
}));
|
||||
|
||||
describe('controller', function () {
|
||||
|
||||
var ctrl, $element, $attrs;
|
||||
beforeEach(inject(function($controller) {
|
||||
$attrs = {}; $element = {};
|
||||
ctrl = $controller('AccordionController', { $scope: $scope, $element: $element, $attrs: $attrs });
|
||||
$attrs = {};
|
||||
ctrl = $controller('UibAccordionController', { $scope: $scope, $attrs: $attrs });
|
||||
}));
|
||||
|
||||
describe('addGroup', function() {
|
||||
@@ -35,6 +36,7 @@ describe('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);
|
||||
@@ -61,13 +63,14 @@ describe('accordion', function () {
|
||||
|
||||
describe('setting accordionConfig', function() {
|
||||
var originalCloseOthers;
|
||||
beforeEach(inject(function(accordionConfig) {
|
||||
originalCloseOthers = accordionConfig.closeOthers;
|
||||
accordionConfig.closeOthers = false;
|
||||
beforeEach(inject(function(uibAccordionConfig) {
|
||||
originalCloseOthers = uibAccordionConfig.closeOthers;
|
||||
uibAccordionConfig.closeOthers = false;
|
||||
}));
|
||||
afterEach(inject(function(accordionConfig) {
|
||||
|
||||
afterEach(inject(function(uibAccordionConfig) {
|
||||
// return it to the original value
|
||||
accordionConfig.closeOthers = originalCloseOthers;
|
||||
uibAccordionConfig.closeOthers = originalCloseOthers;
|
||||
}));
|
||||
|
||||
it('should not close other panels if accordionConfig.closeOthers is false', function() {
|
||||
@@ -80,7 +83,7 @@ describe('accordion', function () {
|
||||
});
|
||||
|
||||
describe('removeGroup', function() {
|
||||
it('should remove the specified panel', function () {
|
||||
it('should remove the specified panel', function() {
|
||||
var group1, group2, group3;
|
||||
ctrl.addGroup(group1 = $scope.$new());
|
||||
ctrl.addGroup(group2 = $scope.$new());
|
||||
@@ -90,7 +93,7 @@ describe('accordion', function () {
|
||||
expect(ctrl.groups[0]).toBe(group1);
|
||||
expect(ctrl.groups[1]).toBe(group3);
|
||||
});
|
||||
it('should ignore remove of non-existing panel', function () {
|
||||
it('should ignore remove of non-existing panel', function() {
|
||||
var group1, group2;
|
||||
ctrl.addGroup(group1 = $scope.$new());
|
||||
ctrl.addGroup(group2 = $scope.$new());
|
||||
@@ -98,47 +101,108 @@ describe('accordion', function () {
|
||||
ctrl.removeGroup({});
|
||||
expect(ctrl.groups.length).toBe(2);
|
||||
});
|
||||
it('should remove a panel when the scope is destroyed', function() {
|
||||
var group1, group2, group3;
|
||||
ctrl.addGroup(group1 = $scope.$new());
|
||||
ctrl.addGroup(group2 = $scope.$new());
|
||||
ctrl.addGroup(group3 = $scope.$new());
|
||||
group2.$destroy();
|
||||
expect(ctrl.groups.length).toBe(2);
|
||||
expect(ctrl.groups[0]).toBe(group1);
|
||||
expect(ctrl.groups[1]).toBe(group3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('accordion-group', function () {
|
||||
describe('uib-accordion', function() {
|
||||
var scope, $compile, $templateCache, element;
|
||||
|
||||
beforeEach(inject(function($rootScope, _$compile_, _$templateCache_) {
|
||||
scope = $rootScope;
|
||||
$compile = _$compile_;
|
||||
$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('uib/template/accordion/accordion.html', '<div>{{accordion.text}}</div>');
|
||||
|
||||
element = $compile('<uib-accordion></uib-accordion>')(scope);
|
||||
scope.$digest();
|
||||
|
||||
var ctrl = element.controller('uibAccordion');
|
||||
expect(ctrl).toBeDefined();
|
||||
|
||||
ctrl.text = 'foo';
|
||||
scope.$digest();
|
||||
|
||||
expect(element.html()).toBe('<div class="ng-binding">foo</div>');
|
||||
});
|
||||
|
||||
it('should allow custom templates', function() {
|
||||
$templateCache.put('foo/bar.html', '<div>baz</div>');
|
||||
|
||||
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 findGroupLink = function (index) {
|
||||
return groups.eq(index).find('a').eq(0);
|
||||
var findGroupHeading = function(index) {
|
||||
return groups.eq(index).find('.panel-heading').eq(0);
|
||||
};
|
||||
var findGroupBody = function (index) {
|
||||
var findGroupLink = function(index) {
|
||||
return groups.eq(index).find('.accordion-toggle').eq(0);
|
||||
};
|
||||
var findGroupBody = function(index) {
|
||||
return groups.eq(index).find('.panel-collapse').eq(0);
|
||||
};
|
||||
|
||||
|
||||
beforeEach(inject(function(_$rootScope_, _$compile_) {
|
||||
scope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
}));
|
||||
|
||||
afterEach(function () {
|
||||
element = groups = scope = $compile = undefined;
|
||||
});
|
||||
it('should allow custom templates', inject(function($templateCache) {
|
||||
$templateCache.put('foo/bar.html', '<div>baz</div>');
|
||||
|
||||
describe('with static panels', function () {
|
||||
beforeEach(function () {
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<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('<div>baz</div>');
|
||||
}));
|
||||
|
||||
describe('with static panels', function() {
|
||||
beforeEach(function() {
|
||||
spyOn(Math, 'random').and.returnValue(0.1);
|
||||
var tpl =
|
||||
'<accordion>' +
|
||||
'<accordion-group heading="title 1">Content 1</accordion-group>' +
|
||||
'<accordion-group heading="title 2">Content 2</accordion-group>' +
|
||||
'</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();
|
||||
});
|
||||
|
||||
it('should create accordion panels with content', function () {
|
||||
it('should create accordion panels with content', function() {
|
||||
expect(groups.length).toEqual(2);
|
||||
expect(findGroupLink(0).text()).toEqual('title 1');
|
||||
expect(findGroupBody(0).text().trim()).toEqual('Content 1');
|
||||
@@ -146,34 +210,116 @@ describe('accordion', function () {
|
||||
expect(findGroupBody(1).text().trim()).toEqual('Content 2');
|
||||
});
|
||||
|
||||
it('should change selected element on click', function () {
|
||||
it('should change selected element on click', 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() {
|
||||
var group = groups.eq(0);
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(group).toHaveClass('panel-open');
|
||||
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(group).not.toHaveClass('panel-open');
|
||||
});
|
||||
|
||||
it('should toggle element on spacebar when focused', function() {
|
||||
var group = groups.eq(0);
|
||||
findGroupLink(0)[0].focus();
|
||||
var e = $.Event('keypress');
|
||||
e.which = 32;
|
||||
findGroupLink(0).trigger(e);
|
||||
|
||||
expect(group).toHaveClass('panel-open');
|
||||
|
||||
e = $.Event('keypress');
|
||||
e.which = 32;
|
||||
findGroupLink(0).trigger(e);
|
||||
|
||||
expect(group).not.toHaveClass('panel-open');
|
||||
});
|
||||
|
||||
it('should not toggle with any other keyCode', function() {
|
||||
var group = groups.eq(0);
|
||||
findGroupLink(0)[0].focus();
|
||||
var e = $.Event('keypress');
|
||||
e.which = 65;
|
||||
findGroupLink(0).trigger(e);
|
||||
|
||||
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 dynamic panels', function () {
|
||||
var model;
|
||||
beforeEach(function () {
|
||||
describe('with open-class attribute', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<accordion>' +
|
||||
'<accordion-group ng-repeat="group in groups" heading="{{group.name}}">{{group.content}}</accordion-group>' +
|
||||
'</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();
|
||||
});
|
||||
|
||||
it('should add custom-open-class when opened', function() {
|
||||
var group = groups.eq(0);
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(group).toHaveClass('custom-open-class');
|
||||
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(group).not.toHaveClass('custom-open-class');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with dynamic panels', function() {
|
||||
var model;
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<div uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}">{{group.content}}</div>' +
|
||||
'</uib-accordion>';
|
||||
element = angular.element(tpl);
|
||||
model = [
|
||||
{name: 'title 1', content: 'Content 1'},
|
||||
@@ -184,7 +330,7 @@ describe('accordion', function () {
|
||||
scope.$digest();
|
||||
});
|
||||
|
||||
it('should have no panels initially', function () {
|
||||
it('should have no panels initially', function() {
|
||||
groups = element.find('.panel');
|
||||
expect(groups.length).toEqual(0);
|
||||
});
|
||||
@@ -200,7 +346,7 @@ describe('accordion', function () {
|
||||
expect(findGroupBody(1).text().trim()).toEqual('Content 2');
|
||||
});
|
||||
|
||||
it('should react properly on removing items from the model', function () {
|
||||
it('should react properly on removing items from the model', function() {
|
||||
scope.groups = model;
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
@@ -214,12 +360,12 @@ describe('accordion', function () {
|
||||
});
|
||||
|
||||
describe('is-open attribute', function() {
|
||||
beforeEach(function () {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<accordion>' +
|
||||
'<accordion-group heading="title 1" is-open="open.first">Content 1</accordion-group>' +
|
||||
'<accordion-group heading="title 2" is-open="open.second">Content 2</accordion-group>' +
|
||||
'</accordion>';
|
||||
'<uib-accordion>' +
|
||||
'<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 };
|
||||
$compile(element)(scope);
|
||||
@@ -227,7 +373,7 @@ describe('accordion', function () {
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
|
||||
it('should open the panel with isOpen set to true', function () {
|
||||
it('should open the panel with isOpen set to true', function() {
|
||||
expect(findGroupBody(0).scope().isOpen).toBe(false);
|
||||
expect(findGroupBody(1).scope().isOpen).toBe(true);
|
||||
});
|
||||
@@ -244,12 +390,12 @@ describe('accordion', function () {
|
||||
});
|
||||
|
||||
describe('is-open attribute with dynamic content', function() {
|
||||
beforeEach(function () {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<accordion>' +
|
||||
'<accordion-group heading="title 1" is-open="open1"><div ng-repeat="item in items">{{item}}</div></accordion-group>' +
|
||||
'<accordion-group heading="title 2" is-open="open2">Static content</accordion-group>' +
|
||||
'</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;
|
||||
@@ -257,6 +403,7 @@ describe('accordion', function () {
|
||||
angular.element(document.body).append(element);
|
||||
$compile(element)(scope);
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
|
||||
@@ -264,18 +411,18 @@ describe('accordion', function () {
|
||||
element.remove();
|
||||
});
|
||||
|
||||
it('should have visible panel body when the group with isOpen set to true', function () {
|
||||
expect(findGroupBody(0)[0].clientHeight).not.toBe(0);
|
||||
expect(findGroupBody(1)[0].clientHeight).toBe(0);
|
||||
it('should have visible panel body when the group with isOpen set to true', function() {
|
||||
expect(findGroupBody(0)).toHaveClass('in');
|
||||
expect(findGroupBody(1)).not.toHaveClass('in');
|
||||
});
|
||||
});
|
||||
|
||||
describe('is-open attribute with dynamic groups', function () {
|
||||
beforeEach(function () {
|
||||
describe('is-open attribute with dynamic groups', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<accordion>' +
|
||||
'<accordion-group ng-repeat="group in groups" heading="{{group.name}}" is-open="group.open">{{group.content}}</accordion-group>' +
|
||||
'</accordion>';
|
||||
'<uib-accordion>' +
|
||||
'<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 = [
|
||||
{name: 'title 1', content: 'Content 1', open: false},
|
||||
@@ -287,7 +434,7 @@ describe('accordion', function () {
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
|
||||
it('should have visible group body when the group with isOpen set to true', function () {
|
||||
it('should have visible group body when the group with isOpen set to true', function() {
|
||||
expect(findGroupBody(0).scope().isOpen).toBe(false);
|
||||
expect(findGroupBody(1).scope().isOpen).toBe(true);
|
||||
});
|
||||
@@ -305,13 +452,36 @@ describe('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 () {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<accordion>' +
|
||||
'<accordion-group heading="title 1" is-disabled="disabled">Content 1</accordion-group>' +
|
||||
'</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);
|
||||
@@ -320,7 +490,7 @@ describe('accordion', function () {
|
||||
groupBody = findGroupBody(0);
|
||||
});
|
||||
|
||||
it('should open the panel with isOpen set to true', function () {
|
||||
it('should open the panel with isOpen set to true', function() {
|
||||
expect(groupBody.scope().isOpen).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -339,55 +509,86 @@ describe('accordion', function () {
|
||||
scope.$digest();
|
||||
expect(groupBody.scope().isOpen).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have text-muted styling', function() {
|
||||
expect(findGroupLink(0).find('span:first')).toHaveClass('text-muted');
|
||||
});
|
||||
});
|
||||
|
||||
describe('accordion-heading element', 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]">' +
|
||||
'<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);
|
||||
}
|
||||
|
||||
describe('uib-accordion-heading element', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<accordion ng-init="a = [1,2,3]">' +
|
||||
'<accordion-group heading="I get overridden">' +
|
||||
'<accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </accordion-heading>' +
|
||||
'<uib-accordion ng-init="a = [1,2,3]">' +
|
||||
'<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' +
|
||||
'</accordion-group>' +
|
||||
'</accordion>';
|
||||
'</div>' +
|
||||
'</uib-accordion>';
|
||||
element = $compile(tpl)(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
it('transcludes the <accordion-heading> content into the heading link', function() {
|
||||
|
||||
it('transcludes the <uib-accordion-heading> content into the heading link', function() {
|
||||
expect(findGroupLink(0).text()).toBe('Heading Element 123 ');
|
||||
});
|
||||
|
||||
it('attaches the same scope to the transcluded heading and body', function() {
|
||||
expect(findGroupLink(0).find('span').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() {
|
||||
expect(findGroupLink(0).find('span:first').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should have disabled styling when is-disabled is true', isDisabledStyleCheck);
|
||||
});
|
||||
|
||||
describe('accordion-heading attribute', function() {
|
||||
describe('uib-accordion-heading attribute', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<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>' +
|
||||
'<uib-accordion ng-init="a = [1,2,3]">' +
|
||||
'<div uib-accordion-group heading="I get overridden">' +
|
||||
'<div uib-accordion-heading>Heading Element <span ng-repeat="x in a">{{x}}</span> </div>' +
|
||||
'Body' +
|
||||
'</accordion-group>' +
|
||||
'</accordion>';
|
||||
'</div>' +
|
||||
'</uib-accordion>';
|
||||
element = $compile(tpl)(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
it('transcludes the <accordion-heading> content into the heading link', function() {
|
||||
|
||||
it('transcludes the <uib-accordion-heading> content into the heading link', function() {
|
||||
expect(findGroupLink(0).text()).toBe('Heading Element 123 ');
|
||||
});
|
||||
|
||||
it('attaches the same scope to the transcluded heading and body', function() {
|
||||
expect(findGroupLink(0).find('span').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('accordion-heading, with repeating accordion-groups', function() {
|
||||
it('should clone the accordion-heading for each group', function() {
|
||||
element = $compile('<accordion><accordion-group ng-repeat="x in [1,2,3]"><accordion-heading>{{x}}</accordion-heading></accordion-group></accordion>')(scope);
|
||||
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><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);
|
||||
@@ -397,10 +598,9 @@ describe('accordion', function () {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('accordion-heading attribute, with repeating accordion-groups', function() {
|
||||
it('should clone the accordion-heading for each group', function() {
|
||||
element = $compile('<accordion><accordion-group ng-repeat="x in [1,2,3]"><div accordion-heading>{{x}}</div></accordion-group></accordion>')(scope);
|
||||
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><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);
|
||||
@@ -410,5 +610,15 @@ describe('accordion', function () {
|
||||
});
|
||||
});
|
||||
|
||||
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><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');
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+24
-9
@@ -1,18 +1,33 @@
|
||||
angular.module('ui.bootstrap.alert', [])
|
||||
|
||||
.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) {
|
||||
$scope.closeable = 'close' in $attrs;
|
||||
.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;
|
||||
|
||||
if (dismissOnTimeout) {
|
||||
$timeout(function() {
|
||||
$scope.close();
|
||||
}, parseInt(dismissOnTimeout, 10));
|
||||
}
|
||||
}])
|
||||
|
||||
.directive('alert', function () {
|
||||
.directive('uibAlert', function() {
|
||||
return {
|
||||
restrict:'EA',
|
||||
controller:'AlertController',
|
||||
templateUrl:'template/alert/alert.html',
|
||||
transclude:true,
|
||||
replace:true,
|
||||
controller: 'UibAlertController',
|
||||
controllerAs: 'alert',
|
||||
restrict: 'A',
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/alert/alert.html';
|
||||
},
|
||||
transclude: true,
|
||||
scope: {
|
||||
type: '@',
|
||||
close: '&'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<div ng-controller="AlertDemoCtrl">
|
||||
<alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</alert>
|
||||
<button class='btn btn-default' ng-click="addAlert()">Add Alert</button>
|
||||
<script type="text/ng-template" id="alert.html">
|
||||
<div ng-transclude></div>
|
||||
</script>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
Alert is an AngularJS-version of bootstrap's alert.
|
||||
This directive can be used both to generate alerts from static and dynamic model data (using the `ng-repeat` directive).
|
||||
|
||||
This directive can be used to generate alerts from the dynamic model data (using the ng-repeat directive);
|
||||
### uib-alert settings
|
||||
|
||||
The presence of the "close" attribute determines if a close button is displayed
|
||||
* `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;
|
||||
@@ -1,21 +1,22 @@
|
||||
describe('alert', function () {
|
||||
var scope, $compile;
|
||||
var element;
|
||||
describe('uib-alert', function() {
|
||||
var element, scope, $compile, $templateCache, $timeout;
|
||||
|
||||
beforeEach(module('ui.bootstrap.alert'));
|
||||
beforeEach(module('template/alert/alert.html'));
|
||||
|
||||
beforeEach(inject(function ($rootScope, _$compile_) {
|
||||
beforeEach(module('uib/template/alert/alert.html'));
|
||||
|
||||
beforeEach(inject(function($rootScope, _$compile_, _$templateCache_, _$timeout_) {
|
||||
scope = $rootScope;
|
||||
$compile = _$compile_;
|
||||
$templateCache = _$templateCache_;
|
||||
$timeout = _$timeout_;
|
||||
|
||||
element = angular.element(
|
||||
'<div>' +
|
||||
'<alert ng-repeat="alert in alerts" type="{{alert.type}}"' +
|
||||
'close="removeAlert($index)">{{alert.msg}}' +
|
||||
'</alert>' +
|
||||
'</div>');
|
||||
'<div>' +
|
||||
'<div uib-alert ng-repeat="alert in alerts" ' +
|
||||
'ng-class="\'alert-\' + (alert.type || \'warning\')" ' +
|
||||
'close="removeAlert($index)">{{alert.msg}}' +
|
||||
'</div>' +
|
||||
'</div>');
|
||||
|
||||
scope.alerts = [
|
||||
{ msg:'foo', type:'success'},
|
||||
@@ -35,29 +36,36 @@ describe('alert', function () {
|
||||
}
|
||||
|
||||
function findContent(index) {
|
||||
return element.find('div[ng-transclude] span').eq(index);
|
||||
return element.find('div[ng-transclude]').eq(index);
|
||||
}
|
||||
|
||||
it('should generate alerts using ng-repeat', function () {
|
||||
var alerts = createAlerts();
|
||||
expect(alerts.length).toEqual(3);
|
||||
});
|
||||
it('should expose the controller to the view', function() {
|
||||
$templateCache.put('uib/template/alert/alert.html', '<div>{{alert.text}}</div>');
|
||||
|
||||
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';
|
||||
element = $compile('<div uib-alert></div>')(scope);
|
||||
scope.$digest();
|
||||
|
||||
expect(alerts.eq(0)).toHaveClass('alert-error');
|
||||
var ctrl = element.controller('uib-alert');
|
||||
expect(ctrl).toBeDefined();
|
||||
|
||||
ctrl.text = 'foo';
|
||||
scope.$digest();
|
||||
|
||||
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('<div uib-alert template-url="foo/bar.html"></div>')(scope);
|
||||
scope.$digest();
|
||||
|
||||
expect(element.html()).toBe('<div>baz</div>');
|
||||
});
|
||||
|
||||
it('should generate alerts using ng-repeat', function() {
|
||||
var alerts = createAlerts();
|
||||
expect(alerts.length).toEqual(3);
|
||||
});
|
||||
|
||||
it('should show the alert content', function() {
|
||||
@@ -68,20 +76,19 @@ describe('alert', function () {
|
||||
}
|
||||
});
|
||||
|
||||
it('should show close buttons and have the dismissable class', function () {
|
||||
it('should show close buttons and have the dismissible class', function() {
|
||||
var alerts = createAlerts();
|
||||
|
||||
for (var i = 0, n = alerts.length; i < n; i++) {
|
||||
expect(findCloseButton(i).css('display')).not.toBe('none');
|
||||
expect(alerts.eq(i)).toHaveClass('alert-dismissable');
|
||||
expect(alerts.eq(i)).toHaveClass('alert-dismissible');
|
||||
}
|
||||
});
|
||||
|
||||
it('should fire callback when closed', function () {
|
||||
|
||||
it('should fire callback when closed', function() {
|
||||
var alerts = createAlerts();
|
||||
|
||||
scope.$apply(function () {
|
||||
scope.$apply(function() {
|
||||
scope.removeAlert = jasmine.createSpy();
|
||||
});
|
||||
|
||||
@@ -91,18 +98,32 @@ describe('alert', function () {
|
||||
expect(scope.removeAlert).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('should not show close button and have the dismissable class if no close callback specified', function () {
|
||||
element = $compile('<alert>No close</alert>')(scope);
|
||||
it('should not show close button and have the dismissible class if no close callback specified', function() {
|
||||
element = $compile('<div uib-alert>No close</div>')(scope);
|
||||
scope.$digest();
|
||||
expect(findCloseButton(0)).toBeHidden();
|
||||
expect(element).not.toHaveClass('alert-dismissable');
|
||||
expect(element).not.toHaveClass('alert-dismissible');
|
||||
});
|
||||
|
||||
it('should be possible to add additional classes for alert', function () {
|
||||
var element = $compile('<alert class="alert-block" type="info">Default alert!</alert>')(scope);
|
||||
it('should close automatically if dismiss-on-timeout is defined on the element', function() {
|
||||
scope.removeAlert = jasmine.createSpy();
|
||||
$compile('<div uib-alert close="removeAlert()" dismiss-on-timeout="500">Default alert!</div>')(scope);
|
||||
scope.$digest();
|
||||
expect(element).toHaveClass('alert-block');
|
||||
expect(element).toHaveClass('alert-info');
|
||||
|
||||
$timeout.flush();
|
||||
expect(scope.removeAlert).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not close immediately with a dynamic dismiss-on-timeout', function() {
|
||||
scope.removeAlert = jasmine.createSpy();
|
||||
scope.dismissTime = 500;
|
||||
$compile('<div uib-alert close="removeAlert()" dismiss-on-timeout="{{dismissTime}}">Default alert!</div>')(scope);
|
||||
scope.$digest();
|
||||
|
||||
$timeout.flush(100);
|
||||
expect(scope.removeAlert).not.toHaveBeenCalled();
|
||||
|
||||
$timeout.flush(500);
|
||||
expect(scope.removeAlert).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
angular.module('ui.bootstrap.bindHtml', [])
|
||||
|
||||
.directive('bindHtmlUnsafe', function () {
|
||||
return function (scope, element, attr) {
|
||||
element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe);
|
||||
scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) {
|
||||
element.html(value || '');
|
||||
});
|
||||
};
|
||||
});
|
||||
+42
-22
@@ -1,49 +1,66 @@
|
||||
angular.module('ui.bootstrap.buttons', [])
|
||||
|
||||
.constant('buttonConfig', {
|
||||
.constant('uibButtonConfig', {
|
||||
activeClass: 'active',
|
||||
toggleEvent: 'click'
|
||||
})
|
||||
|
||||
.controller('ButtonsController', ['buttonConfig', function(buttonConfig) {
|
||||
.controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
|
||||
this.activeClass = buttonConfig.activeClass || 'active';
|
||||
this.toggleEvent = buttonConfig.toggleEvent || 'click';
|
||||
}])
|
||||
|
||||
.directive('btnRadio', function () {
|
||||
.directive('uibBtnRadio', ['$parse', function($parse) {
|
||||
return {
|
||||
require: ['btnRadio', 'ngModel'],
|
||||
controller: 'ButtonsController',
|
||||
link: function (scope, element, attrs, ctrls) {
|
||||
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'});
|
||||
|
||||
//model -> UI
|
||||
ngModelCtrl.$render = function () {
|
||||
element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
|
||||
ngModelCtrl.$render = function() {
|
||||
element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
|
||||
};
|
||||
|
||||
//ui->model
|
||||
element.bind(buttonsCtrl.toggleEvent, function () {
|
||||
element.on(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));
|
||||
scope.$apply(function() {
|
||||
ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
|
||||
ngModelCtrl.$render();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (attrs.uibUncheckable) {
|
||||
scope.$watch(uncheckableExpr, function(uncheckable) {
|
||||
attrs.$set('uncheckable', uncheckable ? '' : undefined);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
}])
|
||||
|
||||
.directive('btnCheckbox', function () {
|
||||
.directive('uibBtnCheckbox', function() {
|
||||
return {
|
||||
require: ['btnCheckbox', 'ngModel'],
|
||||
controller: 'ButtonsController',
|
||||
link: function (scope, element, attrs, ctrls) {
|
||||
require: ['uibBtnCheckbox', 'ngModel'],
|
||||
controller: 'UibButtonsController',
|
||||
controllerAs: 'button',
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
||||
|
||||
element.find('input').css({display: 'none'});
|
||||
|
||||
function getTrueValue() {
|
||||
return getCheckboxValue(attrs.btnCheckboxTrue, true);
|
||||
}
|
||||
@@ -52,19 +69,22 @@ angular.module('ui.bootstrap.buttons', [])
|
||||
return getCheckboxValue(attrs.btnCheckboxFalse, false);
|
||||
}
|
||||
|
||||
function getCheckboxValue(attributeValue, defaultValue) {
|
||||
var val = scope.$eval(attributeValue);
|
||||
return angular.isDefined(val) ? val : defaultValue;
|
||||
function getCheckboxValue(attribute, defaultValue) {
|
||||
return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
|
||||
}
|
||||
|
||||
//model -> UI
|
||||
ngModelCtrl.$render = function () {
|
||||
ngModelCtrl.$render = function() {
|
||||
element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
|
||||
};
|
||||
|
||||
//ui->model
|
||||
element.bind(buttonsCtrl.toggleEvent, function () {
|
||||
scope.$apply(function () {
|
||||
element.on(buttonsCtrl.toggleEvent, function() {
|
||||
if (attrs.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.$apply(function() {
|
||||
ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
|
||||
ngModelCtrl.$render();
|
||||
});
|
||||
|
||||
+18
-12
@@ -1,26 +1,32 @@
|
||||
<div ng-controller="ButtonsCtrl">
|
||||
<h4>Single toggle</h4>
|
||||
<pre>{{singleModel}}</pre>
|
||||
<button type="button" class="btn btn-primary" ng-model="singleModel" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
|
||||
<button type="button" class="btn btn-primary" ng-model="singleModel" uib-btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">
|
||||
Single Toggle
|
||||
</button>
|
||||
<h4>Checkbox</h4>
|
||||
<pre>{{checkModel}}</pre>
|
||||
<pre>Model: {{checkModel}}</pre>
|
||||
<pre>Results: {{checkResults}}</pre>
|
||||
<div class="btn-group">
|
||||
<label class="btn btn-primary" ng-model="checkModel.left" btn-checkbox>Left</label>
|
||||
<label class="btn btn-primary" ng-model="checkModel.middle" btn-checkbox>Middle</label>
|
||||
<label class="btn btn-primary" ng-model="checkModel.right" btn-checkbox>Right</label>
|
||||
<label class="btn btn-primary" ng-model="checkModel.left" uib-btn-checkbox>Left</label>
|
||||
<label class="btn btn-primary" ng-model="checkModel.middle" uib-btn-checkbox>Middle</label>
|
||||
<label class="btn btn-primary" ng-model="checkModel.right" uib-btn-checkbox>Right</label>
|
||||
</div>
|
||||
<h4>Radio & Uncheckable Radio</h4>
|
||||
<pre>{{radioModel || 'null'}}</pre>
|
||||
<div class="btn-group">
|
||||
<label class="btn btn-primary" ng-model="radioModel" btn-radio="'Left'">Left</label>
|
||||
<label class="btn btn-primary" ng-model="radioModel" btn-radio="'Middle'">Middle</label>
|
||||
<label class="btn btn-primary" ng-model="radioModel" btn-radio="'Right'">Right</label>
|
||||
<label class="btn btn-primary" ng-model="radioModel" uib-btn-radio="'Left'">Left</label>
|
||||
<label class="btn btn-primary" ng-model="radioModel" uib-btn-radio="'Middle'">Middle</label>
|
||||
<label class="btn btn-primary" ng-model="radioModel" uib-btn-radio="'Right'">Right</label>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<label class="btn btn-success" ng-model="radioModel" btn-radio="'Left'" uncheckable>Left</label>
|
||||
<label class="btn btn-success" ng-model="radioModel" btn-radio="'Middle'" uncheckable>Middle</label>
|
||||
<label class="btn btn-success" ng-model="radioModel" btn-radio="'Right'" uncheckable>Right</label>
|
||||
<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'" uib-uncheckable="uncheckable">Right</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-default" ng-click="uncheckable = !uncheckable">
|
||||
Toggle uncheckable
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,4 +8,15 @@ angular.module('ui.bootstrap.demo').controller('ButtonsCtrl', function ($scope)
|
||||
middle: true,
|
||||
right: false
|
||||
};
|
||||
|
||||
$scope.checkResults = [];
|
||||
|
||||
$scope.$watchCollection('checkModel', function () {
|
||||
$scope.checkResults = [];
|
||||
angular.forEach($scope.checkModel, function (value, key) {
|
||||
if (value) {
|
||||
$scope.checkResults.push(key);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1 +1,50 @@
|
||||
There are two directives that can make a group of buttons behave like a set of checkboxes, radio buttons, or a hybrid where radio buttons can be unchecked.
|
||||
With the buttons directive, we can make a group of buttons behave like a set of checkboxes (`uib-btn-checkbox`) or behave like a set of radio buttons (`uib-btn-radio`).
|
||||
|
||||
### uib-btn-checkbox settings
|
||||
|
||||
* `btn-checkbox-false`
|
||||
_(Default: `false`)_ -
|
||||
Sets the value for the unchecked status.
|
||||
|
||||
* `btn-checkbox-true`
|
||||
_(Default: `true`)_ -
|
||||
Sets the value for the checked 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.
|
||||
|
||||
* `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`
|
||||
<small class="badge">B</small> -
|
||||
Whether a radio button can be unchecked or not.
|
||||
|
||||
### Additional settings `uibButtonConfig`
|
||||
|
||||
* `activeClass`
|
||||
_(Default: `active`)_ -
|
||||
Class to apply to the checked buttons.
|
||||
|
||||
* `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;
|
||||
@@ -1,25 +1,35 @@
|
||||
describe('buttons', function () {
|
||||
describe('buttons', function() {
|
||||
|
||||
var $scope, $compile;
|
||||
|
||||
beforeEach(module('ui.bootstrap.buttons'));
|
||||
beforeEach(inject(function (_$rootScope_, _$compile_) {
|
||||
beforeEach(inject(function(_$rootScope_, _$compile_) {
|
||||
$scope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
}));
|
||||
|
||||
describe('checkbox', function () {
|
||||
|
||||
var compileButton = function (markup, scope) {
|
||||
describe('checkbox', function() {
|
||||
var compileButton = function(markup, scope) {
|
||||
var el = $compile(markup)(scope);
|
||||
scope.$digest();
|
||||
return el;
|
||||
};
|
||||
|
||||
it('should expose the controller to the view', inject(function($templateCache) {
|
||||
var btn = compileButton('<button ng-model="model" uib-btn-checkbox>{{button.text}}</button>', $scope);
|
||||
var ctrl = btn.controller('uibBtnCheckbox');
|
||||
expect(ctrl).toBeDefined();
|
||||
|
||||
ctrl.text = 'foo';
|
||||
$scope.$digest();
|
||||
|
||||
expect(btn.html()).toBe('foo');
|
||||
}));
|
||||
|
||||
//model -> UI
|
||||
it('should work correctly with default model values', function () {
|
||||
it('should work correctly with default model values', function() {
|
||||
$scope.model = false;
|
||||
var btn = compileButton('<button ng-model="model" btn-checkbox>click</button>', $scope);
|
||||
var btn = compileButton('<button ng-model="model" uib-btn-checkbox>click</button>', $scope);
|
||||
expect(btn).not.toHaveClass('active');
|
||||
|
||||
$scope.model = true;
|
||||
@@ -27,9 +37,9 @@ describe('buttons', function () {
|
||||
expect(btn).toHaveClass('active');
|
||||
});
|
||||
|
||||
it('should bind custom model values', function () {
|
||||
it('should bind custom model values', function() {
|
||||
$scope.model = 1;
|
||||
var btn = compileButton('<button ng-model="model" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">click</button>', $scope);
|
||||
var btn = compileButton('<button ng-model="model" uib-btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">click</button>', $scope);
|
||||
expect(btn).toHaveClass('active');
|
||||
|
||||
$scope.model = 0;
|
||||
@@ -38,9 +48,9 @@ describe('buttons', function () {
|
||||
});
|
||||
|
||||
//UI-> model
|
||||
it('should toggle default model values on click', function () {
|
||||
it('should toggle default model values on click', function() {
|
||||
$scope.model = false;
|
||||
var btn = compileButton('<button ng-model="model" btn-checkbox>click</button>', $scope);
|
||||
var btn = compileButton('<button ng-model="model" uib-btn-checkbox>click</button>', $scope);
|
||||
|
||||
btn.click();
|
||||
expect($scope.model).toEqual(true);
|
||||
@@ -51,9 +61,9 @@ describe('buttons', function () {
|
||||
expect(btn).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
it('should toggle custom model values on click', function () {
|
||||
it('should toggle custom model values on click', function() {
|
||||
$scope.model = 0;
|
||||
var btn = compileButton('<button ng-model="model" btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">click</button>', $scope);
|
||||
var btn = compileButton('<button ng-model="model" uib-btn-checkbox btn-checkbox-true="1" btn-checkbox-false="0">click</button>', $scope);
|
||||
|
||||
btn.click();
|
||||
expect($scope.model).toEqual(1);
|
||||
@@ -64,11 +74,11 @@ describe('buttons', function () {
|
||||
expect(btn).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
it('should monitor true / false value changes - issue 666', function () {
|
||||
it('should monitor true / false value changes - issue 666', function() {
|
||||
|
||||
$scope.model = 1;
|
||||
$scope.trueVal = 1;
|
||||
var btn = compileButton('<button ng-model="model" btn-checkbox btn-checkbox-true="trueVal">click</button>', $scope);
|
||||
var btn = compileButton('<button ng-model="model" uib-btn-checkbox btn-checkbox-true="trueVal">click</button>', $scope);
|
||||
|
||||
expect(btn).toHaveClass('active');
|
||||
expect($scope.model).toEqual(1);
|
||||
@@ -80,19 +90,98 @@ describe('buttons', function () {
|
||||
expect(btn).toHaveClass('active');
|
||||
expect($scope.model).toEqual(2);
|
||||
});
|
||||
|
||||
it('should not toggle when disabled - issue 4013', function() {
|
||||
$scope.model = 1;
|
||||
$scope.falseVal = 0;
|
||||
var btn = compileButton('<button disabled ng-model="model" uib-btn-checkbox btn-checkbox-true="falseVal">click</button>', $scope);
|
||||
|
||||
expect(btn).not.toHaveClass('active');
|
||||
expect($scope.model).toEqual(1);
|
||||
|
||||
btn.click();
|
||||
|
||||
expect(btn).not.toHaveClass('active');
|
||||
|
||||
$scope.$digest();
|
||||
|
||||
expect(btn).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
describe('setting buttonConfig', function() {
|
||||
var uibButtonConfig, originalActiveClass, originalToggleEvent;
|
||||
|
||||
beforeEach(inject(function(_uibButtonConfig_) {
|
||||
uibButtonConfig = _uibButtonConfig_;
|
||||
originalActiveClass = uibButtonConfig.activeClass;
|
||||
originalToggleEvent = uibButtonConfig.toggleEvent;
|
||||
uibButtonConfig.activeClass = false;
|
||||
uibButtonConfig.toggleEvent = false;
|
||||
}));
|
||||
|
||||
afterEach(function() {
|
||||
// return it to the original value
|
||||
uibButtonConfig.activeClass = originalActiveClass;
|
||||
uibButtonConfig.toggleEvent = originalToggleEvent;
|
||||
});
|
||||
|
||||
it('should use default config when buttonConfig.activeClass and buttonConfig.toggleEvent is false', function() {
|
||||
$scope.model = false;
|
||||
var btn = compileButton('<button ng-model="model" uib-btn-checkbox>click</button>', $scope);
|
||||
expect(btn).not.toHaveClass('active');
|
||||
|
||||
$scope.model = true;
|
||||
$scope.$digest();
|
||||
expect(btn).toHaveClass('active');
|
||||
});
|
||||
|
||||
it('should be able to use a different active class', function() {
|
||||
uibButtonConfig.activeClass = 'foo';
|
||||
$scope.model = false;
|
||||
var btn = compileButton('<button ng-model="model" uib-btn-checkbox>click</button>', $scope);
|
||||
expect(btn).not.toHaveClass('foo');
|
||||
|
||||
$scope.model = true;
|
||||
$scope.$digest();
|
||||
expect(btn).toHaveClass('foo');
|
||||
});
|
||||
|
||||
it('should be able to use a different toggle event', function() {
|
||||
uibButtonConfig.toggleEvent = 'mouseenter';
|
||||
$scope.model = false;
|
||||
var btn = compileButton('<button ng-model="model" uib-btn-checkbox>click</button>', $scope);
|
||||
expect(btn).not.toHaveClass('active');
|
||||
|
||||
btn.trigger('mouseenter');
|
||||
|
||||
$scope.$digest();
|
||||
expect(btn).toHaveClass('active');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('radio', function () {
|
||||
|
||||
var compileButtons = function (markup, scope) {
|
||||
describe('radio', function() {
|
||||
var compileButtons = function(markup, scope) {
|
||||
var el = $compile('<div>'+markup+'</div>')(scope);
|
||||
scope.$digest();
|
||||
return el.find('button');
|
||||
};
|
||||
|
||||
it('should expose the controller to the view', inject(function($templateCache) {
|
||||
var btn = compileButtons('<button ng-model="model" uib-btn-radio="1">{{buttons.text}}</button>', $scope);
|
||||
var ctrl = btn.controller('uibBtnRadio');
|
||||
expect(ctrl).toBeDefined();
|
||||
|
||||
ctrl.text = 'foo';
|
||||
$scope.$digest();
|
||||
|
||||
expect(btn.html()).toBe('foo');
|
||||
}));
|
||||
|
||||
//model -> UI
|
||||
it('should work correctly set active class based on model', function () {
|
||||
var btns = compileButtons('<button ng-model="model" btn-radio="1">click1</button><button ng-model="model" btn-radio="2">click2</button>', $scope);
|
||||
it('should set active class based on model', function() {
|
||||
var btns = compileButtons('<button ng-model="model" uib-btn-radio="1">click1</button><button ng-model="model" uib-btn-radio="2">click2</button>', $scope);
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
|
||||
@@ -103,8 +192,8 @@ describe('buttons', function () {
|
||||
});
|
||||
|
||||
//UI->model
|
||||
it('should work correctly set active class based on model', function () {
|
||||
var btns = compileButtons('<button ng-model="model" btn-radio="1">click1</button><button ng-model="model" btn-radio="2">click2</button>', $scope);
|
||||
it('should set active class via click', function() {
|
||||
var btns = compileButtons('<button ng-model="model" uib-btn-radio="1">click1</button><button ng-model="model" uib-btn-radio="2">click2</button>', $scope);
|
||||
expect($scope.model).toBeUndefined();
|
||||
|
||||
btns.eq(0).click();
|
||||
@@ -118,10 +207,10 @@ describe('buttons', function () {
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
it('should watch btn-radio values and update state accordingly', function () {
|
||||
it('should watch uib-btn-radio values and update state accordingly', function() {
|
||||
$scope.values = ['value1', 'value2'];
|
||||
|
||||
var btns = compileButtons('<button ng-model="model" btn-radio="values[0]">click1</button><button ng-model="model" btn-radio="values[1]">click2</button>', $scope);
|
||||
var btns = compileButtons('<button ng-model="model" uib-btn-radio="values[0]">click1</button><button ng-model="model" uib-btn-radio="values[1]">click2</button>', $scope);
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
|
||||
@@ -137,10 +226,59 @@ describe('buttons', function () {
|
||||
expect(btns.eq(1)).toHaveClass('active');
|
||||
});
|
||||
|
||||
describe('uncheckable', function () {
|
||||
it('should do nothing when clicking an active radio', function() {
|
||||
$scope.model = 1;
|
||||
var btns = compileButtons('<button ng-model="model" uib-btn-radio="1">click1</button><button ng-model="model" uib-btn-radio="2">click2</button>', $scope);
|
||||
expect(btns.eq(0)).toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
|
||||
btns.eq(0).click();
|
||||
$scope.$digest();
|
||||
expect(btns.eq(0)).toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
it('should not toggle when disabled - issue 4013', function() {
|
||||
$scope.model = 1;
|
||||
var btns = compileButtons('<button ng-model="model" uib-btn-radio="1">click1</button><button disabled ng-model="model" uib-btn-radio="2">click2</button>', $scope);
|
||||
|
||||
expect(btns.eq(0)).toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
|
||||
btns.eq(1).click();
|
||||
|
||||
expect(btns.eq(0)).toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
|
||||
$scope.$digest();
|
||||
|
||||
expect(btns.eq(0)).toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
it('should handle string values in uib-btn-radio value', function() {
|
||||
$scope.model = 'Two';
|
||||
var btns = compileButtons('<button ng-model="model" uib-btn-radio="\'One\'">click1</button><button ng-model="model" uib-btn-radio="\'Two\'">click2</button>', $scope);
|
||||
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
expect(btns.eq(1)).toHaveClass('active');
|
||||
|
||||
btns.eq(0).click();
|
||||
expect(btns.eq(0)).toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
expect($scope.model).toEqual('One');
|
||||
|
||||
$scope.$digest();
|
||||
|
||||
expect(btns.eq(0)).toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
expect($scope.model).toEqual('One');
|
||||
});
|
||||
|
||||
describe('uncheckable', function() {
|
||||
//model -> UI
|
||||
it('should set active class based on model', function () {
|
||||
var btns = compileButtons('<button ng-model="model" btn-radio="1" uncheckable>click1</button><button ng-model="model" btn-radio="2" uncheckable>click2</button>', $scope);
|
||||
it('should set active class based on model', function() {
|
||||
var btns = compileButtons('<button ng-model="model" uib-btn-radio="1" uncheckable>click1</button><button ng-model="model" uib-btn-radio="2" uncheckable>click2</button>', $scope);
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
|
||||
@@ -151,8 +289,8 @@ describe('buttons', function () {
|
||||
});
|
||||
|
||||
//UI->model
|
||||
it('should unset active class based on model', function () {
|
||||
var btns = compileButtons('<button ng-model="model" btn-radio="1" uncheckable>click1</button><button ng-model="model" btn-radio="2" uncheckable>click2</button>', $scope);
|
||||
it('should unset active class via click', function() {
|
||||
var btns = compileButtons('<button ng-model="model" uib-btn-radio="1" uncheckable>click1</button><button ng-model="model" uib-btn-radio="2" uncheckable>click2</button>', $scope);
|
||||
expect($scope.model).toBeUndefined();
|
||||
|
||||
btns.eq(0).click();
|
||||
@@ -161,15 +299,15 @@ describe('buttons', function () {
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
|
||||
btns.eq(0).click();
|
||||
expect($scope.model).toEqual(undefined);
|
||||
expect($scope.model).toBeNull();
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
it('should watch btn-radio values and update state', function () {
|
||||
it('should watch uib-btn-radio values and update state', function() {
|
||||
$scope.values = ['value1', 'value2'];
|
||||
|
||||
var btns = compileButtons('<button ng-model="model" btn-radio="values[0]" uncheckable>click1</button><button ng-model="model" btn-radio="values[1]" uncheckable>click2</button>', $scope);
|
||||
var btns = compileButtons('<button ng-model="model" uib-btn-radio="values[0]" uncheckable>click1</button><button ng-model="model" uib-btn-radio="values[1]" uncheckable>click2</button>', $scope);
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
|
||||
@@ -184,5 +322,45 @@ describe('buttons', function () {
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
expect($scope.model).toBeUndefined();
|
||||
|
||||
btns.eq(0).click();
|
||||
expect($scope.model).toEqual(1);
|
||||
|
||||
btns.eq(0).click();
|
||||
expect($scope.model).toEqual(1);
|
||||
|
||||
btns.eq(1).click();
|
||||
expect($scope.model).toEqual(2);
|
||||
|
||||
btns.eq(1).click();
|
||||
expect($scope.model).toEqual(2);
|
||||
|
||||
$scope.uncheckable = true;
|
||||
$scope.$digest();
|
||||
expect(btns.eq(0).attr('uncheckable')).toBeUndefined();
|
||||
expect(btns.eq(1).attr('uncheckable')).toBeDefined();
|
||||
|
||||
btns.eq(0).click();
|
||||
expect($scope.model).toEqual(1);
|
||||
|
||||
btns.eq(0).click();
|
||||
expect($scope.model).toEqual(1);
|
||||
|
||||
btns.eq(1).click();
|
||||
expect($scope.model).toEqual(2);
|
||||
|
||||
btns.eq(1).click();
|
||||
expect($scope.model).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.ng-animate.item:not(.left):not(.right) {
|
||||
-webkit-transition: 0s ease-in-out left;
|
||||
transition: 0s ease-in-out left
|
||||
}
|
||||
+298
-235
@@ -1,138 +1,129 @@
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name ui.bootstrap.carousel
|
||||
*
|
||||
* @description
|
||||
* AngularJS version of an image carousel.
|
||||
*
|
||||
*/
|
||||
angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
|
||||
.controller('CarouselController', ['$scope', '$timeout', '$transition', function ($scope, $timeout, $transition) {
|
||||
angular.module('ui.bootstrap.carousel', [])
|
||||
|
||||
.controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
|
||||
var self = this,
|
||||
slides = self.slides = $scope.slides = [],
|
||||
currentIndex = -1,
|
||||
currentTimeout, isPlaying;
|
||||
self.currentSlide = null;
|
||||
SLIDE_DIRECTION = 'uib-slideDirection',
|
||||
currentIndex = $scope.active,
|
||||
currentInterval, isPlaying;
|
||||
|
||||
var destroyed = false;
|
||||
$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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.getCurrentIndex = function() {
|
||||
for (var i = 0; i < slides.length; i++) {
|
||||
if (slides[i].slide.index === currentIndex) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.next = $scope.next = function() {
|
||||
var newIndex = (self.getCurrentIndex() + 1) % slides.length;
|
||||
|
||||
if (newIndex === 0 && $scope.noWrap()) {
|
||||
$scope.pause();
|
||||
return;
|
||||
}
|
||||
|
||||
return self.select(slides[newIndex], 'next');
|
||||
};
|
||||
|
||||
self.prev = $scope.prev = function() {
|
||||
var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
|
||||
|
||||
if ($scope.noWrap() && newIndex === slides.length - 1) {
|
||||
$scope.pause();
|
||||
return;
|
||||
}
|
||||
|
||||
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 = slides.indexOf(nextSlide);
|
||||
var nextIndex = findSlideIndex(nextSlide.slide);
|
||||
//Decide direction if it's not given
|
||||
if (direction === undefined) {
|
||||
direction = nextIndex > currentIndex ? 'next' : 'prev';
|
||||
direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
|
||||
}
|
||||
if (nextSlide && nextSlide !== self.currentSlide) {
|
||||
if ($scope.$currentTransition) {
|
||||
$scope.$currentTransition.cancel();
|
||||
//Timeout so ng-class in template has time to fix classes for finished slide
|
||||
$timeout(goNext);
|
||||
} else {
|
||||
goNext();
|
||||
}
|
||||
}
|
||||
function goNext() {
|
||||
// Scope has been destroyed, stop here.
|
||||
if (destroyed) { return; }
|
||||
//If we have a slide to transition from and we have a transition type and we're allowed, go
|
||||
if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) {
|
||||
//We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime
|
||||
nextSlide.$element.addClass(direction);
|
||||
var reflow = nextSlide.$element[0].offsetWidth; //force reflow
|
||||
|
||||
//Set all other slides to stop doing their stuff for the new transition
|
||||
angular.forEach(slides, function(slide) {
|
||||
angular.extend(slide, {direction: '', entering: false, leaving: false, active: false});
|
||||
});
|
||||
angular.extend(nextSlide, {direction: direction, active: true, entering: true});
|
||||
angular.extend(self.currentSlide||{}, {direction: direction, leaving: true});
|
||||
|
||||
$scope.$currentTransition = $transition(nextSlide.$element, {});
|
||||
//We have to create new pointers inside a closure since next & current will change
|
||||
(function(next,current) {
|
||||
$scope.$currentTransition.then(
|
||||
function(){ transitionDone(next, current); },
|
||||
function(){ transitionDone(next, current); }
|
||||
);
|
||||
}(nextSlide, self.currentSlide));
|
||||
} else {
|
||||
transitionDone(nextSlide, self.currentSlide);
|
||||
}
|
||||
self.currentSlide = nextSlide;
|
||||
currentIndex = nextIndex;
|
||||
//every time you change slides, reset the timer
|
||||
restartTimer();
|
||||
}
|
||||
function transitionDone(next, current) {
|
||||
angular.extend(next, {direction: '', active: true, leaving: false, entering: false});
|
||||
angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false});
|
||||
$scope.$currentTransition = null;
|
||||
//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);
|
||||
}
|
||||
};
|
||||
$scope.$on('$destroy', function () {
|
||||
destroyed = true;
|
||||
});
|
||||
|
||||
/* Allow outside people to call indexOf on slides array */
|
||||
self.indexOfSlide = function(slide) {
|
||||
return slides.indexOf(slide);
|
||||
};
|
||||
|
||||
$scope.next = function() {
|
||||
var newIndex = (currentIndex + 1) % slides.length;
|
||||
|
||||
//Prevent this user-triggered transition from occurring if there is already one in progress
|
||||
if (!$scope.$currentTransition) {
|
||||
return self.select(slides[newIndex], 'next');
|
||||
}
|
||||
};
|
||||
|
||||
$scope.prev = function() {
|
||||
var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1;
|
||||
|
||||
//Prevent this user-triggered transition from occurring if there is already one in progress
|
||||
if (!$scope.$currentTransition) {
|
||||
return self.select(slides[newIndex], 'prev');
|
||||
}
|
||||
$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.$on('$destroy', resetTimer);
|
||||
|
||||
function restartTimer() {
|
||||
resetTimer();
|
||||
var interval = +$scope.interval;
|
||||
if (!isNaN(interval) && interval>=0) {
|
||||
currentTimeout = $timeout(timerFn, interval);
|
||||
}
|
||||
}
|
||||
|
||||
function resetTimer() {
|
||||
if (currentTimeout) {
|
||||
$timeout.cancel(currentTimeout);
|
||||
currentTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
function timerFn() {
|
||||
if (isPlaying) {
|
||||
$scope.next();
|
||||
restartTimer();
|
||||
} else {
|
||||
$scope.pause();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.play = function() {
|
||||
if (!isPlaying) {
|
||||
isPlaying = true;
|
||||
restartTimer();
|
||||
}
|
||||
$scope.isPrevDisabled = function() {
|
||||
return $scope.active === 0 && $scope.noWrap();
|
||||
};
|
||||
|
||||
$scope.isNextDisabled = function() {
|
||||
return $scope.active === slides.length - 1 && $scope.noWrap();
|
||||
};
|
||||
|
||||
$scope.pause = function() {
|
||||
if (!$scope.noPause) {
|
||||
isPlaying = false;
|
||||
@@ -140,143 +131,166 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
$scope.play = function() {
|
||||
if (!isPlaying) {
|
||||
isPlaying = true;
|
||||
restartTimer();
|
||||
}
|
||||
};
|
||||
|
||||
self.removeSlide = function(slide) {
|
||||
//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 {
|
||||
$element.on('mouseenter', $scope.pause);
|
||||
$element.on('mouseleave', $scope.play);
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
destroyed = true;
|
||||
resetTimer();
|
||||
});
|
||||
|
||||
$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;
|
||||
}
|
||||
} else if (currentIndex > index) {
|
||||
currentIndex--;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetTimer() {
|
||||
if (currentInterval) {
|
||||
$interval.cancel(currentInterval);
|
||||
currentInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
$scope.next();
|
||||
} else {
|
||||
$scope.pause();
|
||||
}
|
||||
}
|
||||
}])
|
||||
|
||||
/**
|
||||
* @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">
|
||||
<carousel>
|
||||
<slide>
|
||||
<img src="http://placekitten.com/150/150" style="margin:auto;">
|
||||
<div class="carousel-caption">
|
||||
<p>Beautiful!</p>
|
||||
</div>
|
||||
</slide>
|
||||
<slide>
|
||||
<img src="http://placekitten.com/100/150" style="margin:auto;">
|
||||
<div class="carousel-caption">
|
||||
<p>D'aww!</p>
|
||||
</div>
|
||||
</slide>
|
||||
</carousel>
|
||||
</file>
|
||||
<file name="demo.css">
|
||||
.carousel-indicators {
|
||||
top: auto;
|
||||
bottom: 15px;
|
||||
}
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
.directive('carousel', [function() {
|
||||
.directive('uibCarousel', function() {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
transclude: true,
|
||||
replace: true,
|
||||
controller: 'CarouselController',
|
||||
require: 'carousel',
|
||||
templateUrl: 'template/carousel/carousel.html',
|
||||
controller: 'UibCarouselController',
|
||||
controllerAs: 'carousel',
|
||||
restrict: 'A',
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/carousel/carousel.html';
|
||||
},
|
||||
scope: {
|
||||
active: '=',
|
||||
interval: '=',
|
||||
noTransition: '=',
|
||||
noPause: '='
|
||||
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.
|
||||
*
|
||||
* @example
|
||||
<example module="ui.bootstrap">
|
||||
<file name="index.html">
|
||||
<div ng-controller="CarouselDemoCtrl">
|
||||
<carousel>
|
||||
<slide ng-repeat="slide in slides" active="slide.active">
|
||||
<img ng-src="{{slide.image}}" style="margin:auto;">
|
||||
<div class="carousel-caption">
|
||||
<h4>Slide {{$index}}</h4>
|
||||
<p>{{slide.text}}</p>
|
||||
</div>
|
||||
</slide>
|
||||
</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('slide', function() {
|
||||
.directive('uibSlide', ['$animate', function($animate) {
|
||||
return {
|
||||
require: '^carousel',
|
||||
restrict: 'EA',
|
||||
require: '^uibCarousel',
|
||||
restrict: 'A',
|
||||
transclude: true,
|
||||
replace: true,
|
||||
templateUrl: 'template/carousel/slide.html',
|
||||
templateUrl: function(element, attrs) {
|
||||
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() {
|
||||
@@ -284,10 +298,59 @@ function CarouselDemoCtrl($scope) {
|
||||
});
|
||||
|
||||
scope.$watch('active', function(active) {
|
||||
if (active) {
|
||||
carouselCtrl.select(scope);
|
||||
}
|
||||
$animate[active ? 'addClass' : 'removeClass'](element, 'active');
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
}])
|
||||
|
||||
.animation('.item', ['$animateCss',
|
||||
function($animateCss) {
|
||||
var SLIDE_DIRECTION = 'uib-slideDirection';
|
||||
|
||||
function removeClass(element, className, callback) {
|
||||
element.removeClass(className);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
beforeAddClass: function(element, className, done) {
|
||||
if (className === 'active') {
|
||||
var stopped = false;
|
||||
var direction = element.data(SLIDE_DIRECTION);
|
||||
var directionClass = direction === 'next' ? 'left' : 'right';
|
||||
var removeClassFn = removeClass.bind(this, element,
|
||||
directionClass + ' ' + direction, done);
|
||||
element.addClass(direction);
|
||||
|
||||
$animateCss(element, {addClass: directionClass})
|
||||
.start()
|
||||
.done(removeClassFn);
|
||||
|
||||
return function() {
|
||||
stopped = true;
|
||||
};
|
||||
}
|
||||
done();
|
||||
},
|
||||
beforeRemoveClass: function (element, className, done) {
|
||||
if (className === 'active') {
|
||||
var stopped = false;
|
||||
var direction = element.data(SLIDE_DIRECTION);
|
||||
var directionClass = direction === 'next' ? 'left' : 'right';
|
||||
var removeClassFn = removeClass.bind(this, element, directionClass, done);
|
||||
|
||||
$animateCss(element, {addClass: directionClass})
|
||||
.start()
|
||||
.done(removeClassFn);
|
||||
|
||||
return function() {
|
||||
stopped = true;
|
||||
};
|
||||
}
|
||||
done();
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -2,4 +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 `<carousel>` element with `<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.
|
||||
|
||||
### uib-carousel settings
|
||||
|
||||
* `active`
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `Index of first slide`)_ -
|
||||
Index of current active slide.
|
||||
|
||||
* `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,22 +1,29 @@
|
||||
<div ng-controller="CarouselDemoCtrl">
|
||||
<div style="height: 305px">
|
||||
<carousel interval="myInterval">
|
||||
<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>
|
||||
</slide>
|
||||
</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">
|
||||
Disable Slide Looping
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
Interval, in milliseconds: <input type="number" class="form-control" ng-model="myInterval">
|
||||
<br />Enter a negative number to stop the interval.
|
||||
<br />Enter a negative number or 0 to stop the interval.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +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;
|
||||
var newWidth = 600 + slides.length + 1;
|
||||
slides.push({
|
||||
image: 'http://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');
|
||||
+390
-133
@@ -1,25 +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, $timeout;
|
||||
beforeEach(inject(function(_$rootScope_, _$compile_, _$controller_, _$timeout_) {
|
||||
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() {
|
||||
@@ -27,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(
|
||||
'<carousel interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<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}}' +
|
||||
'</slide>' +
|
||||
'</carousel>'
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
scope.interval = 5000;
|
||||
scope.nopause = undefined;
|
||||
@@ -44,19 +36,41 @@ 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);
|
||||
for (var i = 0; i < scope.slides.length; i++) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it('should set the selected slide to active = true', function() {
|
||||
expect(scope.slides[0].content).toBe('one');
|
||||
it('should allow overriding of the carousel template', function() {
|
||||
$templateCache.put('foo/bar.html', '<div>foo</div>');
|
||||
|
||||
elm = $compile('<div uib-carousel template-url="foo/bar.html"></div>')(scope);
|
||||
$rootScope.$digest();
|
||||
|
||||
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(
|
||||
'<div uib-carousel interval="interval" no-transition="true" no-pause="nopause">' +
|
||||
'<div uib-slide template-url="foo/bar.html"></div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
$rootScope.$digest();
|
||||
|
||||
var slide = elm.find('.slide');
|
||||
expect(slide.html()).toBe('bar');
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -73,16 +87,60 @@ describe('carousel', function() {
|
||||
expect(indicators.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should stop cycling slides forward when noWrap is truthy', function () {
|
||||
elm = $compile(
|
||||
'<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();
|
||||
|
||||
var $scope = elm.isolateScope();
|
||||
spyOn($scope, 'pause');
|
||||
|
||||
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(
|
||||
'<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();
|
||||
|
||||
var $scope = elm.isolateScope();
|
||||
spyOn($scope, 'pause');
|
||||
|
||||
testSlideActive(0);
|
||||
$scope.prev();
|
||||
testSlideActive(0);
|
||||
expect($scope.pause).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should hide navigation when only one slide', function () {
|
||||
scope.slides=[{active:false,content:'one'}];
|
||||
scope.slides = [{active:false,content:'one'}];
|
||||
scope.$apply();
|
||||
elm = $compile(
|
||||
'<carousel interval="interval" no-transition="true">' +
|
||||
'<slide ng-repeat="slide in slides" active="slide.active">' +
|
||||
'{{slide.content}}' +
|
||||
'</slide>' +
|
||||
'</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);
|
||||
|
||||
@@ -93,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);
|
||||
@@ -126,43 +216,34 @@ 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();
|
||||
testSlideActive(1);
|
||||
});
|
||||
|
||||
it('shouldnt go forward if interval is NaN or negative', function() {
|
||||
it('shouldnt go forward if interval is NaN or negative or has no slides', function() {
|
||||
testSlideActive(0);
|
||||
var previousInterval = scope.interval;
|
||||
scope.$apply('interval = -1');
|
||||
//no timeout to flush, interval watch doesn't make a new one when interval is invalid
|
||||
$interval.flush(previousInterval);
|
||||
testSlideActive(0);
|
||||
scope.$apply('interval = 1000');
|
||||
$timeout.flush();
|
||||
$interval.flush(1000);
|
||||
testSlideActive(1);
|
||||
scope.$apply('interval = false');
|
||||
$interval.flush(1000);
|
||||
testSlideActive(1);
|
||||
scope.$apply('interval = 1000');
|
||||
$timeout.flush();
|
||||
$interval.flush(1000);
|
||||
testSlideActive(2);
|
||||
scope.$apply('slides = []');
|
||||
$interval.flush(1000);
|
||||
testSlideActive(2);
|
||||
});
|
||||
|
||||
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');
|
||||
@@ -182,23 +263,24 @@ describe('carousel', function() {
|
||||
|
||||
it('should be playing by default and cycle through slides', function() {
|
||||
testSlideActive(0);
|
||||
$timeout.flush();
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(1);
|
||||
$timeout.flush();
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(2);
|
||||
$timeout.flush();
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(0);
|
||||
});
|
||||
|
||||
it('should pause and play on mouseover', function() {
|
||||
testSlideActive(0);
|
||||
$timeout.flush();
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(1);
|
||||
elm.trigger('mouseenter');
|
||||
expect($timeout.flush).toThrow();//pause should cancel current timeout
|
||||
testSlideActive(1);
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(1);
|
||||
elm.trigger('mouseleave');
|
||||
$timeout.flush();
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(2);
|
||||
});
|
||||
|
||||
@@ -206,30 +288,36 @@ describe('carousel', function() {
|
||||
scope.$apply('nopause = true');
|
||||
testSlideActive(0);
|
||||
elm.trigger('mouseenter');
|
||||
$timeout.flush();
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(1);
|
||||
elm.trigger('mouseleave');
|
||||
$timeout.flush();
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(2);
|
||||
});
|
||||
|
||||
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);
|
||||
$timeout.flush();
|
||||
$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');
|
||||
@@ -251,89 +339,258 @@ describe('carousel', function() {
|
||||
testSlideActive(1);
|
||||
});
|
||||
|
||||
it('issue 1414 - should not continue running timers after scope is destroyed', function() {
|
||||
it('should buffer the slides if transition is clicked and only transition to the last requested', function() {
|
||||
var carouselScope = elm.children().scope();
|
||||
|
||||
testSlideActive(0);
|
||||
$timeout.flush();
|
||||
carouselScope.$currentTransition = null;
|
||||
carouselScope.select(carouselScope.slides[1]);
|
||||
$animate.flush();
|
||||
|
||||
testSlideActive(1);
|
||||
$timeout.flush();
|
||||
|
||||
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);
|
||||
$timeout.flush();
|
||||
|
||||
$interval.flush(scope.interval);
|
||||
$animate.flush();
|
||||
|
||||
testSlideActive(0);
|
||||
scope.$destroy();
|
||||
expect($timeout.flush).toThrow('No deferred tasks to be flushed');
|
||||
});
|
||||
|
||||
it('issue 1414 - should not continue running timers after scope is destroyed', function() {
|
||||
testSlideActive(0);
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(1);
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(2);
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(0);
|
||||
spyOn($interval, 'cancel').and.callThrough();
|
||||
scope.$destroy();
|
||||
expect($interval.cancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
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', index: 1},
|
||||
{content:'new2', index: 2},
|
||||
{content:'new3', index: 3}
|
||||
];
|
||||
scope.$apply();
|
||||
|
||||
testSlideActive(0);
|
||||
carouselScope.$currentTransition = true;
|
||||
|
||||
scope.slides = [];
|
||||
scope.$apply();
|
||||
|
||||
expect(carouselScope.$currentTransition).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
ctrl = $controller('CarouselController', {$scope: scope, $element: null});
|
||||
for(var i = 0;i < slides.length;i++){
|
||||
scope.noWrap = angular.noop;
|
||||
ctrl = $controller('UibCarouselController', {$scope: scope, $element: angular.element('<div></div>')});
|
||||
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').andCallThrough();
|
||||
scope.interval = 2000;
|
||||
scope.$digest();
|
||||
|
||||
$timeout.flush();
|
||||
expect(scope.next.calls.length).toBe(1);
|
||||
|
||||
scope.$destroy();
|
||||
|
||||
$timeout.flush(scope.interval);
|
||||
expect(scope.next.calls.length).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('uib/template/carousel/carousel.html', '<div>{{carousel.text}}</div>');
|
||||
|
||||
var scope = $rootScope.$new();
|
||||
var elm = $compile('<div uib-carousel interval="bar" no-transition="false" no-pause="true"></div>')(scope);
|
||||
$rootScope.$digest();
|
||||
|
||||
var ctrl = elm.controller('uibCarousel');
|
||||
|
||||
expect(ctrl).toBeDefined();
|
||||
|
||||
ctrl.text = 'foo';
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elm.html()).toBe('<div class="ng-binding">foo</div>');
|
||||
}));
|
||||
});
|
||||
|
||||
it('should expose a custom model in the carousel slide', function() {
|
||||
var scope = $rootScope.$new();
|
||||
scope.slides = [
|
||||
{active:false,content:'one'},
|
||||
{active:false,content:'two'},
|
||||
{active:false,content:'three'}
|
||||
];
|
||||
var elm = $compile(
|
||||
'<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}}' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
)(scope);
|
||||
$rootScope.$digest();
|
||||
|
||||
var ctrl = elm.controller('uibCarousel');
|
||||
|
||||
expect(angular.equals(ctrl.slides.map(function(slide) {
|
||||
return slide.slide.actual;
|
||||
}), scope.slides)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
+102
-44
@@ -1,69 +1,127 @@
|
||||
angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition'])
|
||||
|
||||
.directive('collapse', ['$transition', function ($transition) {
|
||||
angular.module('ui.bootstrap.collapse', [])
|
||||
|
||||
.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) {
|
||||
link: function(scope, element, attrs) {
|
||||
var expandingExpr = $parse(attrs.expanding),
|
||||
expandedExpr = $parse(attrs.expanded),
|
||||
collapsingExpr = $parse(attrs.collapsing),
|
||||
collapsedExpr = $parse(attrs.collapsed),
|
||||
horizontal = false,
|
||||
css = {},
|
||||
cssTo = {};
|
||||
|
||||
var initialAnimSkip = true;
|
||||
var currentTransition;
|
||||
init();
|
||||
|
||||
function doTransition(change) {
|
||||
var newTransition = $transition(element, change);
|
||||
if (currentTransition) {
|
||||
currentTransition.cancel();
|
||||
function init() {
|
||||
horizontal = !!('horizontal' in attrs);
|
||||
if (horizontal) {
|
||||
css = {
|
||||
width: ''
|
||||
};
|
||||
cssTo = {width: '0'};
|
||||
} else {
|
||||
css = {
|
||||
height: ''
|
||||
};
|
||||
cssTo = {height: '0'};
|
||||
}
|
||||
currentTransition = newTransition;
|
||||
newTransition.then(newTransitionDone, newTransitionDone);
|
||||
return newTransition;
|
||||
|
||||
function newTransitionDone() {
|
||||
// Make sure it's this transition, otherwise, leave it alone.
|
||||
if (currentTransition === newTransition) {
|
||||
currentTransition = undefined;
|
||||
}
|
||||
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 (initialAnimSkip) {
|
||||
initialAnimSkip = false;
|
||||
expandDone();
|
||||
} else {
|
||||
element.removeClass('collapse').addClass('collapsing');
|
||||
doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone);
|
||||
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');
|
||||
element.addClass('collapse in');
|
||||
element.css({height: 'auto'});
|
||||
element.removeClass('collapsing')
|
||||
.addClass('collapse')
|
||||
.css(css);
|
||||
expandedExpr(scope);
|
||||
}
|
||||
|
||||
function collapse() {
|
||||
if (initialAnimSkip) {
|
||||
initialAnimSkip = false;
|
||||
collapseDone();
|
||||
element.css({height: 0});
|
||||
} else {
|
||||
// CSS transitions don't work with height: auto, so we have to manually change the height to a specific value
|
||||
element.css({ height: element[0].scrollHeight + 'px' });
|
||||
//trigger reflow so a browser realizes that height was updated from auto to a specific value
|
||||
var x = element[0].offsetWidth;
|
||||
|
||||
element.removeClass('collapse in').addClass('collapsing');
|
||||
|
||||
doTransition({ height: 0 }).then(collapseDone);
|
||||
if (!element.hasClass('collapse') && !element.hasClass('in')) {
|
||||
return collapseDone();
|
||||
}
|
||||
|
||||
$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: cssTo
|
||||
}).start()['finally'](collapseDone);
|
||||
} else {
|
||||
$animate.removeClass(element, 'in', {
|
||||
to: cssTo
|
||||
}).then(collapseDone);
|
||||
}
|
||||
}, angular.noop);
|
||||
}
|
||||
|
||||
function collapseDone() {
|
||||
element.removeClass('collapsing');
|
||||
element.addClass('collapse');
|
||||
element.css(cssTo); // Required so that collapse works when animation is disabled
|
||||
element.removeClass('collapsing')
|
||||
.addClass('collapse');
|
||||
collapsedExpr(scope);
|
||||
}
|
||||
|
||||
scope.$watch(attrs.collapse, function (shouldCollapse) {
|
||||
scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
|
||||
if (shouldCollapse) {
|
||||
collapse();
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,40 @@
|
||||
<style>
|
||||
.horizontal-collapse {
|
||||
height: 70px;
|
||||
}
|
||||
.navbar-collapse.in {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
</style>
|
||||
<div ng-controller="CollapseDemoCtrl">
|
||||
<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>
|
||||
<div collapse="isCollapsed">
|
||||
<div class="well well-lg">Some content</div>
|
||||
<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>
|
||||
</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;
|
||||
});
|
||||
|
||||
@@ -1,2 +1,37 @@
|
||||
AngularJS version of Bootstrap's collapse plugin.
|
||||
Provides a simple way to hide and show an element with a css transition
|
||||
**uib-collapse** provides a simple way to hide and show an element with a css transition
|
||||
|
||||
### 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,18 +1,24 @@
|
||||
describe('collapse directive', function () {
|
||||
|
||||
var scope, $compile, $timeout, $transition;
|
||||
var element;
|
||||
describe('collapse directive', function() {
|
||||
var element, compileFn, scope, $compile, $animate, $q;
|
||||
|
||||
beforeEach(module('ui.bootstrap.collapse'));
|
||||
beforeEach(inject(function(_$rootScope_, _$compile_, _$timeout_, _$transition_) {
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
beforeEach(inject(function(_$rootScope_, _$compile_, _$animate_, _$q_) {
|
||||
scope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
$timeout = _$timeout_;
|
||||
$transition = _$transition_;
|
||||
$animate = _$animate_;
|
||||
$q = _$q_;
|
||||
}));
|
||||
|
||||
beforeEach(function() {
|
||||
element = $compile('<div 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);
|
||||
});
|
||||
|
||||
@@ -20,63 +26,146 @@ describe('collapse directive', function () {
|
||||
element.remove();
|
||||
});
|
||||
|
||||
it('should be hidden on initialization if isCollapsed = true without transition', function() {
|
||||
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();
|
||||
//No animation timeout here
|
||||
expect(element.height()).toBe(0);
|
||||
assertCallbacks({ collapsed: true });
|
||||
});
|
||||
|
||||
it('should collapse if isCollapsed = true with animation on subsequent use', function() {
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
it('should not trigger any animation on initialization if isCollapsed = true', function() {
|
||||
var wrapperFn = function() {
|
||||
$animate.flush();
|
||||
};
|
||||
|
||||
scope.isCollapsed = true;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
$timeout.flush();
|
||||
expect(element.height()).toBe(0);
|
||||
|
||||
expect(wrapperFn).toThrowError(/No pending animations ready to be closed or flushed/);
|
||||
});
|
||||
|
||||
it('should be shown on initialization if isCollapsed = false without transition', function() {
|
||||
it('should collapse if isCollapsed = true on subsequent use', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
initCallbacks();
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(element.height()).toBe(0);
|
||||
assertCallbacks({ collapsing: true, collapsed: true });
|
||||
});
|
||||
|
||||
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();
|
||||
//No animation timeout here
|
||||
$animate.flush();
|
||||
expect(element.height()).not.toBe(0);
|
||||
assertCallbacks({ expanding: true, expanded: true });
|
||||
});
|
||||
|
||||
it('should expand if isCollapsed = false with animation on subsequent use', function() {
|
||||
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();
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
initCallbacks();
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
$timeout.flush();
|
||||
$animate.flush();
|
||||
expect(element.height()).not.toBe(0);
|
||||
assertCallbacks({ expanding: true, expanded: true });
|
||||
});
|
||||
|
||||
it('should expand if isCollapsed = true with animation on subsequent uses', function() {
|
||||
it('should collapse if isCollapsed = true on subsequent uses', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
initCallbacks();
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$timeout.flush();
|
||||
$animate.flush();
|
||||
expect(element.height()).toBe(0);
|
||||
if ($transition.transitionEndEventName) {
|
||||
element.triggerHandler($transition.transitionEndEventName);
|
||||
expect(element.height()).toBe(0);
|
||||
}
|
||||
assertCallbacks({ collapsing: true, collapsed: true });
|
||||
});
|
||||
|
||||
it('should change aria-expanded attribute', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
expect(element.attr('aria-expanded')).toBe('true');
|
||||
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(element.attr('aria-expanded')).toBe('false');
|
||||
});
|
||||
|
||||
it('should change aria-hidden attribute', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
expect(element.attr('aria-hidden')).toBe('false');
|
||||
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
expect(element.attr('aria-hidden')).toBe('true');
|
||||
});
|
||||
|
||||
describe('dynamic content', function() {
|
||||
|
||||
var element;
|
||||
|
||||
beforeEach(function() {
|
||||
element = angular.element('<div collapse="isCollapsed"><p>Initial content</p><div ng-show="exp">Additional content</div></div>');
|
||||
element = angular.element('<div uib-collapse="isCollapsed"><p>Initial content</p><div ng-show="exp">Additional content</div></div>');
|
||||
$compile(element)(scope);
|
||||
angular.element(document.body).append(element);
|
||||
});
|
||||
@@ -104,6 +193,96 @@ describe('collapse directive', function () {
|
||||
scope.$digest();
|
||||
expect(element.height()).toBeLessThan(collapseHeight);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('expanding callback returning a promise', function() {
|
||||
var defer, collapsedHeight;
|
||||
|
||||
beforeEach(function() {
|
||||
defer = $q.defer();
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
it('should wait for it to resolve before animating', function() {
|
||||
defer.resolve();
|
||||
|
||||
// should now expand
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('collapsing callback returning a promise', function() {
|
||||
var defer, expandedHeight;
|
||||
|
||||
beforeEach(function() {
|
||||
defer = $q.defer();
|
||||
scope.isCollapsed = false;
|
||||
scope.collapsing = function() {
|
||||
return defer.promise;
|
||||
};
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
+478
-65
@@ -1,72 +1,317 @@
|
||||
angular.module('ui.bootstrap.dateparser', [])
|
||||
|
||||
.service('dateParser', ['$locale', 'orderByFilter', function($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;
|
||||
|
||||
this.parsers = {};
|
||||
var localeId;
|
||||
var formatCodeToRegex;
|
||||
|
||||
var formatCodeToRegex = {
|
||||
'yyyy': {
|
||||
regex: '\\d{4}',
|
||||
apply: function(value) { this.year = +value; }
|
||||
},
|
||||
'yy': {
|
||||
regex: '\\d{2}',
|
||||
apply: function(value) { this.year = +value + 2000; }
|
||||
},
|
||||
'y': {
|
||||
regex: '\\d{1,4}',
|
||||
apply: function(value) { this.year = +value; }
|
||||
},
|
||||
'MMMM': {
|
||||
regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
|
||||
apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
|
||||
},
|
||||
'MMM': {
|
||||
regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
|
||||
apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
|
||||
},
|
||||
'MM': {
|
||||
regex: '0[1-9]|1[0-2]',
|
||||
apply: function(value) { this.month = value - 1; }
|
||||
},
|
||||
'M': {
|
||||
regex: '[1-9]|1[0-2]',
|
||||
apply: function(value) { this.month = value - 1; }
|
||||
},
|
||||
'dd': {
|
||||
regex: '[0-2][0-9]{1}|3[0-1]{1}',
|
||||
apply: function(value) { this.date = +value; }
|
||||
},
|
||||
'd': {
|
||||
regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
|
||||
apply: function(value) { this.date = +value; }
|
||||
},
|
||||
'EEEE': {
|
||||
regex: $locale.DATETIME_FORMATS.DAY.join('|')
|
||||
},
|
||||
'EEE': {
|
||||
regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
|
||||
this.init = function() {
|
||||
localeId = $locale.id;
|
||||
|
||||
this.parsers = {};
|
||||
this.formatters = {};
|
||||
|
||||
formatCodeToRegex = [
|
||||
{
|
||||
key: 'yyyy',
|
||||
regex: '\\d{4}',
|
||||
apply: function(value) { this.year = +value; },
|
||||
formatter: function(date) {
|
||||
var _date = new Date();
|
||||
_date.setFullYear(Math.abs(date.getFullYear()));
|
||||
return dateFilter(_date, 'yyyy');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'yy',
|
||||
regex: '\\d{2}',
|
||||
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');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'y',
|
||||
regex: '\\d{1,4}',
|
||||
apply: function(value) { this.year = +value; },
|
||||
formatter: function(date) {
|
||||
var _date = new Date();
|
||||
_date.setFullYear(Math.abs(date.getFullYear()));
|
||||
return dateFilter(_date, 'y');
|
||||
}
|
||||
},
|
||||
{
|
||||
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); },
|
||||
formatter: function(date) { return dateFilter(date, 'MMMM'); }
|
||||
},
|
||||
{
|
||||
key: 'MMM',
|
||||
regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
|
||||
apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); },
|
||||
formatter: function(date) { return dateFilter(date, 'MMM'); }
|
||||
},
|
||||
{
|
||||
key: 'MM',
|
||||
regex: '0[1-9]|1[0-2]',
|
||||
apply: function(value) { this.month = value - 1; },
|
||||
formatter: function(date) { return dateFilter(date, 'MM'); }
|
||||
},
|
||||
{
|
||||
key: 'M',
|
||||
regex: '[1-9]|1[0-2]',
|
||||
apply: function(value) { this.month = value - 1; },
|
||||
formatter: function(date) { return dateFilter(date, 'M'); }
|
||||
},
|
||||
{
|
||||
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; },
|
||||
formatter: function(date) { return dateFilter(date, 'dd'); }
|
||||
},
|
||||
{
|
||||
key: 'd',
|
||||
regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
|
||||
apply: function(value) { this.date = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'd'); }
|
||||
},
|
||||
{
|
||||
key: 'EEEE',
|
||||
regex: $locale.DATETIME_FORMATS.DAY.join('|'),
|
||||
formatter: function(date) { return dateFilter(date, 'EEEE'); }
|
||||
},
|
||||
{
|
||||
key: 'EEE',
|
||||
regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'),
|
||||
formatter: function(date) { return dateFilter(date, 'EEE'); }
|
||||
},
|
||||
{
|
||||
key: 'HH',
|
||||
regex: '(?:0|1)[0-9]|2[0-3]',
|
||||
apply: function(value) { this.hours = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'HH'); }
|
||||
},
|
||||
{
|
||||
key: 'hh',
|
||||
regex: '0[0-9]|1[0-2]',
|
||||
apply: function(value) { this.hours = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'hh'); }
|
||||
},
|
||||
{
|
||||
key: 'H',
|
||||
regex: '1?[0-9]|2[0-3]',
|
||||
apply: function(value) { this.hours = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'H'); }
|
||||
},
|
||||
{
|
||||
key: 'h',
|
||||
regex: '[0-9]|1[0-2]',
|
||||
apply: function(value) { this.hours = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'h'); }
|
||||
},
|
||||
{
|
||||
key: 'mm',
|
||||
regex: '[0-5][0-9]',
|
||||
apply: function(value) { this.minutes = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'mm'); }
|
||||
},
|
||||
{
|
||||
key: 'm',
|
||||
regex: '[0-9]|[1-5][0-9]',
|
||||
apply: function(value) { this.minutes = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'm'); }
|
||||
},
|
||||
{
|
||||
key: 'sss',
|
||||
regex: '[0-9][0-9][0-9]',
|
||||
apply: function(value) { this.milliseconds = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'sss'); }
|
||||
},
|
||||
{
|
||||
key: 'ss',
|
||||
regex: '[0-5][0-9]',
|
||||
apply: function(value) { this.seconds = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 'ss'); }
|
||||
},
|
||||
{
|
||||
key: 's',
|
||||
regex: '[0-9]|[1-5][0-9]',
|
||||
apply: function(value) { this.seconds = +value; },
|
||||
formatter: function(date) { return dateFilter(date, 's'); }
|
||||
},
|
||||
{
|
||||
key: 'a',
|
||||
regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
|
||||
apply: function(value) {
|
||||
if (this.hours === 12) {
|
||||
this.hours = 0;
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -76,34 +321,160 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
};
|
||||
}
|
||||
|
||||
this.parse = function(input, format) {
|
||||
if ( !angular.isString(input) || !format ) {
|
||||
return input;
|
||||
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 ( !this.parsers[format] ) {
|
||||
this.parsers[format] = createParser(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;
|
||||
}
|
||||
|
||||
format = $locale.DATETIME_FORMATS[format] || format;
|
||||
format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
|
||||
|
||||
if ($locale.id !== localeId) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
if (!this.parsers[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())) {
|
||||
fields = {
|
||||
year: baseDate.getFullYear(),
|
||||
month: baseDate.getMonth(),
|
||||
date: baseDate.getDate(),
|
||||
hours: baseDate.getHours(),
|
||||
minutes: baseDate.getMinutes(),
|
||||
seconds: baseDate.getSeconds(),
|
||||
milliseconds: baseDate.getMilliseconds()
|
||||
};
|
||||
} else {
|
||||
if (baseDate) {
|
||||
$log.warn('dateparser:', 'baseDate is not a valid date');
|
||||
}
|
||||
fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
|
||||
}
|
||||
|
||||
if ( results && results.length ) {
|
||||
var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt;
|
||||
for (var i = 1, n = results.length; i < n; i++) {
|
||||
var mapper = map[i - 1];
|
||||
if (mapper.matcher === 'Z') {
|
||||
tzOffset = true;
|
||||
}
|
||||
|
||||
for( var i = 1, n = results.length; i < n; i++ ) {
|
||||
var mapper = map[i-1];
|
||||
if ( mapper.apply ) {
|
||||
if (mapper.apply) {
|
||||
mapper.apply.call(fields, results[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if ( isValid(fields.year, fields.month, fields.date) ) {
|
||||
dt = new Date( fields.year, fields.month, fields.date, fields.hours);
|
||||
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()) && !tzOffset) {
|
||||
dt = new Date(baseDate);
|
||||
datesetter.call(dt, fields.year, fields.month, fields.date);
|
||||
timesetter.call(dt, fields.hours, fields.minutes,
|
||||
fields.seconds, fields.milliseconds);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return dt;
|
||||
@@ -113,14 +484,56 @@ angular.module('ui.bootstrap.dateparser', [])
|
||||
// Check if date is valid for specific month (and year for February).
|
||||
// Month: 0 = Jan, 1 = Feb, etc
|
||||
function isValid(year, month, date) {
|
||||
if ( month === 1 && date > 28) {
|
||||
return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
|
||||
if (date < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( month === 3 || month === 5 || month === 8 || month === 10) {
|
||||
return date < 31;
|
||||
if (month === 1 && date > 28) {
|
||||
return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);
|
||||
}
|
||||
|
||||
if (month === 3 || month === 5 || month === 8 || month === 10) {
|
||||
return date < 31;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function toInt(str) {
|
||||
return parseInt(str, 10);
|
||||
}
|
||||
|
||||
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,16 +1,283 @@
|
||||
describe('date parser', function () {
|
||||
var dateParser;
|
||||
describe('date parser', function() {
|
||||
var dateParser, oldDate;
|
||||
|
||||
beforeEach(module('ui.bootstrap.dateparser'));
|
||||
beforeEach(inject(function (_dateParser_) {
|
||||
dateParser = _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);
|
||||
}
|
||||
|
||||
describe('wih custom formats', function() {
|
||||
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));
|
||||
expectParse('31.12.2013', 'dd.MM.yyyy', new Date(2013, 11, 31, 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,10 +361,202 @@ 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() {
|
||||
expectParse('22.March.15.22', 'd.MMMM.yy.HH', new Date(2015, 2, 22, 22));
|
||||
expectParse('8-March-1991-11', 'd-MMMM-yyyy-HH', new Date(1991, 2, 8, 11));
|
||||
expectParse('February/5/1980/00', 'MMMM/d/yyyy/HH', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/February/5 03', 'yyyy/MMMM/d HH', new Date(1955, 1, 5, 3));
|
||||
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));
|
||||
expectParse('8-March-1991-11', 'd-MMMM-yyyy-hh', new Date(1991, 2, 8, 11));
|
||||
expectParse('February/5/1980/00', 'MMMM/d/yyyy/hh', new Date(1980, 1, 5, 0));
|
||||
expectParse('1955/February/5 03', 'yyyy/MMMM/d hh', new Date(1955, 1, 5, 3));
|
||||
expectParse('11-08-13 23', 'd-MM-yy hh', undefined);
|
||||
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.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));
|
||||
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 3', 'd-MM-yy h', new Date(2013, 7, 11, 3));
|
||||
});
|
||||
|
||||
it('should work correctly for `mm`', function() {
|
||||
expectParse('22.March.15.22', 'd.MMMM.yy.mm', new Date(2015, 2, 22, 0, 22));
|
||||
expectParse('8-March-1991-59', 'd-MMMM-yyyy-mm', new Date(1991, 2, 8, 0, 59));
|
||||
expectParse('February/5/1980/00', 'MMMM/d/yyyy/mm', new Date(1980, 1, 5, 0, 0));
|
||||
expectParse('1955/February/5 03', 'yyyy/MMMM/d mm', new Date(1955, 1, 5, 0, 3));
|
||||
expectParse('11-08-13 46', 'd-MM-yy mm', new Date(2013, 7, 11, 0, 46));
|
||||
expectParse('22.March.15.22:33', 'd.MMMM.yy.HH:mm', new Date(2015, 2, 22, 22, 33));
|
||||
expectParse('22.March.15.2:01', 'd.MMMM.yy.H:mm', new Date(2015, 2, 22, 2, 1));
|
||||
});
|
||||
|
||||
it('should work correctly for `m`', function() {
|
||||
expectParse('22.March.15.22', 'd.MMMM.yy.m', new Date(2015, 2, 22, 0, 22));
|
||||
expectParse('8-March-1991-59', 'd-MMMM-yyyy-m', new Date(1991, 2, 8, 0, 59));
|
||||
expectParse('February/5/1980/0', 'MMMM/d/yyyy/m', new Date(1980, 1, 5, 0, 0));
|
||||
expectParse('1955/February/5 3', 'yyyy/MMMM/d m', new Date(1955, 1, 5, 0, 3));
|
||||
expectParse('11-08-13 46', 'd-MM-yy m', new Date(2013, 7, 11, 0, 46));
|
||||
expectParse('22.March.15.22:3', 'd.MMMM.yy.HH:m', new Date(2015, 2, 22, 22, 3));
|
||||
expectParse('22.March.15.2:1', 'd.MMMM.yy.H:m', new Date(2015, 2, 22, 2, 1));
|
||||
});
|
||||
|
||||
it('should work correctly for `sss`', function() {
|
||||
expectParse('22.March.15.123', 'd.MMMM.yy.sss', new Date(2015, 2, 22, 0, 0, 0, 123));
|
||||
expectParse('8-March-1991-059', 'd-MMMM-yyyy-sss', new Date(1991, 2, 8, 0, 0, 0, 59));
|
||||
expectParse('February/5/1980/000', 'MMMM/d/yyyy/sss', new Date(1980, 1, 5, 0, 0, 0));
|
||||
expectParse('1955/February/5 003', 'yyyy/MMMM/d sss', new Date(1955, 1, 5, 0, 0, 0, 3));
|
||||
expectParse('11-08-13 046', 'd-MM-yy sss', new Date(2013, 7, 11, 0, 0, 0, 46));
|
||||
expectParse('22.March.15.22:33:044', 'd.MMMM.yy.HH:mm:sss', new Date(2015, 2, 22, 22, 33, 0, 44));
|
||||
expectParse('22.March.15.0:0:001', 'd.MMMM.yy.H:m:sss', new Date(2015, 2, 22, 0, 0, 0, 1));
|
||||
});
|
||||
|
||||
it('should work correctly for `ss`', function() {
|
||||
expectParse('22.March.15.22', 'd.MMMM.yy.ss', new Date(2015, 2, 22, 0, 0, 22));
|
||||
expectParse('8-March-1991-59', 'd-MMMM-yyyy-ss', new Date(1991, 2, 8, 0, 0, 59));
|
||||
expectParse('February/5/1980/00', 'MMMM/d/yyyy/ss', new Date(1980, 1, 5, 0, 0, 0));
|
||||
expectParse('1955/February/5 03', 'yyyy/MMMM/d ss', new Date(1955, 1, 5, 0, 0, 3));
|
||||
expectParse('11-08-13 46', 'd-MM-yy ss', new Date(2013, 7, 11, 0, 0, 46));
|
||||
expectParse('22.March.15.22:33:44', 'd.MMMM.yy.HH:mm:ss', new Date(2015, 2, 22, 22, 33, 44));
|
||||
expectParse('22.March.15.0:0:01', 'd.MMMM.yy.H:m:ss', new Date(2015, 2, 22, 0, 0, 1));
|
||||
});
|
||||
|
||||
it('should work correctly for `s`', function() {
|
||||
expectParse('22.March.15.22', 'd.MMMM.yy.s', new Date(2015, 2, 22, 0, 0, 22));
|
||||
expectParse('8-March-1991-59', 'd-MMMM-yyyy-s', new Date(1991, 2, 8, 0, 0, 59));
|
||||
expectParse('February/5/1980/0', 'MMMM/d/yyyy/s', new Date(1980, 1, 5, 0, 0, 0));
|
||||
expectParse('1955/February/5 3', 'yyyy/MMMM/d s', new Date(1955, 1, 5, 0, 0, 3));
|
||||
expectParse('11-08-13 46', 'd-MM-yy s', new Date(2013, 7, 11, 0, 0, 46));
|
||||
expectParse('22.March.15.22:33:4', 'd.MMMM.yy.HH:mm:s', new Date(2015, 2, 22, 22, 33, 4));
|
||||
expectParse('22.March.15.22:3:4', 'd.MMMM.yy.HH:m:s', new Date(2015, 2, 22, 22, 3, 4));
|
||||
});
|
||||
|
||||
it('should work correctly for `a`', function() {
|
||||
expectParse('22.March.15.10AM', 'd.MMMM.yy.hha', new Date(2015, 2, 22, 10));
|
||||
expectParse('22.March.15.10PM', 'd.MMMM.yy.hha', new Date(2015, 2, 22, 22));
|
||||
expectParse('8-March-1991-11AM', 'd-MMMM-yyyy-hha', new Date(1991, 2, 8, 11));
|
||||
expectParse('8-March-1991-11PM', 'd-MMMM-yyyy-hha', new Date(1991, 2, 8, 23));
|
||||
expectParse('February/5/1980/12AM', 'MMMM/d/yyyy/hha', new Date(1980, 1, 5, 0));
|
||||
expectParse('February/5/1980/12PM', 'MMMM/d/yyyy/hha', new Date(1980, 1, 5, 12));
|
||||
expectParse('1955/February/5 03AM', 'yyyy/MMMM/d hha', new Date(1955, 1, 5, 3));
|
||||
expectParse('1955/February/5 03PM', 'yyyy/MMMM/d hha', new Date(1955, 1, 5, 15));
|
||||
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('wih predefined formats', function() {
|
||||
describe('with predefined formats', function() {
|
||||
it('should work correctly for `shortDate`', function() {
|
||||
expectParse('9/3/10', 'shortDate', new Date(2010, 8, 3, 0));
|
||||
});
|
||||
@@ -79,9 +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() {
|
||||
expectParse('00.02.2013', 'dd.MM.yyyy', undefined);
|
||||
});
|
||||
|
||||
it('should work for 29 days in February for leap years', function() {
|
||||
@@ -89,18 +634,185 @@ 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 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 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) {
|
||||
spyOn(dateParser, 'init').and.callThrough();
|
||||
expect($locale.id).toBe('en-us');
|
||||
|
||||
$locale.id = 'en-uk';
|
||||
|
||||
dateParser.parse('22.March.15.22', 'd.MMMM.yy.s');
|
||||
|
||||
expect(dateParser.init).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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('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);
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('overrideParser', function() {
|
||||
var twoDigitYearParser = function (value) {
|
||||
this.year = +value + (+value > 30 ? 1900 : 2000);
|
||||
};
|
||||
|
||||
it('should get the current parser', function() {
|
||||
expect(dateParser.getParser('yy')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should override the parser', function() {
|
||||
dateParser.overrideParser('yy', twoDigitYearParser);
|
||||
expect(dateParser.parse('68', 'yy').getFullYear()).toEqual(1968);
|
||||
expect(dateParser.parse('67', 'yy').getFullYear()).toEqual(1967);
|
||||
expect(dateParser.parse('31', 'yy').getFullYear()).toEqual(1931);
|
||||
expect(dateParser.parse('30', 'yy').getFullYear()).toEqual(2030);
|
||||
});
|
||||
|
||||
it('should clear cached parsers', function() {
|
||||
expect(dateParser.parse('68', 'yy').getFullYear()).toEqual(2068);
|
||||
dateParser.overrideParser('yy', twoDigitYearParser);
|
||||
expect(dateParser.parse('68', 'yy').getFullYear()).toEqual(1968);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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%
|
||||
}
|
||||
+551
-505
File diff suppressed because it is too large
Load Diff
@@ -1,31 +1,26 @@
|
||||
<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="DatepickerDemoCtrl">
|
||||
<pre>Selected date is: <em>{{dt | date:'fullDate' }}</em></pre>
|
||||
|
||||
<h4>Inline</h4>
|
||||
<div style="display:inline-block; min-height:290px;">
|
||||
<datepicker ng-model="dt" min-date="minDate" show-weeks="true" class="well well-sm"></datepicker>
|
||||
</div>
|
||||
|
||||
<h4>Popup</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="input-group">
|
||||
<input type="text" class="form-control" datepicker-popup="{{format}}" ng-model="dt" is-open="opened" min-date="minDate" max-date="'2015-06-22'" 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 />
|
||||
<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="dt = '2009-08-24'">2009-08-24</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()" tooltip="After today restriction">Min date</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="toggleMin()" uib-tooltip="After today restriction">Min date</button>
|
||||
</div>
|
||||
|
||||
+47
-16
@@ -4,32 +4,63 @@ 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.open = function($event) {
|
||||
$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
|
||||
$scope.opened = true;
|
||||
$scope.setDate = function(year, month, day) {
|
||||
$scope.dt = new Date(year, month, day);
|
||||
};
|
||||
|
||||
$scope.dateOptions = {
|
||||
formatYear: 'yy',
|
||||
startingDay: 1
|
||||
};
|
||||
var tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
var afterTomorrow = new Date(tomorrow);
|
||||
afterTomorrow.setDate(tomorrow.getDate() + 1);
|
||||
$scope.events = [
|
||||
{
|
||||
date: tomorrow,
|
||||
status: 'full'
|
||||
},
|
||||
{
|
||||
date: afterTomorrow,
|
||||
status: 'partially'
|
||||
}
|
||||
];
|
||||
|
||||
$scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
|
||||
$scope.format = $scope.formats[0];
|
||||
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 '';
|
||||
}
|
||||
});
|
||||
|
||||
+119
-87
@@ -1,119 +1,147 @@
|
||||
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 `datepicker` or globally configured through the `datepickerConfig`.
|
||||
### 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)_.
|
||||
* `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 not 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.
|
||||
|
||||
* `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.
|
||||
|
||||
### Popup Settings ###
|
||||
* `minMode`
|
||||
<small class="badge">C</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `day`)_ -
|
||||
Sets a lower limit for mode.
|
||||
|
||||
Options for datepicker can be passed as JSON using the `datepicker-options` attribute.
|
||||
Specific settings for the `datepicker-popup`, that can globally configured through the `datepickerPopupConfig`, are:
|
||||
* `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
|
||||
|
||||
* `datepicker-popup`
|
||||
_(Default: 'yyyy-MM-dd')_ :
|
||||
The format for displayed dates.
|
||||
* `shortcutPropagation`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `false`)_ -
|
||||
An option to disable the propagation of the keydown event.
|
||||
|
||||
* `show-button-bar`
|
||||
_(Default: true)_ :
|
||||
Whether to display a button bar underneath the datepicker.
|
||||
* `showWeeks`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `true`)_ -
|
||||
Whether to display week numbers.
|
||||
|
||||
* `current-text`
|
||||
_(Default: 'Today')_ :
|
||||
The text to display for the current day button.
|
||||
* `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.
|
||||
|
||||
* `clear-text`
|
||||
_(Default: 'Clear')_ :
|
||||
The text to display for the clear button.
|
||||
* `yearColumns`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `5`)_ -
|
||||
Number of columns displayed in year selection.
|
||||
|
||||
* `close-text`
|
||||
_(Default: 'Done')_ :
|
||||
The text to display for the close button.
|
||||
### Keyboard support
|
||||
|
||||
* `close-on-date-selection`
|
||||
_(Default: true)_ :
|
||||
Whether to close calendar when a date is chosen.
|
||||
|
||||
* `datepicker-append-to-body`
|
||||
_(Default: false)_:
|
||||
Append the datepicker popup element to `body`, rather than inserting after `datepicker-popup`. For global configuration, use `datepickerPopupConfig.appendToBody`.
|
||||
|
||||
### Keyboard Support ###
|
||||
|
||||
Depending on datepicker's current mode, the date may reffer either to day, month or year. Accordingly, the term view reffers either to a month, year or year range.
|
||||
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.
|
||||
|
||||
* `Left`: Move focus to the previous date. Will move to the last date of the previous view, if the current date is the first date of a view.
|
||||
* `Right`: Move focus to the next date. Will move to the first date of the following view, if the current date is the last date of a view.
|
||||
@@ -126,4 +154,8 @@ Depending on datepicker's current mode, the date may reffer 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');
|
||||
+1548
-1506
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]);
|
||||
});
|
||||
});
|
||||
+111
-18
@@ -1,11 +1,11 @@
|
||||
|
||||
<div ng-controller="DropdownCtrl">
|
||||
<!-- Simple dropdown -->
|
||||
<span class="dropdown" on-toggle="toggled(open)">
|
||||
<a href class="dropdown-toggle">
|
||||
<span uib-dropdown on-toggle="toggled(open)">
|
||||
<a href id="simple-dropdown" uib-dropdown-toggle>
|
||||
Click me for a dropdown, yo!
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<ul class="dropdown-menu" uib-dropdown-menu aria-labelledby="simple-dropdown">
|
||||
<li ng-repeat="choice in items">
|
||||
<a href>{{choice}}</a>
|
||||
</li>
|
||||
@@ -13,32 +13,55 @@
|
||||
</span>
|
||||
|
||||
<!-- Single button -->
|
||||
<div class="btn-group" dropdown is-open="status.isopen">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" ng-disabled="disabled">
|
||||
<div class="btn-group" uib-dropdown is-open="status.isopen">
|
||||
<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="dropdown-menu" role="menu">
|
||||
<li><a href="#">Action</a></li>
|
||||
<li><a href="#">Another action</a></li>
|
||||
<li><a href="#">Something else here</a></li>
|
||||
<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>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#">Separated link</a></li>
|
||||
<li role="menuitem"><a href="#">Separated link</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Split button -->
|
||||
<div class="btn-group" dropdown>
|
||||
<button type="button" class="btn btn-danger">Action</button>
|
||||
<button type="button" class="btn btn-danger dropdown-toggle">
|
||||
<div class="btn-group" uib-dropdown>
|
||||
<button id="split-button" type="button" class="btn btn-danger">Action</button>
|
||||
<button type="button" class="btn btn-danger" uib-dropdown-toggle>
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">Split button!</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="#">Action</a></li>
|
||||
<li><a href="#">Another action</a></li>
|
||||
<li><a href="#">Something else here</a></li>
|
||||
<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>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#">Separated link</a></li>
|
||||
<li role="menuitem"><a href="#">Separated link</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Single button using append-to-body -->
|
||||
<div class="btn-group" uib-dropdown dropdown-append-to-body>
|
||||
<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="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>
|
||||
<li class="divider"></li>
|
||||
<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="dropdown-menu" uib-dropdown-menu template-url="dropdown.html" aria-labelledby="button-template-url">
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -48,4 +71,74 @@
|
||||
<button type="button" class="btn btn-warning btn-sm" ng-click="disabled = !disabled">Enable/Disable</button>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
<!-- Single button with 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="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>
|
||||
<li class="divider"></li>
|
||||
<li role="menuitem"><a href="#">Separated link</a></li>
|
||||
</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="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>
|
||||
<li class="divider"></li>
|
||||
<li role="menuitem"><a href="#">Separated link in Template</a></li>
|
||||
</ul>
|
||||
</script>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module('ui.bootstrap.demo').controller('DropdownCtrl', function ($scope) {
|
||||
angular.module('ui.bootstrap.demo').controller('DropdownCtrl', function ($scope, $log) {
|
||||
$scope.items = [
|
||||
'The first choice!',
|
||||
'And another choice for you.',
|
||||
@@ -10,7 +10,7 @@ angular.module('ui.bootstrap.demo').controller('DropdownCtrl', function ($scope)
|
||||
};
|
||||
|
||||
$scope.toggled = function(open) {
|
||||
console.log('Dropdown is now: ', open);
|
||||
$log.log('Dropdown is now: ', open);
|
||||
};
|
||||
|
||||
$scope.toggleDropdown = function($event) {
|
||||
@@ -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'));
|
||||
});
|
||||
|
||||
@@ -1,4 +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 dropdown-toggle>` element to toggle it when is clicked.
|
||||
There is also the `on-toggle(open)` optional expression fired when dropdown changes state.
|
||||
|
||||
This directive is composed by three parts:
|
||||
|
||||
* `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.
|
||||
|
||||
Each of these parts need to be used as attribute directives.
|
||||
|
||||
### uib-dropdown settings
|
||||
|
||||
* `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.
|
||||
|
||||
* `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.
|
||||
|
||||
+342
-61
@@ -1,64 +1,157 @@
|
||||
angular.module('ui.bootstrap.dropdown', [])
|
||||
angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.position'])
|
||||
|
||||
.constant('dropdownConfig', {
|
||||
.constant('uibDropdownConfig', {
|
||||
appendToOpenClass: 'uib-dropdown-open',
|
||||
openClass: 'open'
|
||||
})
|
||||
|
||||
.service('dropdownService', ['$document', function($document) {
|
||||
.service('uibDropdownService', ['$document', '$rootScope', '$$multiMap', function($document, $rootScope, $$multiMap) {
|
||||
var openScope = null;
|
||||
var openedContainers = $$multiMap.createNew();
|
||||
|
||||
this.open = function( dropdownScope ) {
|
||||
if ( !openScope ) {
|
||||
$document.bind('click', closeDropdown);
|
||||
$document.bind('keydown', escapeKeyBind);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if ( openScope && openScope !== dropdownScope ) {
|
||||
openScope.isOpen = false;
|
||||
return false;
|
||||
};
|
||||
|
||||
this.open = function(dropdownScope, element, appendTo) {
|
||||
if (!openScope) {
|
||||
$document.on('click', closeDropdown);
|
||||
}
|
||||
|
||||
if (openScope && openScope !== dropdownScope) {
|
||||
openScope.isOpen = false;
|
||||
}
|
||||
|
||||
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 ) {
|
||||
if ( openScope === 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', escapeKeyBind);
|
||||
}
|
||||
|
||||
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 ) {
|
||||
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 || !openScope.isOpen) { 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) ) {
|
||||
return;
|
||||
if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
openScope.$apply(function() {
|
||||
openScope.isOpen = false;
|
||||
});
|
||||
var dropdownElement = openScope.getDropdownElement();
|
||||
if (evt && openScope.getAutoClose() === 'outsideClick' &&
|
||||
dropdownElement && dropdownElement[0].contains(evt.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
openScope.focusToggleElement();
|
||||
openScope.isOpen = false;
|
||||
|
||||
if (!$rootScope.$$phase) {
|
||||
openScope.$apply();
|
||||
}
|
||||
};
|
||||
|
||||
var escapeKeyBind = function( evt ) {
|
||||
if ( evt.which === 27 ) {
|
||||
this.keybindFilter = function(evt) {
|
||||
if (!openScope) {
|
||||
// see this.close as ESC could have been pressed which kills the scope so we can not proceed
|
||||
return;
|
||||
}
|
||||
|
||||
var dropdownElement = openScope.getDropdownElement();
|
||||
var toggleElement = openScope.getToggleElement();
|
||||
var dropdownElementTargeted = dropdownElement && dropdownElement[0].contains(evt.target);
|
||||
var toggleElementTargeted = toggleElement && toggleElement[0].contains(evt.target);
|
||||
if (evt.which === 27) {
|
||||
evt.stopPropagation();
|
||||
openScope.focusToggleElement();
|
||||
closeDropdown();
|
||||
} else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen && (dropdownElementTargeted || toggleElementTargeted)) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
openScope.focusDropdownEntry(evt.which);
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) {
|
||||
.controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) {
|
||||
var self = this,
|
||||
scope = $scope.$new(), // create a child scope so we are not polluting original one
|
||||
openClass = dropdownConfig.openClass,
|
||||
getIsOpen,
|
||||
setIsOpen = angular.noop,
|
||||
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop;
|
||||
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,
|
||||
keynavEnabled = false,
|
||||
selectedOption = null,
|
||||
body = $document.find('body');
|
||||
|
||||
this.init = function( element ) {
|
||||
self.$element = element;
|
||||
$element.addClass('dropdown');
|
||||
|
||||
if ( $attrs.isOpen ) {
|
||||
this.init = function() {
|
||||
if ($attrs.isOpen) {
|
||||
getIsOpen = $parse($attrs.isOpen);
|
||||
setIsOpen = getIsOpen.assign;
|
||||
|
||||
@@ -66,10 +159,17 @@ angular.module('ui.bootstrap.dropdown', [])
|
||||
scope.isOpen = !!value;
|
||||
});
|
||||
}
|
||||
|
||||
keynavEnabled = angular.isDefined($attrs.keyboardNav);
|
||||
};
|
||||
|
||||
this.toggle = function( open ) {
|
||||
return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
|
||||
this.toggle = function(open) {
|
||||
scope.isOpen = arguments.length ? !!open : !scope.isOpen;
|
||||
if (angular.isFunction(setIsOpen)) {
|
||||
setIsOpen(scope, scope.isOpen);
|
||||
}
|
||||
|
||||
return scope.isOpen;
|
||||
};
|
||||
|
||||
// Allow other directives to watch status
|
||||
@@ -81,78 +181,259 @@ angular.module('ui.bootstrap.dropdown', [])
|
||||
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') :
|
||||
$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 ) {
|
||||
if (self.toggleElement) {
|
||||
self.toggleElement[0].focus();
|
||||
}
|
||||
};
|
||||
|
||||
scope.$watch('isOpen', function( isOpen, wasOpen ) {
|
||||
$animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass);
|
||||
function removeDropdownMenu() {
|
||||
$element.append(self.dropdownMenu);
|
||||
}
|
||||
|
||||
scope.$watch('isOpen', function(isOpen, wasOpen) {
|
||||
var appendTo = null,
|
||||
appendToBody = false;
|
||||
|
||||
if (angular.isDefined($attrs.dropdownAppendTo)) {
|
||||
var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
|
||||
if (appendToEl) {
|
||||
appendTo = angular.element(appendToEl);
|
||||
}
|
||||
}
|
||||
|
||||
if (angular.isDefined($attrs.dropdownAppendToBody)) {
|
||||
var appendToBodyValue = $parse($attrs.dropdownAppendToBody)(scope);
|
||||
if (appendToBodyValue !== false) {
|
||||
appendToBody = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (appendToBody && !appendTo) {
|
||||
appendTo = body;
|
||||
}
|
||||
|
||||
if (appendTo && self.dropdownMenu) {
|
||||
if (isOpen) {
|
||||
appendTo.append(self.dropdownMenu);
|
||||
$element.on('$destroy', removeDropdownMenu);
|
||||
} else {
|
||||
$element.off('$destroy', removeDropdownMenu);
|
||||
removeDropdownMenu();
|
||||
}
|
||||
}
|
||||
|
||||
if (appendTo && self.dropdownMenu) {
|
||||
var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
|
||||
css,
|
||||
rightalign,
|
||||
scrollbarPadding,
|
||||
scrollbarWidth = 0;
|
||||
|
||||
css = {
|
||||
top: pos.top + 'px',
|
||||
display: isOpen ? 'block' : 'none'
|
||||
};
|
||||
|
||||
rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
|
||||
if (!rightalign) {
|
||||
css.left = pos.left + 'px';
|
||||
css.right = 'auto';
|
||||
} else {
|
||||
css.left = 'auto';
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
$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;
|
||||
$document.on('keydown', uibDropdownService.keybindFilter);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$document.on('keydown', uibDropdownService.keybindFilter);
|
||||
}
|
||||
|
||||
if ( isOpen ) {
|
||||
scope.focusToggleElement();
|
||||
dropdownService.open( scope );
|
||||
uibDropdownService.open(scope, $element, appendTo);
|
||||
} else {
|
||||
dropdownService.close( scope );
|
||||
uibDropdownService.close(scope, $element, appendTo);
|
||||
if (self.dropdownMenuTemplateUrl) {
|
||||
if (templateScope) {
|
||||
templateScope.$destroy();
|
||||
}
|
||||
var newEl = angular.element('<ul class="dropdown-menu"></ul>');
|
||||
self.dropdownMenu.replaceWith(newEl);
|
||||
self.dropdownMenu = newEl;
|
||||
}
|
||||
|
||||
self.selectedOption = null;
|
||||
}
|
||||
|
||||
setIsOpen($scope, isOpen);
|
||||
if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
|
||||
toggleInvoker($scope, { open: !!isOpen });
|
||||
if (angular.isFunction(setIsOpen)) {
|
||||
setIsOpen($scope, isOpen);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('$locationChangeSuccess', function() {
|
||||
scope.isOpen = false;
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
scope.$destroy();
|
||||
});
|
||||
}])
|
||||
|
||||
.directive('dropdown', function() {
|
||||
.directive('uibDropdown', function() {
|
||||
return {
|
||||
restrict: 'CA',
|
||||
controller: 'DropdownController',
|
||||
controller: 'UibDropdownController',
|
||||
link: function(scope, element, attrs, dropdownCtrl) {
|
||||
dropdownCtrl.init( element );
|
||||
dropdownCtrl.init();
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
.directive('dropdownToggle', function() {
|
||||
.directive('uibDropdownMenu', function() {
|
||||
return {
|
||||
restrict: 'CA',
|
||||
require: '?^dropdown',
|
||||
restrict: 'A',
|
||||
require: '?^uibDropdown',
|
||||
link: function(scope, element, attrs, dropdownCtrl) {
|
||||
if ( !dropdownCtrl ) {
|
||||
if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.addClass('dropdown-menu');
|
||||
|
||||
var tplUrl = attrs.templateUrl;
|
||||
if (tplUrl) {
|
||||
dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
|
||||
}
|
||||
|
||||
if (!dropdownCtrl.dropdownMenu) {
|
||||
dropdownCtrl.dropdownMenu = element;
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
.directive('uibDropdownToggle', function() {
|
||||
return {
|
||||
require: '?^uibDropdown',
|
||||
link: function(scope, element, attrs, dropdownCtrl) {
|
||||
if (!dropdownCtrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.addClass('dropdown-toggle');
|
||||
|
||||
dropdownCtrl.toggleElement = element;
|
||||
|
||||
var toggleDropdown = function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if ( !element.hasClass('disabled') && !attrs.disabled ) {
|
||||
if (!element.hasClass('disabled') && !attrs.disabled) {
|
||||
scope.$apply(function() {
|
||||
dropdownCtrl.toggle();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
element.bind('click', toggleDropdown);
|
||||
element.on('click', toggleDropdown);
|
||||
|
||||
// WAI-ARIA
|
||||
element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
|
||||
scope.$watch(dropdownCtrl.isOpen, function( isOpen ) {
|
||||
scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
|
||||
element.attr('aria-expanded', !!isOpen);
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
element.unbind('click', toggleDropdown);
|
||||
element.off('click', toggleDropdown);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
require('../multiMap');
|
||||
require('../position/index-nocss.js');
|
||||
require('./dropdown');
|
||||
|
||||
var MODULE_NAME = 'ui.bootstrap.module.dropdown';
|
||||
|
||||
angular.module(MODULE_NAME, ['ui.bootstrap.dropdown']);
|
||||
|
||||
module.exports = MODULE_NAME;
|
||||
@@ -0,0 +1,2 @@
|
||||
require('../position/position.css');
|
||||
module.exports = require('./index-nocss.js');
|
||||
+813
-155
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
-13
@@ -1,24 +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 ng-click="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" ng-click="ok()">OK</button>
|
||||
<button class="btn btn-warning" 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 class="btn btn-default" ng-click="open()">Open me!</button>
|
||||
<button class="btn btn-default" ng-click="open('lg')">Large modal</button>
|
||||
<button class="btn btn-default" ng-click="open('sm')">Small modal</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>
|
||||
|
||||
+102
-19
@@ -1,43 +1,126 @@
|
||||
angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope, $modal, $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'];
|
||||
$ctrl.animationsEnabled = true;
|
||||
|
||||
$scope.open = function (size) {
|
||||
|
||||
var modalInstance = $modal.open({
|
||||
$ctrl.open = function (size, parentSelector) {
|
||||
var parentElem = parentSelector ?
|
||||
angular.element($document[0].querySelector('.modal-demo ' + parentSelector)) : undefined;
|
||||
var modalInstance = $uibModal.open({
|
||||
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());
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
// Please note that $modalInstance represents a modal window (instance) dependency.
|
||||
// It is not the same as the $modal service used above.
|
||||
$ctrl.openComponentModal = function () {
|
||||
var modalInstance = $uibModal.open({
|
||||
animation: $ctrl.animationsEnabled,
|
||||
component: 'modalComponent',
|
||||
resolve: {
|
||||
items: function () {
|
||||
return $ctrl.items;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($scope, $modalInstance, items) {
|
||||
|
||||
$scope.items = items;
|
||||
$scope.selected = {
|
||||
item: $scope.items[0]
|
||||
modalInstance.result.then(function (selectedItem) {
|
||||
$ctrl.selected = selectedItem;
|
||||
}, function () {
|
||||
$log.info('modal-component dismissed at: ' + new Date());
|
||||
});
|
||||
};
|
||||
|
||||
$scope.ok = function () {
|
||||
$modalInstance.close($scope.selected.item);
|
||||
$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';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$modalInstance.dismiss('cancel');
|
||||
$ctrl.toggleAnimation = function () {
|
||||
$ctrl.animationsEnabled = !$ctrl.animationsEnabled;
|
||||
};
|
||||
});
|
||||
|
||||
// Please note that $uibModalInstance represents a modal window (instance) dependency.
|
||||
// It is not the same as the $uibModal service used above.
|
||||
|
||||
angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($uibModalInstance, items) {
|
||||
var $ctrl = this;
|
||||
$ctrl.items = items;
|
||||
$ctrl.selected = {
|
||||
item: $ctrl.items[0]
|
||||
};
|
||||
|
||||
$ctrl.ok = function () {
|
||||
$uibModalInstance.close($ctrl.selected.item);
|
||||
};
|
||||
|
||||
$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'});
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user