Compare commits
1845 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 | |||
| 88cd454549 | |||
| 1a998c4686 | |||
| b44215df6f | |||
| bc23d1c2bd | |||
| 08c150e127 | |||
| b75f7afb9e | |||
| 7b7cdf8422 | |||
| a0be450ddb | |||
| e793c37b09 | |||
| 9030d2c971 | |||
| 7512b93fec | |||
| e62ab94a63 | |||
| 63ae06c9e1 | |||
| 5cd6f4af47 | |||
| aa8c6541e6 | |||
| 990015fb89 | |||
| 6e83dc647a | |||
| 79105368b3 | |||
| 8d7c2a263f | |||
| b519b63268 | |||
| e420204fa2 | |||
| cdbfac46d2 | |||
| e8e8fb67b4 | |||
| a708fe6de5 | |||
| 42cc3f269b | |||
| c7db0df400 | |||
| aa188aec02 | |||
| 44e7d77d3a | |||
| 0b31e8658e | |||
| d7c314e21c | |||
| 98af6f9102 | |||
| d51c189004 | |||
| 353e612717 | |||
| ef62364595 | |||
| afdefcf99c | |||
| 1590920cdc | |||
| 3ac3b48746 | |||
| 5b41e47144 | |||
| a316e3ee07 | |||
| 5dc57927e8 | |||
| 941a382858 | |||
| a81d6f986a | |||
| 2b429f5de5 | |||
| 60cee9dcb5 | |||
| 06ad3bd5db | |||
| cc07cdda20 | |||
| 7899b120f8 | |||
| 976f6083e8 | |||
| da95122222 | |||
| 96def3d6e3 | |||
| 00829b6058 | |||
| ded352c5c1 | |||
| e0eb1bce37 | |||
| bd2ae0ee02 | |||
| 73c2dea548 | |||
| 97b0747777 | |||
| b0b1434389 | |||
| 2423f6d4c0 | |||
| de5a25e6de | |||
| cb31b875f9 | |||
| 004dd1de25 | |||
| 4c76a858de | |||
| 82df4fb1c4 | |||
| f48e4339e9 | |||
| 3b6ab25b5c | |||
| 4b811b7adc | |||
| d0024931de | |||
| f9aa4590e9 | |||
| f9b6c49623 | |||
| f715d05225 | |||
| d89bbd1008 | |||
| 6a830116f2 | |||
| c6c0e2d9ef | |||
| 341b5b5886 | |||
| 890e2d37c1 | |||
| 93da30d559 | |||
| 467dd159b3 | |||
| 0d4c2e21c3 | |||
| 3e938bd60a | |||
| 5ca23e9746 | |||
| d19b4c0e85 | |||
| 378a933797 | |||
| e1993491f2 | |||
| dea67b08b5 | |||
| 9a2638bfb0 | |||
| bce250540a | |||
| c01d25511a | |||
| c5862b0bf3 | |||
| 31115017d5 | |||
| 09678b1260 | |||
| cde6a4509f | |||
| a88b115e7a | |||
| 7f4b40eb8b | |||
| e8d5fefcff | |||
| 0daa7a7470 | |||
| 101c43ac3e | |||
| 68e5644024 | |||
| a294c87f23 | |||
| b7eb69edfc | |||
| 22ebd2300e | |||
| 794954afe5 | |||
| 42d6d436c1 | |||
| a6a1049041 | |||
| c336351be0 | |||
| 9de864e9e3 | |||
| 9dfe31572a | |||
| 1946833135 | |||
| ab0387c041 | |||
| 4f56e60e60 | |||
| 4a9dbbef04 | |||
| 3704db9ab0 | |||
| 1ba07c1b5c | |||
| 85140f84ab | |||
| 66cb03bc62 | |||
| f452969f5d | |||
| 6bc6634cf5 | |||
| ab054c96f8 | |||
| 1078573678 | |||
| c188416cab | |||
| 94a7f5934a | |||
| 8c89ccb163 | |||
| 4d641ca718 | |||
| dfc36fd94b | |||
| 6fb6a7657f | |||
| 594a64bfd2 | |||
| d9ee743be6 | |||
| 3d6443476a | |||
| 2a1b80ebf3 | |||
| e204be61ee | |||
| 0314cd40b7 | |||
| 8fb39ce0e1 | |||
| f8c858ad52 | |||
| 22fc117704 | |||
| 25de67dcdc | |||
| 1417c54833 | |||
| ae31079c01 | |||
| 745246ebf0 | |||
| 83ceb78a8b | |||
| 772734177f | |||
| 05490d6501 | |||
| f0a129ada4 | |||
| 2947a2d22c | |||
| 16b4c9a362 | |||
| 3fb39d29c4 | |||
| 23b7baaa76 | |||
| bb85213bab | |||
| 7474c47b1a | |||
| a33bac957e | |||
| 06bd92d4ce | |||
| 9c43ae7c0a | |||
| f88546da0f | |||
| a61112de05 | |||
| 35c93076df | |||
| 34dfff4bbf | |||
| 4a2809290b | |||
| 9d5bc0aec7 | |||
| 0702403d1f | |||
| a1edfe9ef3 | |||
| 68cb2e5aaf | |||
| 1b90a72b11 | |||
| 566ae7323e | |||
| 70b1a63b3a | |||
| 47e227f6f4 | |||
| d65901cff8 | |||
| 27a642f528 | |||
| 45f568da08 | |||
| f0d8d99b98 | |||
| 92c1715fe3 | |||
| bc8d21c151 | |||
| 42a029f611 | |||
| 912770fdcc | |||
| 6eb9c2a19b | |||
| b87959ac29 | |||
| 85cf5b4a6b | |||
| 40c850bcd8 | |||
| ccf5086c01 | |||
| 1c20f8fc20 | |||
| 19f3c9c257 | |||
| b940b068cb | |||
| 7a4f06c4dc | |||
| 049cf4a210 | |||
| bcf39efeaf | |||
| 1933488ca6 | |||
| f75f85af3c | |||
| e1c9dc0d68 | |||
| 99267e6028 | |||
| 090da1941b | |||
| a8e60e4b16 | |||
| da1cf74d53 | |||
| 96f474d154 | |||
| 51fd006016 | |||
| 278f6963f2 | |||
| ab92f5a721 | |||
| 0754ad7b5c | |||
| e986485a6e | |||
| 5f8e3e8382 | |||
| c0df320151 | |||
| c4d0e2a78e | |||
| ba79abe837 | |||
| d9e0f483ae | |||
| f56a396dd7 | |||
| 32f0f63872 | |||
| 96985290b2 | |||
| 20d4efb38b | |||
| 28c85de2ec | |||
| afd9f36642 | |||
| c61e6974b2 | |||
| aee5cc8aec | |||
| 0d0375d41a | |||
| 6b1ca32e04 | |||
| 15e255d7a9 | |||
| dc02ad1d03 | |||
| e0426b90de | |||
| 5d0ca36d34 | |||
| 444c488d62 | |||
| 428f147cc9 | |||
| 9613b61bdd | |||
| 6724a7212e | |||
| a99b3608be | |||
| 37684330e4 | |||
| eadf934ab6 | |||
| 5bcff62323 | |||
| dfc3b0bd58 | |||
| 76b485c261 | |||
| 7e60284e8d | |||
| d221d51748 | |||
| 458a9bd375 | |||
| b0032d688e | |||
| 517dff6e45 | |||
| 3db699d79c | |||
| c409fdcc95 | |||
| 1f632b65c0 | |||
| dbae55a98b | |||
| cdc268f56b | |||
| aac3e97f75 | |||
| 3199dd8856 | |||
| c141daceb1 | |||
| f70081b749 | |||
| a74efa7bc4 | |||
| 3ffe7188a7 | |||
| 9205a57874 | |||
| 54e614a8c7 | |||
| 81e014a80e | |||
| 70af344759 | |||
| 161a99621b | |||
| 7515df45b9 | |||
| 0b3999421b | |||
| 5b9d929c37 | |||
| c9acebbea2 | |||
| d6bf79b461 | |||
| db73cb3334 | |||
| 7a5b42f91c | |||
| 672a557ab2 | |||
| d168589a92 | |||
| d7bf5c119d | |||
| e76512fabb | |||
| 709e679c5b | |||
| ea053b1991 | |||
| dd415a9800 | |||
| 2db1ba56bc | |||
| dd8eac2289 | |||
| 1fbcb5d6c9 | |||
| 48955d692b | |||
| 41bea46b55 | |||
| 3fe7aa8cec | |||
| 5dd9823835 | |||
| 6b1c68fb96 | |||
| 93cd0df8d4 | |||
| 60515ae16f | |||
| 40b80c3b31 | |||
| 365573ab24 | |||
| d64f4a976b | |||
| 8b1ab79a83 | |||
| 79f836c394 | |||
| 4d158e0dd9 | |||
| 9ec21286ea | |||
| 2abadcad2c | |||
| 9dbc2390f2 | |||
| c7b097b5ab | |||
| 6efd6745b2 | |||
| 0e9f9980ed | |||
| 0d810acdb9 | |||
| 127ab70dc3 | |||
| d290fd2b54 | |||
| b3b2a9845b | |||
| a7bd3169a9 | |||
| 9eca35a8de | |||
| a72c635c76 | |||
| fd21214d42 | |||
| 968e54072b | |||
| 5a01a6960c | |||
| 570dba904f | |||
| 253c49ff26 | |||
| 85647c9b64 | |||
| cc2df9a02d | |||
| 4801564390 | |||
| ee67a9c843 | |||
| 104bdd1be8 | |||
| 78a1e9d749 | |||
| 5d9bd0584e | |||
| cf5c27ae8f | |||
| 0149eff61e | |||
| 0cdc4609d7 | |||
| 4540476fa3 | |||
| e01bddf14d | |||
| 8ecf93edb0 | |||
| 53709f0ff6 | |||
| cf228827a5 | |||
| 81164dae38 | |||
| 58cd74bc09 | |||
| aa3eaa9148 | |||
| 473838677f | |||
| a08173ec79 | |||
| 509357e76e | |||
| e67dfbcda2 | |||
| d1f9453003 | |||
| c6ba8d7f31 | |||
| dde804b6b6 | |||
| 157f614a82 | |||
| 006986db1d | |||
| 0b5ca7812e | |||
| a5577f857b | |||
| 5723a9e18c | |||
| 6a2f481477 | |||
| 0e3e295ccc | |||
| e55c2de3e1 | |||
| 08d8b21d63 | |||
| 7c5cb18f24 | |||
| 3e5f796442 | |||
| 8e7fbf06a5 | |||
| 8d153acbf8 | |||
| 0caea91744 | |||
| 7fce2fe82f | |||
| 015625d1ae | |||
| 37777caf9d | |||
| 82532d1b74 | |||
| dd99f35d93 | |||
| 1f89fd4bd5 | |||
| 7589339330 | |||
| c0215c8be9 | |||
| 9aecd4ed8e | |||
| 5f9e270d0a | |||
| 45dd9be184 | |||
| 87b4aa7457 | |||
| ff9d969ec4 | |||
| d870f21297 | |||
| 21d2ecb839 | |||
| 20ab01ad10 | |||
| e55d906381 | |||
| 9af6f96e27 | |||
| d7a48523e4 | |||
| 80cef61475 | |||
| ed5f97902f | |||
| 17163c22dd | |||
| ed3700be00 | |||
| ce226fa65d | |||
| 8620aedba9 | |||
| 366e0c8a1f | |||
| d34f2de189 | |||
| d3da8b78fd | |||
| bf30898da2 | |||
| b08e993fb4 | |||
| 5de71216de | |||
| 64df05e367 | |||
| aac4a0dd4a | |||
| 7fdf73016b | |||
| a89be34576 | |||
| d474824b7c | |||
| f6aa26bd07 | |||
| 290b268384 | |||
| ceb396f716 | |||
| dab18336e4 | |||
| a742690b65 | |||
| d50b0547f8 | |||
| 4fd5bf4345 | |||
| 58e8ef4f87 | |||
| f45815cb11 | |||
| 25caf5fb83 | |||
| ba1f741dff | |||
| a51c309ea8 | |||
| 220e7b6012 | |||
| d9e5bc9e8d | |||
| 5ffae83d66 | |||
| fe47c9bb61 | |||
| 682ae66e36 | |||
| 8d904867f1 | |||
| 8278f53b88 | |||
| b21f70aa59 | |||
| c74684fe9b | |||
| 93a82af0e4 | |||
| 56f624a263 | |||
| 8dc92afa74 | |||
| b1fa7bb89a | |||
| 3e30cd94a4 | |||
| 63ae7e122d | |||
| 360cd5ca95 | |||
| 224bc2f599 | |||
| f5b37f68de | |||
| e2238174d2 | |||
| 624fd5f565 | |||
| 4da17a4418 | |||
| 90a8aa79c8 | |||
| a3890e753b | |||
| 38050bb624 | |||
| 566bdd1641 | |||
| 1c990397df | |||
| ea1e858a49 | |||
| c4e169cbb7 | |||
| d2df0b35ae | |||
| 5ddd01f56d | |||
| 7e178a38ce | |||
| 60619d511b | |||
| 64fff6761e | |||
| 5b1115e3f6 | |||
| 015913259b | |||
| 8868f236ee | |||
| be7ecff01c | |||
| b867538979 | |||
| ee7417071d | |||
| 1fb30d1b62 | |||
| 86243cb2e0 | |||
| 9291aaa884 | |||
| 23a32265ee | |||
| cd889b60b6 | |||
| 9246905acc | |||
| 3fcb70f0f6 | |||
| 6a4527b7ea | |||
| dfa53155bf | |||
| d08962634b | |||
| ace7bc60e5 | |||
| 56fd692e38 | |||
| 465ad140c3 | |||
| 311ffcb9e5 | |||
| 17d6c3b58c | |||
| 5f895c13ec | |||
| d952647565 | |||
| 88c94ee681 | |||
| 58efec801c | |||
| 25f6e55c3c | |||
| e63ebbaf20 | |||
| e8c5b548c7 | |||
| 9c6240914e | |||
| 853199e0da | |||
| 356cfb577d | |||
| 201fdf8318 | |||
| bb523cd760 | |||
| 6c8219cdd0 | |||
| 9bc5207b2c | |||
| e1bff6b703 | |||
| 53e0a39f71 | |||
| 497ba15f4b | |||
| 7f35a3f2e7 | |||
| db366a89c2 | |||
| 04a21e33b0 | |||
| 67e9d23a25 | |||
| 91ac17c9ed | |||
| 5f7f307995 | |||
| 1d19663f27 | |||
| 88d17a75dc | |||
| c2e8d38899 | |||
| 2b78dd16ab | |||
| c690b834a7 | |||
| a023d082b3 | |||
| b0745c8e05 | |||
| 48dac234c3 | |||
| 421ef0eb73 | |||
| 32d298443d | |||
| 06081e815c | |||
| 30a00a0773 | |||
| f009b23f2d | |||
| 3e5a58e54c | |||
| c53265955b | |||
| 39d7661435 | |||
| 041261b536 | |||
| 76fee1f9e1 | |||
| d87ed0e216 |
@@ -0,0 +1,18 @@
|
||||
# This file is for unifying the coding style for different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
# Tabs in JS unless otherwise specified
|
||||
[**.js]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -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,6 @@
|
||||
*.html eol=lf
|
||||
*.css eol=lf
|
||||
*.js eol=lf
|
||||
*.md eol=lf
|
||||
*.json eol=lf
|
||||
*.yml eol=lf
|
||||
@@ -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:
|
||||
@@ -9,11 +9,14 @@ lib-cov
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
.idea
|
||||
|
||||
pids
|
||||
logs
|
||||
results
|
||||
dist
|
||||
# test coverage files
|
||||
.coverage/
|
||||
|
||||
node_modules
|
||||
npm-debug.log
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"curly": true,
|
||||
"immed": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"sub": true,
|
||||
"boss": true,
|
||||
"eqnull": true,
|
||||
"quotmark": "single",
|
||||
"trailing": true,
|
||||
"undef": true,
|
||||
"browser": true,
|
||||
"jquery": true,
|
||||
"globals": {
|
||||
"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*
|
||||
+19
-11
@@ -1,11 +1,19 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.8"
|
||||
|
||||
before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- npm install --quiet -g grunt-cli karma
|
||||
- npm install
|
||||
|
||||
script: grunt
|
||||
language: node_js
|
||||
node_js:
|
||||
- "5.9"
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
|
||||
before_install:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- npm install --quiet -g karma
|
||||
|
||||
script: grunt
|
||||
sudo: false
|
||||
|
||||
+2093
-93
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/)
|
||||
+45
-12
@@ -1,12 +1,45 @@
|
||||
We are always looking for the quality contributions and will be happy to accept your Pull Requests as long as those adhere to some basic rules:
|
||||
|
||||
* Please make sure that your contribution fits well in the project's context:
|
||||
* we are aiming at rebuilding boostrap directives in pure AngularJS, without any dependencies on any external JavaScript library;
|
||||
* the only dependency should be boostrap CSS and its markup structure;
|
||||
* directives should be html-agnostic as much as possible which in practice means:
|
||||
* templates should be referred to using the `templateUrl` property
|
||||
* it should be easy to change a default template to a custom one
|
||||
* directives shouldn't manipulate DOM structure directly (when possible)
|
||||
* 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 ned 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
|
||||
## Got a question or problem?
|
||||
|
||||
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 requests for general support and redirecting people to StackOverflow.
|
||||
|
||||
## You think you've found a bug?
|
||||
|
||||
Oh, we are ashamed and want to fix it asap! But before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will systematically ask you to provide a _minimal_ reproduce scenario using http://plnkr.co/. Having a live reproduce scenario gives us wealth of important information without going back & forth to you with additional questions like:
|
||||
* 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
|
||||
|
||||
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 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.
|
||||
|
||||
|
||||
## You want to contribute some code?
|
||||
|
||||
We are always looking for the quality contributions and will be happy to accept your Pull Requests as long as those adhere to some basic rules:
|
||||
|
||||
* Please make sure that your contribution fits well in the project's context:
|
||||
* we are aiming at rebuilding bootstrap directives in pure AngularJS, without any dependencies on any external JavaScript library;
|
||||
* the only dependency should be bootstrap CSS and its markup structure;
|
||||
* directives should be html-agnostic as much as possible which in practice means:
|
||||
* templates should be referred to using the `templateUrl` property
|
||||
* it should be easy to change a default template to a custom one
|
||||
* directives shouldn't manipulate DOM structure directly (when possible)
|
||||
* 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)
|
||||
|
||||
+306
-167
@@ -1,18 +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');
|
||||
require('load-grunt-tasks')(grunt);
|
||||
|
||||
// Project configuration.
|
||||
grunt.util.linefeed = '\n';
|
||||
|
||||
grunt.initConfig({
|
||||
ngversion: '1.0.5',
|
||||
bsversion: '2.3.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',
|
||||
@@ -21,32 +19,46 @@ module.exports = function(grunt) {
|
||||
meta: {
|
||||
modules: 'angular.module("ui.bootstrap", [<%= srcModules %>]);',
|
||||
tplmodules: 'angular.module("ui.bootstrap.tpls", [<%= tplModules %>]);',
|
||||
all: 'angular.module("ui.bootstrap", ["ui.bootstrap.tpls", <%= srcModules %>]);'
|
||||
all: 'angular.module("ui.bootstrap", ["ui.bootstrap.tpls", <%= srcModules %>]);',
|
||||
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')
|
||||
},
|
||||
watch: {
|
||||
delta: {
|
||||
docs: {
|
||||
files: ['misc/demo/index.html'],
|
||||
tasks: ['after-test']
|
||||
},
|
||||
html: {
|
||||
files: ['template/**/*.html'],
|
||||
tasks: ['html2js']
|
||||
tasks: ['html2js', 'karma:watch:run']
|
||||
},
|
||||
js: {
|
||||
//nospawn makes the tests start faster
|
||||
nospawn: true,
|
||||
files: ['src/**/*.js'],
|
||||
//we don't need to jshint here, it slows down everything else
|
||||
tasks: ['test-run']
|
||||
files: ['src/**/*.js', '!src/**/index.js'],
|
||||
tasks: ['karma:watch:run']
|
||||
}
|
||||
},
|
||||
concat: {
|
||||
dist: {
|
||||
options: {
|
||||
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.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'
|
||||
@@ -60,28 +72,31 @@ module.exports = function(grunt) {
|
||||
},
|
||||
files: [{
|
||||
expand: true,
|
||||
src: ["**/*.html"],
|
||||
cwd: "misc/demo/",
|
||||
dest: "dist/"
|
||||
src: ['**/*.html'],
|
||||
cwd: 'misc/demo/',
|
||||
dest: 'dist/'
|
||||
}]
|
||||
},
|
||||
demoassets: {
|
||||
files: [{
|
||||
expand: true,
|
||||
//Don't re-copy html files, we process those
|
||||
src: ["**/**/*", "!**/*.html"],
|
||||
cwd: "misc/demo",
|
||||
dest: "dist/"
|
||||
src: ['**/**/*', '!**/*.html'],
|
||||
cwd: 'misc/demo',
|
||||
dest: 'dist/'
|
||||
}]
|
||||
}
|
||||
},
|
||||
uglify: {
|
||||
options: {
|
||||
banner: '<%= meta.banner %>'
|
||||
},
|
||||
dist:{
|
||||
src:['<%= dist %>/<%= filename %>-<%= pkg.version %>.js'],
|
||||
src:['<%= concat.dist.dest %>'],
|
||||
dest:'<%= dist %>/<%= filename %>-<%= pkg.version %>.min.js'
|
||||
},
|
||||
dist_tpls:{
|
||||
src:['<%= dist %>/<%= filename %>-tpls-<%= pkg.version %>.js'],
|
||||
src:['<%= concat.dist_tpls.dest %>'],
|
||||
dest:'<%= dist %>/<%= filename %>-tpls-<%= pkg.version %>.min.js'
|
||||
}
|
||||
},
|
||||
@@ -89,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,
|
||||
@@ -98,30 +116,94 @@ module.exports = function(grunt) {
|
||||
}]
|
||||
}
|
||||
},
|
||||
jshint: {
|
||||
files: ['Gruntfile.js','src/**/*.js'],
|
||||
eslint: {
|
||||
files: ['Gruntfile.js','src/**/*.js']
|
||||
},
|
||||
karma: {
|
||||
options: {
|
||||
curly: true,
|
||||
immed: true,
|
||||
newcap: true,
|
||||
noarg: true,
|
||||
sub: true,
|
||||
boss: true,
|
||||
eqnull: true,
|
||||
globals: {
|
||||
angular: true
|
||||
}
|
||||
configFile: 'karma.conf.js'
|
||||
},
|
||||
watch: {
|
||||
background: true
|
||||
},
|
||||
continuous: {
|
||||
singleRun: true
|
||||
},
|
||||
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']
|
||||
},
|
||||
coverage: {
|
||||
preprocessors: {
|
||||
'src/*/*.js': 'coverage'
|
||||
},
|
||||
reporters: ['progress', 'coverage']
|
||||
}
|
||||
},
|
||||
conventionalChangelog: {
|
||||
options: {
|
||||
changelogOpts: {
|
||||
preset: 'angular'
|
||||
},
|
||||
templateFile: 'misc/changelog.tpl.md'
|
||||
},
|
||||
release: {
|
||||
src: 'CHANGELOG.md'
|
||||
}
|
||||
},
|
||||
shell: {
|
||||
//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 conventionalChangelog'
|
||||
],
|
||||
'release-complete': [
|
||||
'git commit CHANGELOG.md package.json -m "chore(release): v%version%"',
|
||||
'git tag %version%'
|
||||
],
|
||||
'release-start': [
|
||||
'grunt version:minor:"SNAPSHOT"',
|
||||
'git commit package.json -m "chore(release): Starting v%version%"'
|
||||
]
|
||||
},
|
||||
'ddescribe-iit': {
|
||||
files: [
|
||||
'src/**/*.spec.js'
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
//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', ['jshint', 'html2js']);
|
||||
//register before and after test tasks so we've don't have to change cli
|
||||
//options on the google's CI server
|
||||
grunt.registerTask('before-test', ['enforce', 'ddescribe-iit', 'eslint', 'html2js']);
|
||||
grunt.registerTask('after-test', ['build', 'copy']);
|
||||
|
||||
//Rename our watch task to 'delta', then make actual 'watch'
|
||||
//task build things, then start test server
|
||||
grunt.renameTask('watch', 'delta');
|
||||
grunt.registerTask('watch', ['before-test', 'after-test', 'karma:watch', 'delta']);
|
||||
|
||||
// Default task.
|
||||
grunt.registerTask('default', ['before-test', 'test', 'after-test']);
|
||||
|
||||
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');
|
||||
}
|
||||
});
|
||||
|
||||
//Common ui.bootstrap module containing all modules for src and templates
|
||||
//findModule: Adds a given module to config
|
||||
var foundModules = {};
|
||||
@@ -140,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")
|
||||
.map(grunt.file.read).join("\n"),
|
||||
html: grunt.file.expand("src/"+name+"/docs/*.html")
|
||||
.map(grunt.file.read).join("\n")
|
||||
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`)
|
||||
.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,
|
||||
@@ -196,6 +293,8 @@ module.exports = function(grunt) {
|
||||
});
|
||||
|
||||
grunt.registerTask('build', 'Create bootstrap build files', function() {
|
||||
var _ = grunt.util._;
|
||||
|
||||
//If arguments define what modules to build, build those. Else, everything
|
||||
if (this.args.length) {
|
||||
this.args.forEach(findModule);
|
||||
@@ -203,28 +302,41 @@ module.exports = function(grunt) {
|
||||
} else {
|
||||
grunt.file.expand({
|
||||
filter: 'isDirectory', cwd: '.'
|
||||
}, 'src/*').forEach(function(dir) {
|
||||
}, 'src/*').forEach((dir) => {
|
||||
findModule(dir.split('/')[1]);
|
||||
});
|
||||
}
|
||||
|
||||
//Pluck will take an array of objects, and map the given key to a new array
|
||||
//@example: expect( pluck([{a:1},{a:2}], 'a') ).toBe([1,2])
|
||||
function pluck(array, key) {
|
||||
return array.map(function(obj) {
|
||||
return obj[key];
|
||||
});
|
||||
}
|
||||
|
||||
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('demoModules', modules.filter(function(module) {
|
||||
return module.docs.md && module.docs.js && module.docs.html;
|
||||
}));
|
||||
grunt.config('srcModules', _.pluck(modules, 'moduleName'));
|
||||
grunt.config('tplModules', _.pluck(modules, 'tplModules').filter((tpls) => tpls.length > 0));
|
||||
grunt.config('demoModules', modules
|
||||
.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 srcFiles = pluck(modules, 'srcFiles');
|
||||
var tpljsFiles = pluck(modules, 'tpljsFiles');
|
||||
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
|
||||
grunt.config('concat.dist.src', grunt.config('concat.dist.src')
|
||||
.concat(srcFiles));
|
||||
@@ -232,114 +344,141 @@ 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 single-run server', function() {
|
||||
var options = ['--single-run', '--no-auto-watch', '--log-level=warn']
|
||||
.concat(this.args) //Let user augment test args with command line args
|
||||
.concat(process.env.TRAVIS ? '--browsers=Firefox' : '');
|
||||
runKarma('start', options);
|
||||
});
|
||||
|
||||
grunt.registerTask('server', 'start karma server', function() {
|
||||
var options = ['--no-single-run', '--no-auto-watch'].concat(this.args);
|
||||
runKarma('start', options);
|
||||
});
|
||||
|
||||
grunt.registerTask('test-run', 'run tests against continuous karma server', function() {
|
||||
var options = ['--single-run', '--no-auto-watch'].concat(this.args);
|
||||
runKarma('run', options);
|
||||
});
|
||||
|
||||
grunt.registerTask('test-watch', 'start karma server, watch & execute tests', function() {
|
||||
var options = ['--no-single-run', '--auto-watch'].concat(this.args);
|
||||
runKarma('start', options);
|
||||
});
|
||||
|
||||
//changelog generation
|
||||
grunt.registerTask('changelog', 'generates changelog markdown from git commits', function () {
|
||||
|
||||
var changeFrom = this.args[0], changeTo = this.args[1] || 'HEAD';
|
||||
|
||||
var done = grunt.task.current.async();
|
||||
var child = grunt.util.spawn({
|
||||
cmd:process.platform === 'win32' ? 'git.cmd' : 'git',
|
||||
args: [
|
||||
'log',
|
||||
changeFrom + '..' + changeTo,
|
||||
'--format=%H%n%s%n%b%n==END=='
|
||||
]
|
||||
}, function (err, result, code) {
|
||||
|
||||
var changelog = {};
|
||||
function addChange(changeType, component, change) {
|
||||
if (!changelog[changeType]) {
|
||||
changelog[changeType] = {};
|
||||
}
|
||||
if (!changelog[changeType][component]) {
|
||||
changelog[changeType][component] = [];
|
||||
}
|
||||
changelog[changeType][component].push(change);
|
||||
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')) {
|
||||
var karmaOptions = grunt.config.get('karma.options'),
|
||||
coverageOpts = grunt.config.get('karma.coverage');
|
||||
grunt.util._.extend(karmaOptions, coverageOpts);
|
||||
grunt.config.set('karma.options', karmaOptions);
|
||||
}
|
||||
grunt.task.run(this.args.length ? 'karma:jenkins' : 'karma:continuous');
|
||||
}
|
||||
});
|
||||
|
||||
var COMMIT_MSG_REGEXP = /^(chore|demo|docs|feat|fix|refactor|style|test)\((.+)\):? (.+)$/;
|
||||
var gitlog = result.toString().split('\n==END==\n').reverse();
|
||||
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.');
|
||||
});
|
||||
|
||||
if (code) {
|
||||
grunt.log.error(err);
|
||||
done(false);
|
||||
} else {
|
||||
grunt.registerTask('makeRawFilesJs', function() {
|
||||
var _ = grunt.util._;
|
||||
var jsFilename = 'dist/assets/raw-files.json';
|
||||
var genRawFilesJs = require('./misc/raw-files-generator');
|
||||
|
||||
gitlog.forEach(function (logItem) {
|
||||
var lines = logItem.split('\n');
|
||||
var sha1 = lines.shift().substr(0,8); //Only first 7 of sha1
|
||||
var subject = lines.shift();
|
||||
genRawFilesJs(grunt, jsFilename, _.flatten(grunt.config('concat.dist_tpls.src')),
|
||||
grunt.config('meta.banner'), grunt.config('meta.cssFileBanner'));
|
||||
});
|
||||
|
||||
var msgMatches = subject.match(COMMIT_MSG_REGEXP);
|
||||
var changeType = msgMatches[1];
|
||||
var component = msgMatches[2];
|
||||
var componentMsg = msgMatches[3];
|
||||
grunt.registerTask('makeVersionsMappingFile', function() {
|
||||
var done = this.async();
|
||||
|
||||
var breaking = logItem.match(/BREAKING CHANGE:([\s\S]*)/);
|
||||
if (breaking) {
|
||||
addChange('breaking', component, {
|
||||
sha1: sha1,
|
||||
msg: breaking[1]
|
||||
});
|
||||
}
|
||||
addChange(changeType, component, {sha1:sha1, msg:componentMsg});
|
||||
});
|
||||
var exec = require('child_process').exec;
|
||||
|
||||
console.log(grunt.template.process(grunt.file.read('misc/changelog.tpl.md'), {data: {
|
||||
changelog: changelog,
|
||||
today: grunt.template.today('yyyy-mm-dd'),
|
||||
version : grunt.config('pkg.version')
|
||||
}}));
|
||||
var versionsMappingFile = 'dist/versions-mapping.json';
|
||||
|
||||
done();
|
||||
}
|
||||
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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Karma configuration
|
||||
function runKarma(command, options) {
|
||||
var karmaCmd = process.platform === 'win32' ? 'karma.cmd' : 'karma';
|
||||
var args = [command].concat(options);
|
||||
var done = grunt.task.current.async();
|
||||
var child = grunt.util.spawn({
|
||||
cmd: karmaCmd,
|
||||
args: args
|
||||
}, function(err, result, code) {
|
||||
if (code) {
|
||||
done(false);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
child.stdout.pipe(process.stdout);
|
||||
child.stderr.pipe(process.stderr);
|
||||
/**
|
||||
* 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+)*([\'|\"])/;
|
||||
var contents = grunt.file.read(file);
|
||||
var version;
|
||||
contents = contents.replace(VERSION_REGEX, function(match, left, center) {
|
||||
version = center;
|
||||
if (type) {
|
||||
version = require('semver').inc(version, type);
|
||||
}
|
||||
//semver.inc strips our suffix if it existed
|
||||
if (suffix) {
|
||||
version += '-' + suffix;
|
||||
}
|
||||
return left + version + '"';
|
||||
});
|
||||
grunt.log.ok('Version set to ' + version.cyan);
|
||||
grunt.file.write(file, contents);
|
||||
return version;
|
||||
}
|
||||
|
||||
grunt.registerTask('version', 'Set version. If no arguments, it just takes off suffix', function() {
|
||||
setVersion(this.args[0], this.args[1]);
|
||||
});
|
||||
|
||||
grunt.registerMultiTask('shell', 'run shell commands', function() {
|
||||
var self = this;
|
||||
var sh = require('shelljs');
|
||||
self.data.forEach(function(cmd) {
|
||||
cmd = cmd.replace('%version%', grunt.file.readJSON('package.json').version);
|
||||
grunt.log.ok(cmd);
|
||||
var result = sh.exec(cmd,{silent:true});
|
||||
if (result.code !== 0) {
|
||||
grunt.fatal(result.output);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return grunt;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2012-2013 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,80 +1,188 @@
|
||||
# bootstrap - [AngularJS](http://angularjs.org/) directives specific to [twitter bootstrap](http://twitter.github.io/bootstrap/)
|
||||
### 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/)
|
||||
|
||||
[](http://travis-ci.org/angular-ui/bootstrap)
|
||||
### 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)
|
||||
|
||||
## Demo
|
||||
|
||||
Do you want to see directives in action? Visit http://angular-ui.github.io/bootstrap/!
|
||||
# Demo
|
||||
|
||||
## Installation
|
||||
Do you want to see directives in action? Visit https://angular-ui.github.io/bootstrap/!
|
||||
|
||||
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:
|
||||
# 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
|
||||
|
||||
## Project philosophy
|
||||
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:
|
||||
|
||||
### Native, lightweight directives
|
||||
```js
|
||||
import accordion from 'angular-ui-bootstrap/src/accordion';
|
||||
|
||||
We are aiming at providing a set of AngularJS directives based on Twitter 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 orginal JavaScript code size and better integrated into the AngularJS ecosystem.
|
||||
angular.module('myModule', [accordion]);
|
||||
```
|
||||
|
||||
### Customizability
|
||||
You can import all the pieces you need in the same way:
|
||||
|
||||
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-Boostrap version of the templates!
|
||||
```js
|
||||
import accordion from 'angular-ui-bootstrap/src/accordion';
|
||||
import datepicker from 'angular-ui-bootstrap/src/datepicker';
|
||||
|
||||
### Take what you need and not more
|
||||
angular.module('myModule', [accordion, datepicker]);
|
||||
```
|
||||
|
||||
Each directive has its own AngularJS module without any dependencies on other modules or third-pary 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.
|
||||
This will load all the dependencies (if any) and also the templates (if any).
|
||||
|
||||
### Quality and stability
|
||||
Be sure to have a loader able to process `css` files like `css-loader`.
|
||||
|
||||
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**!
|
||||
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
|
||||
|
||||
## Contributing to the project
|
||||
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
|
||||
|
||||
Directives from this repository are automatically tested with the following browsers:
|
||||
* Chrome (stable and canary channel)
|
||||
* Firefox
|
||||
* IE 9 and 10
|
||||
* Opera
|
||||
* Safari
|
||||
|
||||
Modern mobile browsers should work without problems.
|
||||
|
||||
## 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.
|
||||
|
||||
**Please do not create new issues in this repository to ask questions about using UI Bootstrap**
|
||||
|
||||
## 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`
|
||||
* Instal 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
|
||||
|
||||
Check the Grunt build file for other tasks that are defined for this project
|
||||
|
||||
#### TDD
|
||||
* Start karma server: `grunt server`
|
||||
* Run test: `grunt test-run`
|
||||
|
||||
### Release
|
||||
* Bump up version number in `package.json`
|
||||
* Commit the version change with the following message: `chore(release): [versio 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): [versio 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 [versio 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.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
require('./dist/ui-bootstrap-tpls');
|
||||
|
||||
module.exports = 'ui.bootstrap';
|
||||
+69
-43
@@ -1,55 +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 = [
|
||||
];
|
||||
|
||||
// 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
|
||||
reporter = 'progress';
|
||||
|
||||
// web server port
|
||||
port = 9018;
|
||||
// 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'
|
||||
],
|
||||
|
||||
// cli runner port
|
||||
runnerPort = 9100;
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors = true;
|
||||
// list of files to exclude
|
||||
exclude: [
|
||||
'src/**/index.js',
|
||||
'src/**/index-nocss.js',
|
||||
'src/**/docs/*'
|
||||
],
|
||||
|
||||
// level of logging
|
||||
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
|
||||
logLevel = LOG_INFO;
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch = false;
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
preprocessors: {
|
||||
'src/*/{*.js,!(test)/**/*.js}': ['coverage']
|
||||
},
|
||||
|
||||
// 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
|
||||
});
|
||||
};
|
||||
|
||||
+16
-28
@@ -1,28 +1,16 @@
|
||||
# <%= version%> (<%= today%>)
|
||||
|
||||
## Features
|
||||
|
||||
<% _(changelog.feat).forEach(function(changes, component) { %>
|
||||
- **<%= component%>:**
|
||||
<% changes.forEach(function(change) { %>
|
||||
- <%= change.msg%> ([<%= change.sha1%>](https://github.com/angular-ui/bootstrap/commit/<%= change.sha1%>))
|
||||
<% }) %>
|
||||
<% }) %>
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
<% _(changelog.fix).forEach(function(changes, component) { %>
|
||||
- **<%= component%>:**
|
||||
<% changes.forEach(function(change) { %>
|
||||
- <%= change.msg%> ([<%= change.sha1%>](https://github.com/angular-ui/bootstrap/commit/<%= change.sha1%>))
|
||||
<% }) %>
|
||||
<% }) %>
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
<% _(changelog.breaking).forEach(function(changes, component) { %>
|
||||
- **<%= component%>:**
|
||||
<% changes.forEach(function(change) { %>
|
||||
<%= change.msg%>
|
||||
<% }) %>
|
||||
<% }) %>
|
||||
# <%= version%> (<%= today%>)
|
||||
<% if (_(changelog.feat).size() > 0) { %>
|
||||
## Features
|
||||
<% _(changelog.feat).keys().sort().forEach(function(scope) { %>
|
||||
- **<%= scope%>:** <% changelog.feat[scope].forEach(function(change) { %>
|
||||
- <%= 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) %>)<% }); %><% }); %> <% } %>
|
||||
<% if (_(changelog.breaking).size() > 0) { %>
|
||||
## Breaking Changes
|
||||
<% _(changelog.breaking).keys().sort().forEach(function(scope) { %>
|
||||
- **<%= scope%>:** <% changelog.breaking[scope].forEach(function(change) { %>
|
||||
<%= change.msg%><% }); %><% }); %> <% } %>
|
||||
|
||||
+308
-22
@@ -1,31 +1,317 @@
|
||||
/* 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);
|
||||
});
|
||||
}
|
||||
}]).factory('buildFilesService', function ($http, $q) {
|
||||
|
||||
angular.module('bootstrapDemoApp', ['ui.directives', 'ui.bootstrap', 'plunker']);
|
||||
var moduleMap;
|
||||
var rawFiles;
|
||||
|
||||
function MainCtrl($scope, $http, orderByFilter) {
|
||||
var url = "http://50.116.42.77:3001";
|
||||
$scope.selectedModules = [];
|
||||
//iFrame for downloading
|
||||
var $iframe = $("<iframe>").css('display','none').appendTo(document.body);
|
||||
|
||||
$scope.showBuildModal = function() {
|
||||
$scope.buildModalShown = true;
|
||||
//Load modules if they aren't loaded yet
|
||||
if (!$scope.modules) {
|
||||
$http.get(url + "/api/bootstrap").then(function(response) {
|
||||
$scope.modules = response.data.modules;
|
||||
}, function() {
|
||||
$scope.buildGetErrorText = "Error retrieving build files from server.";
|
||||
return {
|
||||
getModuleMap: getModuleMap,
|
||||
getRawFiles: getRawFiles,
|
||||
get: function () {
|
||||
return $q.all({
|
||||
moduleMap: getModuleMap(),
|
||||
rawFiles: getRawFiles()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.downloadBuild = function() {
|
||||
var downloadUrl = url + "/api/bootstrap/download?";
|
||||
angular.forEach($scope.selectedModules, function(module) {
|
||||
downloadUrl += "modules=" + module + "&";
|
||||
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;
|
||||
});
|
||||
|
||||
$scope.showBuildModal = function() {
|
||||
var modalInstance = $uibModal.open({
|
||||
templateUrl: 'buildModal.html',
|
||||
controller: 'SelectModulesCtrl',
|
||||
resolve: {
|
||||
modules: function(buildFilesService) {
|
||||
return buildFilesService.getModuleMap()
|
||||
.then(function (moduleMap) {
|
||||
return Object.keys(moduleMap);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showDownloadModal = function() {
|
||||
var modalInstance = $uibModal.open({
|
||||
templateUrl: 'downloadModal.html',
|
||||
controller: 'DownloadCtrl'
|
||||
});
|
||||
$iframe.attr('src','');
|
||||
$iframe.attr('src', downloadUrl);
|
||||
$scope.buildModalShown = false;
|
||||
};
|
||||
}
|
||||
|
||||
function SelectModulesCtrl($scope, $uibModalInstance, modules, buildFilesService) {
|
||||
$scope.selectedModules = [];
|
||||
$scope.modules = modules;
|
||||
|
||||
$scope.selectedChanged = function(module, selected) {
|
||||
if (selected) {
|
||||
$scope.selectedModules.push(module);
|
||||
} else {
|
||||
$scope.selectedModules.splice($scope.selectedModules.indexOf(module), 1);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.downloadBuild = function () {
|
||||
$uibModalInstance.close($scope.selectedModules);
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$uibModalInstance.dismiss();
|
||||
};
|
||||
|
||||
$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();
|
||||
});
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
$scope.download = function (version) {
|
||||
var options = $scope.options;
|
||||
|
||||
var downloadUrl = ['ui-bootstrap-'];
|
||||
if (options.tpls) {
|
||||
downloadUrl.push('tpls-');
|
||||
}
|
||||
downloadUrl.push(version);
|
||||
if (options.minified) {
|
||||
downloadUrl.push('.min');
|
||||
}
|
||||
downloadUrl.push('.js');
|
||||
|
||||
return downloadUrl.join('');
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$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);
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
+323
-92
@@ -1,92 +1,323 @@
|
||||
body {
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 1s ease;
|
||||
-moz-transition: opacity 1s ease;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
.ng-cloak {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
section {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.page-header h1 > small > a {
|
||||
color: #999;
|
||||
}
|
||||
.page-header h1 > small > a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 30px 0;
|
||||
margin-top: 70px;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.hero-unit {
|
||||
position: relative;
|
||||
padding: 40px 0;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,.4), 0 0 30px rgba(0,0,0,.075);
|
||||
background: #020031;
|
||||
background: -moz-linear-gradient(45deg, #020031 0%, #6d3353 100%);
|
||||
background: -webkit-gradient(linear, left bottom, right top, color-stop(0%,#020031), color-stop(100%,#6d3353));
|
||||
background: -webkit-linear-gradient(45deg, #020031 0%,#6d3353 100%);
|
||||
background: -o-linear-gradient(45deg, #020031 0%,#6d3353 100%);
|
||||
background: -ms-linear-gradient(45deg, #020031 0%,#6d3353 100%);
|
||||
background: linear-gradient(45deg, #020031 0%,#6d3353 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#020031', endColorstr='#6d3353',GradientType=1 );
|
||||
-webkit-box-shadow: inset 0 3px 7px rgba(0,0,0,.2), inset 0 -3px 7px rgba(0,0,0,.2);
|
||||
-moz-box-shadow: inset 0 3px 7px rgba(0,0,0,.2), inset 0 -3px 7px rgba(0,0,0,.2);
|
||||
box-shadow: inset 0 3px 7px rgba(0,0,0,.2), inset 0 -3px 7px rgba(0,0,0,.2);
|
||||
border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
-o-border-radius: 0;
|
||||
}
|
||||
.hero-unit .btn, .pagination-centered .btn {
|
||||
float: none;
|
||||
font-weight: normal;
|
||||
}
|
||||
.hero-unit p {
|
||||
margin: 1em 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 {
|
||||
margin-left: 0;
|
||||
margin-bottom: 0;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.bs-docs-social-buttons li {
|
||||
display: inline-block;
|
||||
padding: 5px 8px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.icon-github {
|
||||
background: no-repeat url('github-16px.png');
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* Not enough room on mobile for markup tab, js tab, and plunk btn.
|
||||
And no one cares about plunk button on a phone anyway */
|
||||
@media only screen and (max-device-width: 480px) {
|
||||
#plunk-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
body {
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 1s ease;
|
||||
-moz-transition: opacity 1s ease;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
.ng-cloak {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.ng-invalid {
|
||||
border: 1px solid red !important;
|
||||
}
|
||||
|
||||
section {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.page-header h1 > small > a {
|
||||
color: #999;
|
||||
}
|
||||
.page-header h1 > small > a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 30px 0;
|
||||
margin-top: 70px;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.bs-social {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
|
||||
.bs-social {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.nav, .pagination, .carousel, .panel-title a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bs-social-buttons {
|
||||
display: inline-block;
|
||||
margin-bottom: 0;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.bs-social-buttons li {
|
||||
display: inline-block;
|
||||
padding: 5px 8px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
|
||||
.visible-xs.collapse.in {
|
||||
display: block!important;
|
||||
}
|
||||
.visible-xs.collapse {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.navbar-fixed-top .collapse {
|
||||
border-top: 1px solid #e7e7e7;
|
||||
margin-left: -15px;
|
||||
margin-right: -15px;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.show-grid {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Container
|
||||
*
|
||||
* Tweak to width of container.
|
||||
*/
|
||||
|
||||
/*@media (min-width: 1200px) {
|
||||
.container{
|
||||
max-width: 970px;
|
||||
}
|
||||
}*/
|
||||
|
||||
/*
|
||||
* Tabs
|
||||
*
|
||||
* Tweaks to the Tabs.
|
||||
*/
|
||||
|
||||
.code .nav-tabs {
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.code pre, .code code {
|
||||
border-top: none;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.code .nav-tabs>li.active>a, .code .nav-tabs>li.active>a:hover, .code .nav-tabs>li.active>a:focus {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #ccc;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
/*
|
||||
* Button Inverse
|
||||
*
|
||||
* Buttons in the masthead.
|
||||
*/
|
||||
|
||||
.btn-outline-inverse {
|
||||
color: #fff;
|
||||
background-color: transparent;
|
||||
border-color: #cdbfe3;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
|
||||
.btn-outline-inverse {
|
||||
width: auto;
|
||||
margin: 20px 5px 20px 0;
|
||||
padding: 18px 24px;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.btn-outline-inverse:hover, .btn-outline-inverse:focus, .btn-outline-inverse:active {
|
||||
color: #563d7c;
|
||||
text-shadow: none;
|
||||
background-color: #fff;
|
||||
border-color: #fff;
|
||||
}
|
||||
|
||||
|
||||
/* Page headers */
|
||||
.bs-header {
|
||||
padding: 30px 15px 40px; /* side padding builds on .container 15px, so 30px */
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
text-shadow: 0 1px 0 rgba(0,0,0,.15);
|
||||
color: #cdbfe3;
|
||||
background-color: #563d7c;
|
||||
background-image: url(header.png);
|
||||
}
|
||||
.bs-header a {
|
||||
color: #fff;
|
||||
font-weight: normal;
|
||||
}
|
||||
.bs-header h1 {
|
||||
color: #fff;
|
||||
}
|
||||
.bs-header p {
|
||||
font-weight: 200;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.bs-header .container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.bs-header {
|
||||
font-size: 30px;
|
||||
text-align: left;
|
||||
}
|
||||
.bs-header h1 {
|
||||
font-size: 100px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.bs-header p {
|
||||
margin-right: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-inner {
|
||||
-webkit-box-shadow: 0 3px 3px rgba(0,0,0,0.175);
|
||||
box-shadow: 0 3px 3px rgba(0,0,0,0.175);
|
||||
}
|
||||
|
||||
/*
|
||||
* Side navigation
|
||||
*
|
||||
* Scrollspy and affixed enhanced navigation to highlight sections and secondary
|
||||
* sections of docs content.
|
||||
*/
|
||||
|
||||
/* By default it's not affixed in mobile views, so undo that */
|
||||
.bs-sidebar.affix {
|
||||
position: static;
|
||||
}
|
||||
|
||||
/* First level of nav */
|
||||
.bs-sidenav {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
background-color: #f7f5fa;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* All levels of nav */
|
||||
.bs-sidebar .nav > li > a {
|
||||
display: block;
|
||||
color: #716b7a;
|
||||
padding: 5px 20px;
|
||||
}
|
||||
.bs-sidebar .nav > li > a:hover,
|
||||
.bs-sidebar .nav > li > a:focus {
|
||||
text-decoration: none;
|
||||
background-color: #e5e3e9;
|
||||
border-right: 1px solid #dbd8e0;
|
||||
}
|
||||
.bs-sidebar .nav > .active > a,
|
||||
.bs-sidebar .nav > .active:hover > a,
|
||||
.bs-sidebar .nav > .active:focus > a {
|
||||
font-weight: bold;
|
||||
color: #563d7c;
|
||||
background-color: transparent;
|
||||
border-right: 1px solid #563d7c;
|
||||
}
|
||||
|
||||
/* Nav: second level (shown on .active) */
|
||||
.bs-sidebar .nav .nav {
|
||||
display: none; /* Hide by default, but at >768px, show it */
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.bs-sidebar .nav .nav > li > a {
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
padding-left: 30px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
/* Show and affix the side nav when space allows it */
|
||||
@media (min-width: 992px) {
|
||||
.bs-sidebar .nav > .active > ul {
|
||||
display: block;
|
||||
}
|
||||
/* Widen the fixed sidebar */
|
||||
.bs-sidebar.affix,
|
||||
.bs-sidebar.affix-bottom {
|
||||
width: 213px;
|
||||
}
|
||||
.bs-sidebar.affix {
|
||||
position: fixed; /* Undo the static from mobile first approach */
|
||||
top: 80px;
|
||||
}
|
||||
.bs-sidebar.affix-bottom {
|
||||
position: absolute; /* Undo the static from mobile first approach */
|
||||
}
|
||||
.bs-sidebar.affix-bottom .bs-sidenav,
|
||||
.bs-sidebar.affix .bs-sidenav {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
/* Widen the fixed sidebar again */
|
||||
.bs-sidebar.affix-bottom,
|
||||
.bs-sidebar.affix {
|
||||
width: 263px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Not enough room on mobile for markup tab, js tab, and plunk btn.
|
||||
And no one cares about plunk button on a phone anyway */
|
||||
@media only screen and (max-device-width: 480px) {
|
||||
#plunk-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-nav .dropdown .navbar-brand {
|
||||
max-width: 100%;
|
||||
margin-right: inherit;
|
||||
margin-left: inherit;
|
||||
}
|
||||
|
||||
.header-placeholder {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
|
||||
.dropdown.open > .navbar-brand + .dropdown-menu {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.header-placeholder {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.navbar-nav .dropdown .navbar-brand {
|
||||
max-width: 200px;
|
||||
margin-right: 5px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
+60
-58
@@ -1,58 +1,60 @@
|
||||
angular.module('plunker', [])
|
||||
|
||||
.factory('plunkGenerator', function ($document) {
|
||||
|
||||
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 addField = function (name, value) {
|
||||
var input = angular.element('<input type="hidden" name="' + name + '">');
|
||||
input.attr('value', value);
|
||||
form.append(input);
|
||||
};
|
||||
|
||||
var indexContent = function (content, version) {
|
||||
return '<!doctype html>\n' +
|
||||
'<html ng-app="plunker">\n' +
|
||||
' <head>\n' +
|
||||
' <script src="http://ajax.googleapis.com/ajax/libs/angularjs/'+ngVersion+'/angular.js"></script>\n' +
|
||||
' <script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-'+version+'.js"></script>\n' +
|
||||
' <script src="example.js"></script>\n' +
|
||||
' <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/'+bsVersion+'/css/bootstrap-combined.min.css" rel="stylesheet">\n' +
|
||||
' </head>\n' +
|
||||
' <body>\n\n' +
|
||||
content + '\n' +
|
||||
' </body>\n' +
|
||||
'</html>\n';
|
||||
};
|
||||
|
||||
var scriptContent = function(content) {
|
||||
return "angular.module('plunker', ['ui.bootstrap']);" + "\n" + content;
|
||||
};
|
||||
|
||||
addField('description', 'http://angular-ui.github.io/bootstrap/');
|
||||
addField('files[index.html]', indexContent(content.markup, version));
|
||||
addField('files[example.js]', scriptContent(content.javascript));
|
||||
|
||||
$document.find('body').append(form);
|
||||
form[0].submit();
|
||||
form.remove();
|
||||
};
|
||||
})
|
||||
|
||||
.controller('PlunkerCtrl', function ($scope, plunkGenerator) {
|
||||
|
||||
$scope.content = {};
|
||||
|
||||
$scope.edit = function (ngVersion, bsVersion, version, module) {
|
||||
plunkGenerator(ngVersion, bsVersion, version, module, $scope.content);
|
||||
};
|
||||
})
|
||||
|
||||
.directive('plunkerContent', function () {
|
||||
return {
|
||||
link:function (scope, element, attrs) {
|
||||
scope.$parent.content[attrs.plunkerContent] = element.text();
|
||||
}
|
||||
}
|
||||
});
|
||||
angular.module('plunker', [])
|
||||
|
||||
.factory('plunkGenerator', function ($document) {
|
||||
|
||||
return function (ngVersion, bsVersion, version, module, content) {
|
||||
|
||||
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);
|
||||
form.append(input);
|
||||
};
|
||||
|
||||
var indexContent = function (content, version) {
|
||||
return '<!doctype html>\n' +
|
||||
'<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' +
|
||||
' </head>\n' +
|
||||
' <body>\n\n' +
|
||||
content + '\n' +
|
||||
' </body>\n' +
|
||||
'</html>\n';
|
||||
};
|
||||
|
||||
var scriptContent = function(content) {
|
||||
return "angular.module('ui.bootstrap.demo', ['ngAnimate', 'ngSanitize', 'ui.bootstrap']);" + "\n" + content;
|
||||
};
|
||||
|
||||
addField('description', 'http://angular-ui.github.io/bootstrap/');
|
||||
addField('files[index.html]', indexContent(content.markup, version));
|
||||
addField('files[example.js]', scriptContent(content.javascript));
|
||||
|
||||
$document.find('body').append(form);
|
||||
form[0].submit();
|
||||
form.remove();
|
||||
};
|
||||
})
|
||||
|
||||
.controller('PlunkerCtrl', function ($scope, plunkGenerator) {
|
||||
|
||||
$scope.content = {};
|
||||
|
||||
$scope.edit = function (ngVersion, bsVersion, version, module) {
|
||||
plunkGenerator(ngVersion, bsVersion, version, module, $scope.content);
|
||||
};
|
||||
})
|
||||
|
||||
.directive('plunkerContent', function () {
|
||||
return {
|
||||
link:function (scope, element, attrs) {
|
||||
scope.content[attrs.plunkerContent] = element.text().trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,18 +14,18 @@ pre {
|
||||
|
||||
code {
|
||||
border: 1px solid #eaeaea;
|
||||
margin: 0px 2px;
|
||||
padding: 0px 5px;
|
||||
margin: 0 2px;
|
||||
padding: 0 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
pre code {
|
||||
border: 0px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
-moz-border-radius: 0px;
|
||||
-webkit-border-radius: 0px;
|
||||
border-radius: 0px;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
-moz-border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,524 +0,0 @@
|
||||
/*
|
||||
Version: 3.2 Timestamp: Mon Sep 10 10:38:04 PDT 2012
|
||||
*/
|
||||
.select2-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
/* inline-block for ie7 */
|
||||
zoom: 1;
|
||||
*display: inline;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.select2-container,
|
||||
.select2-drop,
|
||||
.select2-search,
|
||||
.select2-search input{
|
||||
/*
|
||||
Force border-box so that % widths fit the parent
|
||||
container without overlap because of margin/padding.
|
||||
|
||||
More Info : http://www.quirksmode.org/css/box.html
|
||||
*/
|
||||
-moz-box-sizing: border-box; /* firefox */
|
||||
-ms-box-sizing: border-box; /* ie */
|
||||
-webkit-box-sizing: border-box; /* webkit */
|
||||
-khtml-box-sizing: border-box; /* konqueror */
|
||||
box-sizing: border-box; /* css3 */
|
||||
}
|
||||
|
||||
.select2-container .select2-choice {
|
||||
background-color: #fff;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.5, white));
|
||||
background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 50%);
|
||||
background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 50%);
|
||||
background-image: -o-linear-gradient(bottom, #eeeeee 0%, #ffffff 50%);
|
||||
background-image: -ms-linear-gradient(top, #eeeeee 0%, #ffffff 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#ffffff', GradientType = 0);
|
||||
background-image: linear-gradient(top, #eeeeee 0%, #ffffff 50%);
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
-moz-background-clip: padding;
|
||||
-webkit-background-clip: padding-box;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #aaa;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
padding: 0 0 0 8px;
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.select2-container.select2-drop-above .select2-choice
|
||||
{
|
||||
border-bottom-color: #aaa;
|
||||
-webkit-border-radius:0px 0px 4px 4px;
|
||||
-moz-border-radius:0px 0px 4px 4px;
|
||||
border-radius:0px 0px 4px 4px;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.9, white));
|
||||
background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 90%);
|
||||
background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 90%);
|
||||
background-image: -o-linear-gradient(bottom, #eeeeee 0%, white 90%);
|
||||
background-image: -ms-linear-gradient(top, #eeeeee 0%,#ffffff 90%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 );
|
||||
background-image: linear-gradient(top, #eeeeee 0%,#ffffff 90%);
|
||||
}
|
||||
|
||||
.select2-container .select2-choice span {
|
||||
margin-right: 26px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
-o-text-overflow: ellipsis;
|
||||
-ms-text-overflow: ellipsis;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice abbr {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 26px;
|
||||
top: 8px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
font-size: 1px;
|
||||
background: url('select2.png') right top no-repeat;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
border:0;
|
||||
outline: 0;
|
||||
}
|
||||
.select2-container .select2-choice abbr:hover {
|
||||
background-position: right -11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select2-drop {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
border: 1px solid #aaa;
|
||||
border-top: 0;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
-webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
-moz-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
-o-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
z-index: 9999;
|
||||
width:100%;
|
||||
margin-top:-1px;
|
||||
|
||||
-webkit-border-radius: 0 0 4px 4px;
|
||||
-moz-border-radius: 0 0 4px 4px;
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above {
|
||||
-webkit-border-radius: 4px 4px 0px 0px;
|
||||
-moz-border-radius: 4px 4px 0px 0px;
|
||||
border-radius: 4px 4px 0px 0px;
|
||||
margin-top:1px;
|
||||
border-top: 1px solid #aaa;
|
||||
border-bottom: 0;
|
||||
|
||||
-webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
-moz-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
-o-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.select2-container .select2-choice div {
|
||||
-webkit-border-radius: 0 4px 4px 0;
|
||||
-moz-border-radius: 0 4px 4px 0;
|
||||
border-radius: 0 4px 4px 0;
|
||||
-moz-background-clip: padding;
|
||||
-webkit-background-clip: padding-box;
|
||||
background-clip: padding-box;
|
||||
background: #ccc;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
|
||||
background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
|
||||
background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
|
||||
background-image: -o-linear-gradient(bottom, #ccc 0%, #eee 60%);
|
||||
background-image: -ms-linear-gradient(top, #cccccc 0%, #eeeeee 60%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#cccccc', endColorstr = '#eeeeee', GradientType = 0);
|
||||
background-image: linear-gradient(top, #cccccc 0%, #eeeeee 60%);
|
||||
border-left: 1px solid #aaa;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice div b {
|
||||
background: url('select2.png') no-repeat 0 1px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.select2-search {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
z-index: 10000;
|
||||
min-height: 26px;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.select2-search-hidden {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -10000px;
|
||||
}
|
||||
|
||||
.select2-search input {
|
||||
background: #fff url('select2.png') no-repeat 100% -22px;
|
||||
background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
|
||||
background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
|
||||
background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
|
||||
background: url('select2.png') no-repeat 100% -22px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
|
||||
background: url('select2.png') no-repeat 100% -22px, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
|
||||
background: url('select2.png') no-repeat 100% -22px, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
|
||||
padding: 4px 20px 4px 5px;
|
||||
outline: 0;
|
||||
border: 1px solid #aaa;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
width:100%;
|
||||
margin:0;
|
||||
height:auto !important;
|
||||
min-height: 26px;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above .select2-search input
|
||||
{
|
||||
margin-top:4px;
|
||||
}
|
||||
|
||||
.select2-search input.select2-active {
|
||||
background: #fff url('spinner.gif') no-repeat 100%;
|
||||
background: url('spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
|
||||
background: url('spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
|
||||
background: url('spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
|
||||
background: url('spinner.gif') no-repeat 100%, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
|
||||
background: url('spinner.gif') no-repeat 100%, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
|
||||
background: url('spinner.gif') no-repeat 100%, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
|
||||
}
|
||||
|
||||
|
||||
.select2-container-active .select2-choice,
|
||||
.select2-container-active .select2-choices {
|
||||
-webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
|
||||
-moz-box-shadow : 0 0 5px rgba(0,0,0,.3);
|
||||
-o-box-shadow : 0 0 5px rgba(0,0,0,.3);
|
||||
box-shadow : 0 0 5px rgba(0,0,0,.3);
|
||||
border: 1px solid #5897fb;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice {
|
||||
border: 1px solid #aaa;
|
||||
border-bottom-color: transparent;
|
||||
-webkit-box-shadow: 0 1px 0 #fff inset;
|
||||
-moz-box-shadow : 0 1px 0 #fff inset;
|
||||
-o-box-shadow : 0 1px 0 #fff inset;
|
||||
box-shadow : 0 1px 0 #fff inset;
|
||||
background-color: #eee;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, white), color-stop(0.5, #eeeeee));
|
||||
background-image: -webkit-linear-gradient(center bottom, white 0%, #eeeeee 50%);
|
||||
background-image: -moz-linear-gradient(center bottom, white 0%, #eeeeee 50%);
|
||||
background-image: -o-linear-gradient(bottom, white 0%, #eeeeee 50%);
|
||||
background-image: -ms-linear-gradient(top, #ffffff 0%,#eeeeee 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 );
|
||||
background-image: linear-gradient(top, #ffffff 0%,#eeeeee 50%);
|
||||
-webkit-border-bottom-left-radius : 0;
|
||||
-webkit-border-bottom-right-radius: 0;
|
||||
-moz-border-radius-bottomleft : 0;
|
||||
-moz-border-radius-bottomright: 0;
|
||||
border-bottom-left-radius : 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice div {
|
||||
background: transparent;
|
||||
border-left: none;
|
||||
}
|
||||
.select2-dropdown-open .select2-choice div b {
|
||||
background-position: -18px 1px;
|
||||
}
|
||||
|
||||
/* results */
|
||||
.select2-results {
|
||||
margin: 4px 4px 4px 0;
|
||||
padding: 0 0 0 4px;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.select2-results ul.select2-result-sub {
|
||||
margin: 0 0 0 0;
|
||||
}
|
||||
|
||||
.select2-results ul.select2-result-sub > li .select2-result-label { padding-left: 20px }
|
||||
.select2-results ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 40px }
|
||||
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 60px }
|
||||
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 80px }
|
||||
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 100px }
|
||||
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 110px }
|
||||
.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 120px }
|
||||
|
||||
.select2-results li {
|
||||
list-style: none;
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
.select2-results li.select2-result-with-children > .select2-result-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.select2-results .select2-result-label {
|
||||
padding: 3px 7px 4px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select2-results .select2-highlighted {
|
||||
background: #3875d7;
|
||||
color: #fff;
|
||||
}
|
||||
.select2-results li em {
|
||||
background: #feffde;
|
||||
font-style: normal;
|
||||
}
|
||||
.select2-results .select2-highlighted em {
|
||||
background: transparent;
|
||||
}
|
||||
.select2-results .select2-no-results,
|
||||
.select2-results .select2-searching,
|
||||
.select2-results .select2-selection-limit {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/*
|
||||
disabled look for already selected choices in the results dropdown
|
||||
.select2-results .select2-disabled.select2-highlighted {
|
||||
color: #666;
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
cursor: default;
|
||||
}
|
||||
.select2-results .select2-disabled {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
cursor: default;
|
||||
}
|
||||
*/
|
||||
.select2-results .select2-disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select2-more-results.select2-active {
|
||||
background: #f4f4f4 url('spinner.gif') no-repeat 100%;
|
||||
}
|
||||
|
||||
.select2-more-results {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* disabled styles */
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border: 1px solid #ddd;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice div {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
|
||||
/* multiselect */
|
||||
|
||||
.select2-container-multi .select2-choices {
|
||||
background-color: #fff;
|
||||
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
|
||||
background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
|
||||
background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
|
||||
background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
|
||||
background-image: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%);
|
||||
background-image: linear-gradient(top, #eeeeee 1%, #ffffff 15%);
|
||||
border: 1px solid #aaa;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: text;
|
||||
overflow: hidden;
|
||||
height: auto !important;
|
||||
height: 1%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices {
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-active .select2-choices {
|
||||
-webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
|
||||
-moz-box-shadow : 0 0 5px rgba(0,0,0,.3);
|
||||
-o-box-shadow : 0 0 5px rgba(0,0,0,.3);
|
||||
box-shadow : 0 0 5px rgba(0,0,0,.3);
|
||||
border: 1px solid #5897fb;
|
||||
outline: none;
|
||||
}
|
||||
.select2-container-multi .select2-choices li {
|
||||
float: left;
|
||||
list-style: none;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-field {
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-field input {
|
||||
color: #666;
|
||||
background: transparent !important;
|
||||
font-family: sans-serif;
|
||||
font-size: 100%;
|
||||
height: 15px;
|
||||
padding: 5px;
|
||||
margin: 1px 0;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow : none;
|
||||
-o-box-shadow : none;
|
||||
box-shadow : none;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-field input.select2-active {
|
||||
background: #fff url('spinner.gif') no-repeat 100% !important;
|
||||
}
|
||||
|
||||
.select2-default {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice {
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius : 3px;
|
||||
border-radius : 3px;
|
||||
-moz-background-clip : padding;
|
||||
-webkit-background-clip: padding-box;
|
||||
background-clip : padding-box;
|
||||
background-color: #e4e4e4;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f4f4f4', endColorstr='#eeeeee', GradientType=0 );
|
||||
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
|
||||
background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
|
||||
background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
|
||||
background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
|
||||
background-image: -ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
|
||||
background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
|
||||
-webkit-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
|
||||
-moz-box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
|
||||
box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05);
|
||||
color: #333;
|
||||
border: 1px solid #aaaaaa;
|
||||
line-height: 13px;
|
||||
padding: 3px 5px 3px 18px;
|
||||
margin: 3px 0 3px 5px;
|
||||
position: relative;
|
||||
cursor: default;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-choice span {
|
||||
cursor: default;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-choice-focus {
|
||||
background: #d4d4d4;
|
||||
}
|
||||
|
||||
.select2-search-choice-close {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: 4px;
|
||||
width: 12px;
|
||||
height: 13px;
|
||||
font-size: 1px;
|
||||
background: url('select2.png') right top no-repeat;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-search-choice-close {
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
|
||||
background-position: right -11px;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
|
||||
background-position: right -11px;
|
||||
}
|
||||
|
||||
/* disabled styles */
|
||||
|
||||
.select2-container-multi.select2-container-disabled .select2-choices{
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border: 1px solid #ddd;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
|
||||
background-image: none;
|
||||
background-color: #f4f4f4;
|
||||
border: 1px solid #ddd;
|
||||
padding: 3px 5px 3px 5px;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close {
|
||||
display: none;
|
||||
}
|
||||
/* end multiselect */
|
||||
|
||||
.select2-result-selectable .select2-match,
|
||||
.select2-result-unselectable .select2-result-selectable .select2-match { text-decoration: underline; }
|
||||
.select2-result-unselectable .select2-match { text-decoration: none; }
|
||||
|
||||
.select2-offscreen { position: absolute; left: -10000px; }
|
||||
|
||||
/* Retina-ize icons */
|
||||
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.5) {
|
||||
.select2-search input, .select2-search-choice-close, .select2-container .select2-choice abbr, .select2-container .select2-choice div b {
|
||||
background-image: url(select2x2.png) !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-size: 60px 40px !important;
|
||||
}
|
||||
.select2-search input {
|
||||
background-position: 100% -21px !important;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 613 B |
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* https://github.com/alicelieutier/smoothScroll/
|
||||
* A teeny tiny, standard compliant, smooth scroll script with ease-in-out effect and no jQuery (or any other dependancy, FWIW).
|
||||
* MIT License
|
||||
*/
|
||||
window.smoothScroll = (function(){
|
||||
// We do not want this script to be applied in browsers that do not support those
|
||||
// That means no smoothscroll on IE9 and below.
|
||||
if(document.querySelectorAll === void 0 || window.pageYOffset === void 0 || history.pushState === void 0) { return; }
|
||||
|
||||
// Get the top position of an element in the document
|
||||
var getTop = function(element) {
|
||||
// return value of html.getBoundingClientRect().top ... IE : 0, other browsers : -pageYOffset
|
||||
if(element.nodeName === 'HTML') return -window.pageYOffset
|
||||
return element.getBoundingClientRect().top + window.pageYOffset;
|
||||
}
|
||||
// ease in out function thanks to:
|
||||
// http://blog.greweb.fr/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/
|
||||
var easeInOutCubic = function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }
|
||||
|
||||
// calculate the scroll position we should be in
|
||||
// given the start and end point of the scroll
|
||||
// the time elapsed from the beginning of the scroll
|
||||
// and the total duration of the scroll (default 500ms)
|
||||
var position = function(start, end, elapsed, duration) {
|
||||
if (elapsed > duration) return end;
|
||||
return start + (end - start) * easeInOutCubic(elapsed / duration); // <-- you can change the easing funtion there
|
||||
// return start + (end - start) * (elapsed / duration); // <-- this would give a linear scroll
|
||||
}
|
||||
|
||||
// we use requestAnimationFrame to be called by the browser before every repaint
|
||||
// if the first argument is an element then scroll to the top of this element
|
||||
// if the first argument is numeric then scroll to this location
|
||||
// if the callback exist, it is called when the scrolling is finished
|
||||
var smoothScroll = function(el, duration, callback){
|
||||
duration = duration || 500;
|
||||
var start = window.pageYOffset;
|
||||
|
||||
if (typeof el === 'number') {
|
||||
var end = parseInt(el);
|
||||
} else {
|
||||
var end = getTop(el);
|
||||
}
|
||||
|
||||
var clock = Date.now();
|
||||
var requestAnimationFrame = window.requestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
|
||||
function(fn){window.setTimeout(fn, 15);};
|
||||
|
||||
var step = function(){
|
||||
var elapsed = Date.now() - clock;
|
||||
window.scroll(0, position(start, end, elapsed, duration));
|
||||
if (elapsed > duration) {
|
||||
if (typeof callback === 'function') {
|
||||
callback(el);
|
||||
}
|
||||
} else {
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
}
|
||||
step();
|
||||
}
|
||||
|
||||
var linkHandler = function(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
if (location.hash !== this.hash) {
|
||||
//NOTE(@ajoslin): Changed this line to stop $digest errors
|
||||
//window.history.pushState(null, null, this.hash)
|
||||
angular.element(document).injector().get('$location').hash(this.hash);
|
||||
}
|
||||
// using the history api to solve issue #1 - back doesn't work
|
||||
// most browser don't update :target when the history api is used:
|
||||
// THIS IS A BUG FROM THE BROWSERS.
|
||||
// change the scrolling duration in this call
|
||||
var targetEl = document.getElementById(this.hash.substring(1));
|
||||
if (targetEl) {
|
||||
smoothScroll(document.getElementById(this.hash.substring(1)), 500, function(el) {
|
||||
location.replace('#' + el.id)
|
||||
// this will cause the :target to be activated.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// We look for all the internal links in the documents and attach the smoothscroll function
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
var internal = document.querySelectorAll('a[href^="#"]'), a;
|
||||
for(var i=internal.length; a=internal[--i];){
|
||||
a.addEventListener("click", linkHandler, false);
|
||||
}
|
||||
});
|
||||
|
||||
// return smoothscroll API
|
||||
return smoothScroll;
|
||||
|
||||
})();
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,117 +0,0 @@
|
||||
angular.module('ui.config', []).value('ui.config', {});
|
||||
angular.module('ui.directives', ['ui.config']);
|
||||
|
||||
/**
|
||||
* Enhanced Select2 Dropmenus
|
||||
*
|
||||
* @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2
|
||||
* This change is so that you do not have to do an additional query yourself on top of Select2's own query
|
||||
* @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation
|
||||
*/
|
||||
angular.module('ui.directives').directive('uiSelect2', ['ui.config', '$http', function (uiConfig, $http) {
|
||||
var options = {};
|
||||
if (uiConfig.select2) {
|
||||
angular.extend(options, uiConfig.select2);
|
||||
}
|
||||
return {
|
||||
require: '?ngModel',
|
||||
compile: function (tElm, tAttrs) {
|
||||
var watch,
|
||||
repeatOption,
|
||||
repeatAttr,
|
||||
isSelect = tElm.is('select'),
|
||||
isMultiple = (tAttrs.multiple !== undefined);
|
||||
|
||||
// Enable watching of the options dataset if in use
|
||||
if (tElm.is('select')) {
|
||||
repeatOption = tElm.find('option[ng-repeat], option[data-ng-repeat]');
|
||||
|
||||
if (repeatOption.length) {
|
||||
repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat');
|
||||
watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop();
|
||||
}
|
||||
}
|
||||
|
||||
return function (scope, elm, attrs, controller) {
|
||||
// instance-specific options
|
||||
var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2));
|
||||
|
||||
if (isSelect) {
|
||||
// Use <select multiple> instead
|
||||
delete opts.multiple;
|
||||
delete opts.initSelection;
|
||||
} else if (isMultiple) {
|
||||
opts.multiple = true;
|
||||
}
|
||||
|
||||
if (controller) {
|
||||
// Watch the model for programmatic changes
|
||||
controller.$render = function () {
|
||||
if (isSelect) {
|
||||
elm.select2('val', controller.$modelValue);
|
||||
} else {
|
||||
if (isMultiple && !controller.$modelValue) {
|
||||
elm.select2('data', []);
|
||||
} else if (angular.isObject(controller.$modelValue)) {
|
||||
elm.select2('data', controller.$modelValue);
|
||||
} else {
|
||||
elm.select2('val', controller.$modelValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Watch the options dataset for changes
|
||||
if (watch) {
|
||||
scope.$watch(watch, function (newVal, oldVal, scope) {
|
||||
if (!newVal) return;
|
||||
// Delayed so that the options have time to be rendered
|
||||
setTimeout(function () {
|
||||
elm.select2('val', controller.$viewValue);
|
||||
// Refresh angular to remove the superfluous option
|
||||
elm.trigger('change');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (!isSelect) {
|
||||
// Set the view and model value and update the angular template manually for the ajax/multiple select2.
|
||||
elm.bind("change", function () {
|
||||
scope.$apply(function () {
|
||||
controller.$setViewValue(elm.select2('data'));
|
||||
});
|
||||
});
|
||||
|
||||
if (opts.initSelection) {
|
||||
var initSelection = opts.initSelection;
|
||||
opts.initSelection = function (element, callback) {
|
||||
initSelection(element, function (value) {
|
||||
controller.$setViewValue(value);
|
||||
callback(value);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attrs.$observe('disabled', function (value) {
|
||||
elm.select2(value && 'disable' || 'enable');
|
||||
});
|
||||
|
||||
if (attrs.ngMultiple) {
|
||||
scope.$watch(attrs.ngMultiple, function(newVal) {
|
||||
elm.select2(opts);
|
||||
});
|
||||
}
|
||||
|
||||
// Set initial value since Angular doesn't
|
||||
elm.val(scope.$eval(attrs.ngModel));
|
||||
|
||||
// Initialize the plugin late so that the injected DOM does not disrupt the template compiler
|
||||
setTimeout(function () {
|
||||
elm.select2(opts);
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}]);
|
||||
+298
-165
@@ -1,202 +1,272 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" ng-app="bootstrapDemoApp" id="top">
|
||||
<html lang="en" ng-app="ui.bootstrap.demo" id="top">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Angular directives for Twitter's Bootstrap</title>
|
||||
<meta name="description" content="AngularJS (Angular) native directives for Twitter Bootstrap. Small footprint (5kB gzipped!), no 3rd party JS dependencies (jQuery, bootstrap JS) required! Widgets: <% demoModules.forEach(function(module) { %><%= module.displayName %>, <% }); %>">
|
||||
<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="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/<%= ngversion %>/angular.min.js"></script>
|
||||
<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/select2.js"></script>
|
||||
<script src="assets/ui-select2.js"></script>
|
||||
<script src="assets/plunker.js"></script>
|
||||
<script src="assets/app.js"></script>
|
||||
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/<%= bsversion %>/css/bootstrap.min.css" rel="stylesheet"/>
|
||||
|
||||
<link href="//netdna.bootstrapcdn.com/bootstrap/<%= bsversion %>/css/bootstrap.min.css" rel="stylesheet"/>
|
||||
<link rel="stylesheet" href="assets/rainbow.css"/>
|
||||
<link rel="stylesheet" href="assets/demo.css"/>
|
||||
<link rel="stylesheet" href="assets/select2.css"/>
|
||||
|
||||
<link rel="author" href="https://github.com/angular-ui/bootstrap/graphs/contributors">
|
||||
</head>
|
||||
<body class="ng-cloak" ng-controller="MainCtrl">
|
||||
<header class="navbar navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<header class="navbar navbar-default navbar-fixed-top navbar-inner">
|
||||
<div class="container">
|
||||
<div class="dropdown pull-left">
|
||||
<a class="brand dropdown-toggle" role="button" data-toggle="dropdown" href="#">
|
||||
UI Bootstrap
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
|
||||
<li><a href="https://github.com/angular-ui/"><strong>Related Projects:</strong></a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="http://angular-ui.github.io/">AngularUI</a></li>
|
||||
<li><a href="http://angular-ui.github.io/ng-grid/">Grid</a></li>
|
||||
<li><a href="https://github.com/angular-ui/AngularJs.tmbundle">AngularJS.tmbundle</a></li>
|
||||
<li><a href="http://angular-ui.github.io/router/">Router <span class="label label-success">New!</span></a></li>
|
||||
<li><a href="http://angular-ui.github.io/Tips-n-Tricks/">Tips-n-Tricks <span class="label">WIP</span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="nav-collapse">
|
||||
<ul class="nav">
|
||||
<li>
|
||||
<a href="#" 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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</header>
|
||||
<div style="margin: 32px;"></div>
|
||||
<div class="header-placeholder"></div>
|
||||
<div role="main">
|
||||
|
||||
<header class="hero-unit" id="overview">
|
||||
<header class="bs-header text-center" id="overview">
|
||||
<div class="container">
|
||||
<h1>
|
||||
UI Bootstrap
|
||||
</h1>
|
||||
|
||||
<p>Bootstrap components written in pure <strong><a href="http://angularjs.org">AngularJS</a></strong> by the <strong><a
|
||||
href="http://angular-ui.github.io">AngularUI Team</a></strong></p>
|
||||
<p>Bootstrap components written in pure <a href="http://angularjs.org">AngularJS</a> by the <a
|
||||
href="http://angular-ui.github.io">AngularUI Team</a></p>
|
||||
|
||||
<p class="btn-group">
|
||||
<a class="btn" href="https://github.com/angular-ui/bootstrap"><i class="icon-github"></i> Code on Github</a>
|
||||
<a class="btn 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>
|
||||
<a class="btn" ng-click="showBuildModal()"><i class="icon-wrench"></i> Create a Build</a>
|
||||
<p>
|
||||
<a class="btn btn-outline-inverse btn-lg" href="https://github.com/angular-ui/bootstrap"><i class="icon-github"></i>Code on Github</a>
|
||||
<button type="button" class="btn btn-outline-inverse btn-lg" ng-click="showDownloadModal()">
|
||||
<i class="glyphicon glyphicon-download-alt"></i> Download <small>(<%= pkg.version%>)</small>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-inverse btn-lg" ng-click="showBuildModal()"><i class="glyphicon glyphicon-wrench"></i> Create a Build</button>
|
||||
</p>
|
||||
</div>
|
||||
<div class="bs-docs-social">
|
||||
<div class="container">
|
||||
<ul class="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>
|
||||
<li>
|
||||
<iframe src="http://ghbtns.com/github-btn.html?user=angular-ui&repo=bootstrap&type=fork&count=true"
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="95" height="20"></iframe>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://twitter.com/share" class="twitter-share-button"
|
||||
data-hashtags="angularjs">Tweet</a>
|
||||
<script>!function (d, s, id) {
|
||||
var js, fjs = d.getElementsByTagName(s)[0];
|
||||
if (!d.getElementById(id)) {
|
||||
js = d.createElement(s);
|
||||
js.id = id;
|
||||
js.src = "//platform.twitter.com/widgets.js";
|
||||
fjs.parentNode.insertBefore(js, fjs);
|
||||
}
|
||||
}(document, "script", "twitter-wjs");</script>
|
||||
</li>
|
||||
<li>
|
||||
<!-- Place this tag where you want the +1 button to render. -->
|
||||
<div class="g-plusone" data-size="medium"></div>
|
||||
<div class="bs-social container">
|
||||
<ul class="bs-social-buttons">
|
||||
<li>
|
||||
<iframe src="//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>
|
||||
<li>
|
||||
<iframe src="//ghbtns.com/github-btn.html?user=angular-ui&repo=bootstrap&type=fork&count=true"
|
||||
allowtransparency="true" frameborder="0" scrolling="0" width="110" height="20"></iframe>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://twitter.com/share" class="twitter-share-button"
|
||||
data-hashtags="angularjs">Tweet</a>
|
||||
<script>!function (d, s, id) {
|
||||
var js, fjs = d.getElementsByTagName(s)[0];
|
||||
if (!d.getElementById(id)) {
|
||||
js = d.createElement(s);
|
||||
js.id = id;
|
||||
js.src = "//platform.twitter.com/widgets.js";
|
||||
fjs.parentNode.insertBefore(js, fjs);
|
||||
}
|
||||
}(document, "script", "twitter-wjs");</script>
|
||||
</li>
|
||||
<li>
|
||||
<!-- Place this tag where you want the +1 button to render. -->
|
||||
<div class="g-plusone" data-size="medium"></div>
|
||||
|
||||
<!-- Place this tag after the last +1 button tag. -->
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
var po = document.createElement('script');
|
||||
po.type = 'text/javascript';
|
||||
po.async = true;
|
||||
po.src = 'https://apis.google.com/js/plusone.js';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(po, s);
|
||||
})();
|
||||
</script>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Place this tag after the last +1 button tag. -->
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
var po = document.createElement('script');
|
||||
po.type = 'text/javascript';
|
||||
po.async = true;
|
||||
po.src = 'https://apis.google.com/js/plusone.js';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(po, s);
|
||||
})();
|
||||
</script>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<section class="bs-sidebar visible-xs" id="directives_small">
|
||||
<ul class="nav bs-sidenav">
|
||||
<li><a href="#"><strong>Directives</strong></a></li>
|
||||
<% demoModules.forEach(function(module) { %>
|
||||
<li><a href="#<%= module.name %>"><%= module.displayName %></a></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section id="getting_started">
|
||||
<div class="page-header">
|
||||
<h1>Getting started</h1>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="span12">
|
||||
<section id="getting_started">
|
||||
<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
|
||||
Twitter Bootstrap's markup and CSS. As a result no dependency on jQuery or Bootstrap's
|
||||
Bootstrap's markup and CSS. As a result no dependency on jQuery or Bootstrap's
|
||||
JavaScript is required. The <strong>only required dependencies</strong> are:
|
||||
</p>
|
||||
<ul>
|
||||
<li>AngularJS (minimal version 1.0.4 or 1.1.2)</li>
|
||||
<li>Bootstrap CSS</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.
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Files to download</h3>
|
||||
<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>
|
||||
<code>angular.module('myModule', ['ui.bootstrap']);</code>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<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:
|
||||
|
||||
<% demoModules.forEach(function(module) { %>
|
||||
<section id="<%= module.name %>">
|
||||
<div class="page-header">
|
||||
<h1><%= module.displayName %><small>
|
||||
(<a target="_blank" href="https://github.com/angular-ui/bootstrap/tree/master/src/<%= module.name %>">ui.bootstrap.<%= module.name %></a>)
|
||||
</small></h1>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
<%= module.docs.html %>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<%= module.docs.md %>
|
||||
<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 %>">
|
||||
<div class="page-header">
|
||||
<h1><%= module.displayName %><small>
|
||||
(<a target="_blank" href="https://github.com/angular-ui/bootstrap/tree/master/src/<%= module.name %>">ui.bootstrap.<%= module.name %></a>)
|
||||
</small></h1>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 show-grid">
|
||||
<%= module.docs.html %>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<%= module.docs.md %>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row code">
|
||||
<div class="col-md-12" ng-controller="PlunkerCtrl">
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn btn-info plunk-btn" ng-click="edit('<%= ngversion%>', '<%= bsversion %>', '<%= pkg.version%>', '<%= module.name %>')"><i class="glyphicon glyphicon-edit"></i> Edit in plunker</button>
|
||||
</div>
|
||||
<uib-tabset active="activeTab">
|
||||
<uib-tab index="0" heading="Markup">
|
||||
<div plunker-content="markup">
|
||||
<pre ng-non-bindable><code data-language="html"><%- module.docs.html %></code></pre>
|
||||
</div>
|
||||
</uib-tab>
|
||||
<uib-tab index="1" heading="JavaScript">
|
||||
<div plunker-content="javascript">
|
||||
<pre ng-non-bindable><code data-language="javascript"><%- module.docs.js %></code></pre>
|
||||
</div>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script><%= module.docs.js %></script>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="span12" 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="icon-edit icon-white"></i> Edit in plunker</button>
|
||||
</div>
|
||||
<tabs>
|
||||
<pane heading="Markup" plunker-content="markup"><pre ng-non-bindable><code data-language="html"><%- module.docs.html %></code></pre></pane>
|
||||
<pane heading="JavaScript" plunker-content="javascript"><pre ng-non-bindable><code data-language="javascript"><%- module.docs.js %></code></pre></pane>
|
||||
</tabs>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script><%= module.docs.js %></script>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.container -->
|
||||
</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>Code licensed under <a href="https://github.com/angular-ui/bootstrap/blob/master/LICENSE">MIT License</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>
|
||||
</footer>
|
||||
@@ -204,28 +274,89 @@
|
||||
<script src="assets/rainbow-generic.js"></script>
|
||||
<script src="assets/rainbow-javascript.js"></script>
|
||||
<script src="assets/rainbow-html.js"></script>
|
||||
<div modal="buildModalShown" class="fade">
|
||||
<div class="modal-header"><h2>Create a Custom Build</h2></div>
|
||||
<div class="modal-body">
|
||||
<div ng-show="buildErrorText">
|
||||
<h3 style="text-align: center;">{{buildErrorText}}</h3>
|
||||
<script type="text/ng-template" id="downloadModal.html">
|
||||
<div class="modal-header"><h4 class="modal-title">Download Angular UI Bootstrap</h4></div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<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" 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>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<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" 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>npm</strong></label>
|
||||
<div class="col-sm-9">
|
||||
<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>
|
||||
</div>
|
||||
<div ng-hide="buildErrorText">
|
||||
<label>Select the modules you wish to download:
|
||||
<select style="width: 100%;" ui-select2 multiple
|
||||
ui-options="{multiple: true}"
|
||||
ng-model="selectedModules">
|
||||
<% modules.forEach(function(module) { %>
|
||||
<option value="<%= module.name %>"><%= module.displayName %></option>
|
||||
<% }); %>
|
||||
</select></label>
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-default" ng-click="cancel()">Close</a>
|
||||
<a class="btn btn-primary" ng-href="{{download('<%= pkg.version%>')}}" download><i class="glyphicon glyphicon-download-alt"></i> Download <%= pkg.version %></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-primary" ng-disabled="!selectedModules.length" ng-click="selectedModules.length && downloadBuild()"><i class="icon-download-alt icon-white"></i> Download {{selectedModules.length}} Modules</a>
|
||||
<a class="btn" ng-click="buildModalShown = false">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
<script type="text/ng-template" id="buildModal.html">
|
||||
<div class="modal-header">
|
||||
<h4>
|
||||
Create a Custom Build
|
||||
<br>
|
||||
<small>Select the modules you wish to download</small>
|
||||
</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 || isOldBrowser()">
|
||||
<% modules.forEach(function(module,i) { %>
|
||||
<% if (i % 3 === 0) {%>
|
||||
<div class="btn-group" style="width: 100%;">
|
||||
<% } %>
|
||||
<button type="button" class="btn btn-default"
|
||||
style="width: 33%; border-radius: 0;"
|
||||
ng-class="{'btn-primary': module.<%= module.name %>}"
|
||||
ng-model="module.<%= module.name %>"
|
||||
uib-btn-checkbox
|
||||
ng-change="selectedChanged('<%= module.name %>', module.<%= module.name %>)">
|
||||
<%= module.displayName %>
|
||||
</button>
|
||||
<% if ((i+1) % 3 === 0) { //end button group%>
|
||||
</div>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-default" ng-click="cancel()">Close</a>
|
||||
<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>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
@@ -240,5 +371,7 @@
|
||||
})();
|
||||
|
||||
</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
-1764
File diff suppressed because it is too large
Load Diff
Vendored
-14733
File diff suppressed because it is too large
Load Diff
@@ -1,13 +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 '" + angular.mock.dump(this.actual) + "' 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(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Git COMMIT-MSG hook for validating commit message
|
||||
* See https://docs.google.com/document/d/1rk04jEuGfk9kYzfqCuOlPTSJw3hEDZJTBN5E5f1SALo/edit
|
||||
*
|
||||
* Installation:
|
||||
* >> cd <angular-repo>
|
||||
* >> ln -s validate-commit-msg.js .git/hooks/commit-msg
|
||||
*/
|
||||
var fs = require('fs');
|
||||
var util = require('util');
|
||||
|
||||
|
||||
var MAX_LENGTH = 70;
|
||||
var PATTERN = /^(?:fixup!\s*)?(\w*)(\((\w+)\))?\: (.*)$/;
|
||||
var IGNORED = /^WIP\:/;
|
||||
var TYPES = {
|
||||
chore: true,
|
||||
demo: true,
|
||||
docs: true,
|
||||
feat: true,
|
||||
fix: true,
|
||||
refactor: true,
|
||||
revert: true,
|
||||
style: true,
|
||||
test: true
|
||||
};
|
||||
|
||||
|
||||
var error = function() {
|
||||
// gitx does not display it
|
||||
// http://gitx.lighthouseapp.com/projects/17830/tickets/294-feature-display-hook-error-message-when-hook-fails
|
||||
// https://groups.google.com/group/gitx/browse_thread/thread/a03bcab60844b812
|
||||
console.error('INVALID COMMIT MSG: ' + util.format.apply(null, arguments));
|
||||
};
|
||||
|
||||
|
||||
var validateMessage = function(message) {
|
||||
var isValid = true;
|
||||
|
||||
if (IGNORED.test(message)) {
|
||||
console.log('Commit message validation ignored.');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.length > MAX_LENGTH) {
|
||||
error('is longer than %d characters !', MAX_LENGTH);
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
var match = PATTERN.exec(message);
|
||||
|
||||
if (!match) {
|
||||
error('does not match "<type>(<scope>): <subject>" ! was: "' + message + '"\nNote: <scope> must be only letters.');
|
||||
return false;
|
||||
}
|
||||
|
||||
var type = match[1];
|
||||
var scope = match[3];
|
||||
var subject = match[4];
|
||||
|
||||
if (!TYPES.hasOwnProperty(type)) {
|
||||
error('"%s" is not allowed type !', type);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some more ideas, do want anything like this ?
|
||||
// - allow only specific scopes (eg. fix(docs) should not be allowed ?
|
||||
// - auto correct the type to lower case ?
|
||||
// - auto correct first letter of the subject to lower case ?
|
||||
// - auto add empty line after subject ?
|
||||
// - auto remove empty () ?
|
||||
// - auto correct typos in type ?
|
||||
// - store incorrect messages, so that we can learn
|
||||
|
||||
return isValid;
|
||||
};
|
||||
|
||||
|
||||
var firstLineFromBuffer = function(buffer) {
|
||||
return buffer.toString().split('\n').shift();
|
||||
};
|
||||
|
||||
|
||||
|
||||
// publish for testing
|
||||
exports.validateMessage = validateMessage;
|
||||
|
||||
// hacky start if not run by jasmine :-D
|
||||
if (process.argv.join('').indexOf('jasmine-node') === -1) {
|
||||
var commitMsgFile = process.argv[2];
|
||||
var incorrectLogFile = commitMsgFile.replace('COMMIT_EDITMSG', 'logs/incorrect-commit-msgs');
|
||||
|
||||
fs.readFile(commitMsgFile, function(err, buffer) {
|
||||
var msg = firstLineFromBuffer(buffer);
|
||||
|
||||
if (!validateMessage(msg)) {
|
||||
fs.appendFile(incorrectLogFile, msg + '\n', function() {
|
||||
process.exit(1);
|
||||
});
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
+55
-10
@@ -1,16 +1,61 @@
|
||||
{
|
||||
"author": "https://github.com/angular-ui/bootstrap/graphs/contributors",
|
||||
"name": "angular-ui-bootstrap",
|
||||
"version": "0.3.0",
|
||||
"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",
|
||||
"node-markdown": "0.1.1",
|
||||
"grunt-contrib-concat": "~0.1.3",
|
||||
"grunt-contrib-copy": "~0.4.1",
|
||||
"grunt-contrib-uglify": "~0.2.0",
|
||||
"grunt-contrib-watch": "~0.3.1",
|
||||
"grunt-contrib-jshint": "~0.4.0",
|
||||
"grunt-html2js": "~0.1.3"
|
||||
}
|
||||
"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"
|
||||
}
|
||||
|
||||
+145
-141
@@ -1,141 +1,145 @@
|
||||
angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
||||
|
||||
.constant('accordionConfig', {
|
||||
closeOthers: true
|
||||
})
|
||||
|
||||
.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', 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 ) {
|
||||
group.isOpen = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// This is called from the accordion-group directive to add itself to the accordion
|
||||
this.addGroup = function(groupScope) {
|
||||
var that = this;
|
||||
this.groups.push(groupScope);
|
||||
|
||||
groupScope.$on('$destroy', function (event) {
|
||||
that.removeGroup(groupScope);
|
||||
});
|
||||
};
|
||||
|
||||
// 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 ) {
|
||||
this.groups.splice(this.groups.indexOf(group), 1);
|
||||
}
|
||||
};
|
||||
|
||||
}])
|
||||
|
||||
// The accordion directive simply sets up the directive controller
|
||||
// and adds an accordion CSS class to itself element.
|
||||
.directive('accordion', function () {
|
||||
return {
|
||||
restrict:'EA',
|
||||
controller:'AccordionController',
|
||||
transclude: true,
|
||||
replace: false,
|
||||
templateUrl: 'template/accordion/accordion.html'
|
||||
};
|
||||
})
|
||||
|
||||
// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
|
||||
.directive('accordionGroup', ['$parse', '$transition', '$timeout', function($parse, $transition, $timeout) {
|
||||
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',
|
||||
scope:{ heading:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope
|
||||
controller: ['$scope', function($scope) {
|
||||
this.setHeading = function(element) {
|
||||
this.heading = element;
|
||||
};
|
||||
}],
|
||||
link: function(scope, element, attrs, accordionCtrl) {
|
||||
var getIsOpen, setIsOpen;
|
||||
|
||||
accordionCtrl.addGroup(scope);
|
||||
|
||||
scope.isOpen = false;
|
||||
|
||||
if ( attrs.isOpen ) {
|
||||
getIsOpen = $parse(attrs.isOpen);
|
||||
setIsOpen = getIsOpen.assign;
|
||||
|
||||
scope.$watch(
|
||||
function watchIsOpen() { return getIsOpen(scope.$parent); },
|
||||
function updateOpen(value) { scope.isOpen = value; }
|
||||
);
|
||||
|
||||
scope.isOpen = getIsOpen ? getIsOpen(scope.$parent) : false;
|
||||
}
|
||||
|
||||
scope.$watch('isOpen', function(value) {
|
||||
if ( value ) {
|
||||
accordionCtrl.closeOthers(scope);
|
||||
}
|
||||
if ( setIsOpen ) {
|
||||
setIsOpen(scope.$parent, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
// 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() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true, // Grab the contents to be used as the heading
|
||||
template: '', // In effect remove this element!
|
||||
replace: true,
|
||||
require: '^accordionGroup',
|
||||
compile: function(element, attr, transclude) {
|
||||
return function link(scope, element, attr, accordionGroupCtrl) {
|
||||
// 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() {}));
|
||||
};
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
// 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() {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse', 'ui.bootstrap.tabindex'])
|
||||
|
||||
.constant('uibAccordionConfig', {
|
||||
closeOthers: true
|
||||
})
|
||||
|
||||
.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) {
|
||||
group.isOpen = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// This is called from the accordion-group directive to add itself to the accordion
|
||||
this.addGroup = function(groupScope) {
|
||||
var that = this;
|
||||
this.groups.push(groupScope);
|
||||
|
||||
groupScope.$on('$destroy', function(event) {
|
||||
that.removeGroup(groupScope);
|
||||
});
|
||||
};
|
||||
|
||||
// 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) {
|
||||
this.groups.splice(index, 1);
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
// The accordion directive simply sets up the directive controller
|
||||
// and adds an accordion CSS class to itself element.
|
||||
.directive('uibAccordion', function() {
|
||||
return {
|
||||
controller: 'UibAccordionController',
|
||||
controllerAs: 'accordion',
|
||||
transclude: true,
|
||||
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('uibAccordionGroup', function() {
|
||||
return {
|
||||
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: '=?'
|
||||
},
|
||||
controller: function() {
|
||||
this.setHeading = function(element) {
|
||||
this.heading = element;
|
||||
};
|
||||
},
|
||||
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) {
|
||||
element.toggleClass(scope.openClass, !!value);
|
||||
if (value) {
|
||||
accordionCtrl.closeOthers(scope);
|
||||
}
|
||||
});
|
||||
|
||||
scope.toggleOpen = function($event) {
|
||||
if (!scope.isDisabled) {
|
||||
if (!$event || $event.which === 32) {
|
||||
scope.isOpen = !scope.isOpen;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
.directive('uibAccordionHeading', function() {
|
||||
return {
|
||||
transclude: true, // Grab the contents to be used as the heading
|
||||
template: '', // In effect remove this element!
|
||||
replace: true,
|
||||
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, 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
|
||||
.directive('uibAccordionTransclude', function() {
|
||||
return {
|
||||
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,20 +1,60 @@
|
||||
<div ng-controller="AccordionDemoCtrl">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" ng-model="oneAtATime">
|
||||
Open only one at a time
|
||||
</label>
|
||||
<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>
|
||||
|
||||
<accordion close-others="oneAtATime">
|
||||
<accordion-group heading="Static Header">
|
||||
<p>
|
||||
<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>
|
||||
|
||||
<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-small" ng-click="addItem()">Add Item</button>
|
||||
<div ng-repeat="item in items">{{item}}</div>
|
||||
</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>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
function AccordionDemoCtrl($scope) {
|
||||
angular.module('ui.bootstrap.demo').controller('AccordionDemoCtrl', function ($scope) {
|
||||
$scope.oneAtATime = true;
|
||||
|
||||
$scope.groups = [
|
||||
{
|
||||
title: "Dynamic Group Header - 1",
|
||||
content: "Dynamic Group Body - 1"
|
||||
title: 'Dynamic Group Header - 1',
|
||||
content: 'Dynamic Group Body - 1'
|
||||
},
|
||||
{
|
||||
title: "Dynamic Group Header - 2",
|
||||
content: "Dynamic Group Body - 2"
|
||||
title: 'Dynamic Group Header - 2',
|
||||
content: 'Dynamic Group Body - 2'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -18,4 +18,10 @@ function AccordionDemoCtrl($scope) {
|
||||
var newItemNo = $scope.items.length + 1;
|
||||
$scope.items.push('Item ' + newItemNo);
|
||||
};
|
||||
}
|
||||
|
||||
$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;
|
||||
@@ -0,0 +1,624 @@
|
||||
describe('uib-accordion', function() {
|
||||
var $animate, $scope;
|
||||
|
||||
beforeEach(module('ui.bootstrap.accordion'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
beforeEach(module('uib/template/accordion/accordion.html'));
|
||||
beforeEach(module('uib/template/accordion/accordion-group.html'));
|
||||
|
||||
beforeEach(inject(function(_$animate_, $rootScope) {
|
||||
$animate = _$animate_;
|
||||
$scope = $rootScope;
|
||||
}));
|
||||
|
||||
describe('controller', function () {
|
||||
var ctrl, $element, $attrs;
|
||||
beforeEach(inject(function($controller) {
|
||||
$attrs = {};
|
||||
ctrl = $controller('UibAccordionController', { $scope: $scope, $attrs: $attrs });
|
||||
}));
|
||||
|
||||
describe('addGroup', function() {
|
||||
it('adds a the specified panel to the collection', function() {
|
||||
var group1, group2;
|
||||
ctrl.addGroup(group1 = $scope.$new());
|
||||
ctrl.addGroup(group2 = $scope.$new());
|
||||
expect(ctrl.groups.length).toBe(2);
|
||||
expect(ctrl.groups[0]).toBe(group1);
|
||||
expect(ctrl.groups[1]).toBe(group2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('closeOthers', function() {
|
||||
var group1, group2, group3;
|
||||
beforeEach(function() {
|
||||
ctrl.addGroup(group1 = { isOpen: true, $on : angular.noop });
|
||||
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);
|
||||
expect(group1.isOpen).toBe(false);
|
||||
expect(group2.isOpen).toBe(true);
|
||||
expect(group3.isOpen).toBe(false);
|
||||
});
|
||||
|
||||
it('should close other panels if close-others attribute is true', function() {
|
||||
$attrs.closeOthers = 'true';
|
||||
ctrl.closeOthers(group3);
|
||||
expect(group1.isOpen).toBe(false);
|
||||
expect(group2.isOpen).toBe(false);
|
||||
expect(group3.isOpen).toBe(true);
|
||||
});
|
||||
|
||||
it('should not close other panels if close-others attribute is false', function() {
|
||||
$attrs.closeOthers = 'false';
|
||||
ctrl.closeOthers(group2);
|
||||
expect(group1.isOpen).toBe(true);
|
||||
expect(group2.isOpen).toBe(true);
|
||||
expect(group3.isOpen).toBe(true);
|
||||
});
|
||||
|
||||
describe('setting accordionConfig', function() {
|
||||
var originalCloseOthers;
|
||||
beforeEach(inject(function(uibAccordionConfig) {
|
||||
originalCloseOthers = uibAccordionConfig.closeOthers;
|
||||
uibAccordionConfig.closeOthers = false;
|
||||
}));
|
||||
|
||||
afterEach(inject(function(uibAccordionConfig) {
|
||||
// return it to the original value
|
||||
uibAccordionConfig.closeOthers = originalCloseOthers;
|
||||
}));
|
||||
|
||||
it('should not close other panels if accordionConfig.closeOthers is false', function() {
|
||||
ctrl.closeOthers(group2);
|
||||
expect(group1.isOpen).toBe(true);
|
||||
expect(group2.isOpen).toBe(true);
|
||||
expect(group3.isOpen).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeGroup', function() {
|
||||
it('should remove the specified panel', function() {
|
||||
var group1, group2, group3;
|
||||
ctrl.addGroup(group1 = $scope.$new());
|
||||
ctrl.addGroup(group2 = $scope.$new());
|
||||
ctrl.addGroup(group3 = $scope.$new());
|
||||
ctrl.removeGroup(group2);
|
||||
expect(ctrl.groups.length).toBe(2);
|
||||
expect(ctrl.groups[0]).toBe(group1);
|
||||
expect(ctrl.groups[1]).toBe(group3);
|
||||
});
|
||||
it('should ignore remove of non-existing panel', function() {
|
||||
var group1, group2;
|
||||
ctrl.addGroup(group1 = $scope.$new());
|
||||
ctrl.addGroup(group2 = $scope.$new());
|
||||
expect(ctrl.groups.length).toBe(2);
|
||||
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('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 findGroupHeading = function(index) {
|
||||
return groups.eq(index).find('.panel-heading').eq(0);
|
||||
};
|
||||
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_;
|
||||
}));
|
||||
|
||||
it('should allow custom templates', inject(function($templateCache) {
|
||||
$templateCache.put('foo/bar.html', '<div>baz</div>');
|
||||
|
||||
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 =
|
||||
'<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() {
|
||||
expect(groups.length).toEqual(2);
|
||||
expect(findGroupLink(0).text()).toEqual('title 1');
|
||||
expect(findGroupBody(0).text().trim()).toEqual('Content 1');
|
||||
expect(findGroupLink(1).text()).toEqual('title 2');
|
||||
expect(findGroupBody(1).text().trim()).toEqual('Content 2');
|
||||
});
|
||||
|
||||
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 open-class attribute', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<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'},
|
||||
{name: 'title 2', content: 'Content 2'}
|
||||
];
|
||||
|
||||
$compile(element)(scope);
|
||||
scope.$digest();
|
||||
});
|
||||
|
||||
it('should have no panels initially', function() {
|
||||
groups = element.find('.panel');
|
||||
expect(groups.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should have a panel for each model item', function() {
|
||||
scope.groups = model;
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
expect(groups.length).toEqual(2);
|
||||
expect(findGroupLink(0).text()).toEqual('title 1');
|
||||
expect(findGroupBody(0).text().trim()).toEqual('Content 1');
|
||||
expect(findGroupLink(1).text()).toEqual('title 2');
|
||||
expect(findGroupBody(1).text().trim()).toEqual('Content 2');
|
||||
});
|
||||
|
||||
it('should react properly on removing items from the model', function() {
|
||||
scope.groups = model;
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
expect(groups.length).toEqual(2);
|
||||
|
||||
scope.groups.splice(0,1);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
expect(groups.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is-open attribute', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<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);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
it('should toggle variable on element click', function() {
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(scope.open.first).toBe(true);
|
||||
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(scope.open.second).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is-open attribute with dynamic content', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<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;
|
||||
scope.open2 = false;
|
||||
angular.element(document.body).append(element);
|
||||
$compile(element)(scope);
|
||||
scope.$digest();
|
||||
$animate.flush();
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
element.remove();
|
||||
});
|
||||
|
||||
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() {
|
||||
var tpl =
|
||||
'<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},
|
||||
{name: 'title 2', content: 'Content 2', open: true}
|
||||
];
|
||||
$compile(element)(scope);
|
||||
scope.$digest();
|
||||
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
it('should toggle element on click', function() {
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(findGroupBody(0).scope().isOpen).toBe(true);
|
||||
expect(scope.groups[0].open).toBe(true);
|
||||
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(findGroupBody(0).scope().isOpen).toBe(false);
|
||||
expect(scope.groups[0].open).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is-open attribute with custom class', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<div uib-accordion-group ng-repeat="group in groups" heading="{{group.name}}" is-open="group.open" class="testClass">{{group.content}}</div>' +
|
||||
'</uib-accordion>';
|
||||
element = angular.element(tpl);
|
||||
scope.groups = [
|
||||
{name: 'title 1', content: 'Content 1', open: false},
|
||||
{name: 'title 2', content: 'Content 2', open: true}
|
||||
];
|
||||
$compile(element)(scope);
|
||||
scope.$digest();
|
||||
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
|
||||
it('should add "panel-open" class', function(){
|
||||
expect(groups.eq(0)).not.toHaveClass('panel-open');
|
||||
expect(groups.eq(1)).toHaveClass('panel-open');
|
||||
});
|
||||
});
|
||||
|
||||
describe('`is-disabled` attribute', function() {
|
||||
var groupBody;
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<uib-accordion>' +
|
||||
'<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);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
groupBody = findGroupBody(0);
|
||||
});
|
||||
|
||||
it('should open the panel with isOpen set to true', function() {
|
||||
expect(groupBody.scope().isOpen).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not toggle if disabled', function() {
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(groupBody.scope().isOpen).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should toggle after enabling', function() {
|
||||
scope.disabled = false;
|
||||
scope.$digest();
|
||||
expect(groupBody.scope().isOpen).toBeFalsy();
|
||||
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(groupBody.scope().isOpen).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have text-muted styling', function() {
|
||||
expect(findGroupLink(0).find('span:first')).toHaveClass('text-muted');
|
||||
});
|
||||
});
|
||||
|
||||
// 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 =
|
||||
'<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' +
|
||||
'</div>' +
|
||||
'</uib-accordion>';
|
||||
element = $compile(tpl)(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
|
||||
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).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('uib-accordion-heading attribute', function() {
|
||||
beforeEach(function() {
|
||||
var tpl =
|
||||
'<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' +
|
||||
'</div>' +
|
||||
'</uib-accordion>';
|
||||
element = $compile(tpl)(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.panel');
|
||||
});
|
||||
|
||||
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).scope().$id).toBe(findGroupBody(0).scope().$id);
|
||||
});
|
||||
|
||||
it('should have disabled styling when is-disabled is true', isDisabledStyleCheck);
|
||||
});
|
||||
|
||||
describe('uib-accordion-heading, with repeating uib-accordion-groups', function() {
|
||||
it('should clone the uib-accordion-heading for each group', function() {
|
||||
element = $compile('<uib-accordion><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);
|
||||
expect(findGroupLink(0).text()).toBe('1');
|
||||
expect(findGroupLink(1).text()).toBe('2');
|
||||
expect(findGroupLink(2).text()).toBe('3');
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
expect(findGroupLink(0).text()).toBe('1');
|
||||
expect(findGroupLink(1).text()).toBe('2');
|
||||
expect(findGroupLink(2).text()).toBe('3');
|
||||
});
|
||||
});
|
||||
|
||||
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');
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,299 +0,0 @@
|
||||
describe('accordion', function () {
|
||||
var $scope;
|
||||
|
||||
beforeEach(module('ui.bootstrap.accordion'));
|
||||
beforeEach(module('template/accordion/accordion.html'));
|
||||
beforeEach(module('template/accordion/accordion-group.html'));
|
||||
|
||||
beforeEach(inject(function ($rootScope) {
|
||||
$scope = $rootScope;
|
||||
}));
|
||||
|
||||
describe('controller', function () {
|
||||
|
||||
var ctrl, $element, $attrs;
|
||||
beforeEach(inject(function($controller) {
|
||||
$attrs = {}; $element = {};
|
||||
ctrl = $controller('AccordionController', { $scope: $scope, $element: $element, $attrs: $attrs });
|
||||
}));
|
||||
|
||||
describe('addGroup', function() {
|
||||
it('adds a the specified group to the collection', function() {
|
||||
var group1, group2;
|
||||
ctrl.addGroup(group1 = $scope.$new());
|
||||
ctrl.addGroup(group2 = $scope.$new());
|
||||
expect(ctrl.groups.length).toBe(2);
|
||||
expect(ctrl.groups[0]).toBe(group1);
|
||||
expect(ctrl.groups[1]).toBe(group2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('closeOthers', function() {
|
||||
var group1, group2, group3;
|
||||
beforeEach(function() {
|
||||
ctrl.addGroup(group1 = { isOpen: true, $on : angular.noop });
|
||||
ctrl.addGroup(group2 = { isOpen: true, $on : angular.noop });
|
||||
ctrl.addGroup(group3 = { isOpen: true, $on : angular.noop });
|
||||
});
|
||||
it('should close other groups if close-others attribute is not defined', function() {
|
||||
delete $attrs.closeOthers;
|
||||
ctrl.closeOthers(group2);
|
||||
expect(group1.isOpen).toBe(false);
|
||||
expect(group2.isOpen).toBe(true);
|
||||
expect(group3.isOpen).toBe(false);
|
||||
});
|
||||
|
||||
it('should close other groups if close-others attribute is true', function() {
|
||||
$attrs.closeOthers = 'true';
|
||||
ctrl.closeOthers(group3);
|
||||
expect(group1.isOpen).toBe(false);
|
||||
expect(group2.isOpen).toBe(false);
|
||||
expect(group3.isOpen).toBe(true);
|
||||
});
|
||||
|
||||
it('should not close other groups if close-others attribute is false', function() {
|
||||
$attrs.closeOthers = 'false';
|
||||
ctrl.closeOthers(group2);
|
||||
expect(group1.isOpen).toBe(true);
|
||||
expect(group2.isOpen).toBe(true);
|
||||
expect(group3.isOpen).toBe(true);
|
||||
});
|
||||
|
||||
describe('setting accordionConfig', function() {
|
||||
var originalCloseOthers;
|
||||
beforeEach(inject(function(accordionConfig) {
|
||||
originalCloseOthers = accordionConfig.closeOthers;
|
||||
accordionConfig.closeOthers = false;
|
||||
}));
|
||||
afterEach(inject(function(accordionConfig) {
|
||||
// return it to the original value
|
||||
accordionConfig.closeOthers = originalCloseOthers;
|
||||
}));
|
||||
|
||||
it('should not close other groups if accordionConfig.closeOthers is false', function() {
|
||||
ctrl.closeOthers(group2);
|
||||
expect(group1.isOpen).toBe(true);
|
||||
expect(group2.isOpen).toBe(true);
|
||||
expect(group3.isOpen).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeGroup', function() {
|
||||
it('should remove the specified group', function () {
|
||||
var group1, group2, group3;
|
||||
ctrl.addGroup(group1 = $scope.$new());
|
||||
ctrl.addGroup(group2 = $scope.$new());
|
||||
ctrl.addGroup(group3 = $scope.$new());
|
||||
ctrl.removeGroup(group2);
|
||||
expect(ctrl.groups.length).toBe(2);
|
||||
expect(ctrl.groups[0]).toBe(group1);
|
||||
expect(ctrl.groups[1]).toBe(group3);
|
||||
});
|
||||
it('should ignore remove of non-existing group', function () {
|
||||
var group1, group2;
|
||||
ctrl.addGroup(group1 = $scope.$new());
|
||||
ctrl.addGroup(group2 = $scope.$new());
|
||||
expect(ctrl.groups.length).toBe(2);
|
||||
ctrl.removeGroup({});
|
||||
expect(ctrl.groups.length).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('accordion-group', function () {
|
||||
|
||||
var scope, $compile;
|
||||
var element, groups;
|
||||
var findGroupLink = function (index) {
|
||||
return groups.eq(index).find('a').eq(0);
|
||||
};
|
||||
var findGroupBody = function (index) {
|
||||
return groups.eq(index).find('.accordion-body').eq(0);
|
||||
};
|
||||
|
||||
|
||||
beforeEach(inject(function(_$rootScope_, _$compile_) {
|
||||
scope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
}));
|
||||
|
||||
afterEach(function () {
|
||||
element = groups = scope = $compile = undefined;
|
||||
});
|
||||
|
||||
describe('with static groups', function () {
|
||||
beforeEach(function () {
|
||||
var tpl =
|
||||
"<accordion>" +
|
||||
"<accordion-group heading=\"title 1\">Content 1</accordion-group>" +
|
||||
"<accordion-group heading=\"title 2\">Content 2</accordion-group>" +
|
||||
"</accordion>";
|
||||
element = angular.element(tpl);
|
||||
angular.element(document.body).append(element);
|
||||
$compile(element)(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.accordion-group');
|
||||
});
|
||||
afterEach(function() {
|
||||
element.remove();
|
||||
});
|
||||
|
||||
it('should create accordion groups with content', function () {
|
||||
expect(groups.length).toEqual(2);
|
||||
expect(findGroupLink(0).text()).toEqual('title 1');
|
||||
expect(findGroupBody(0).text().trim()).toEqual('Content 1');
|
||||
expect(findGroupLink(1).text()).toEqual('title 2');
|
||||
expect(findGroupBody(1).text().trim()).toEqual('Content 2');
|
||||
});
|
||||
|
||||
it('should change selected element on click', function () {
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(findGroupBody(0).scope().isOpen).toBe(true);
|
||||
|
||||
findGroupLink(1).click();
|
||||
scope.$digest();
|
||||
expect(findGroupBody(0).scope().isOpen).toBe(false);
|
||||
expect(findGroupBody(1).scope().isOpen).toBe(true);
|
||||
});
|
||||
|
||||
it('should toggle element on click', function() {
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(findGroupBody(0).scope().isOpen).toBe(true);
|
||||
findGroupLink(0).click();
|
||||
scope.$digest();
|
||||
expect(findGroupBody(0).scope().isOpen).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with dynamic groups', function () {
|
||||
var model;
|
||||
beforeEach(function () {
|
||||
var tpl =
|
||||
"<accordion>" +
|
||||
"<accordion-group ng-repeat='group in groups' heading='{{group.name}}'>{{group.content}}</accordion-group>" +
|
||||
"</accordion>";
|
||||
element = angular.element(tpl);
|
||||
model = [
|
||||
{name: 'title 1', content: 'Content 1'},
|
||||
{name: 'title 2', content: 'Content 2'}
|
||||
];
|
||||
|
||||
$compile(element)(scope);
|
||||
scope.$digest();
|
||||
});
|
||||
|
||||
it('should have no groups initially', function () {
|
||||
groups = element.find('.accordion-group');
|
||||
expect(groups.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should have a group for each model item', function() {
|
||||
scope.groups = model;
|
||||
scope.$digest();
|
||||
groups = element.find('.accordion-group');
|
||||
expect(groups.length).toEqual(2);
|
||||
expect(findGroupLink(0).text()).toEqual('title 1');
|
||||
expect(findGroupBody(0).text().trim()).toEqual('Content 1');
|
||||
expect(findGroupLink(1).text()).toEqual('title 2');
|
||||
expect(findGroupBody(1).text().trim()).toEqual('Content 2');
|
||||
});
|
||||
|
||||
it('should react properly on removing items from the model', function () {
|
||||
scope.groups = model;
|
||||
scope.$digest();
|
||||
groups = element.find('.accordion-group');
|
||||
expect(groups.length).toEqual(2);
|
||||
|
||||
scope.groups.splice(0,1);
|
||||
scope.$digest();
|
||||
groups = element.find('.accordion-group');
|
||||
expect(groups.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is-open attribute', function() {
|
||||
beforeEach(function () {
|
||||
var tpl =
|
||||
"<accordion>" +
|
||||
"<accordion-group heading=\"title 1\" is-open=\"open1\">Content 1</accordion-group>" +
|
||||
"<accordion-group heading=\"title 2\" is-open=\"open2\">Content 2</accordion-group>" +
|
||||
"</accordion>";
|
||||
element = angular.element(tpl);
|
||||
scope.open1 = false;
|
||||
scope.open2 = true;
|
||||
$compile(element)(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.accordion-group');
|
||||
});
|
||||
|
||||
it('should open the group with isOpen set to true', function () {
|
||||
expect(findGroupBody(0).scope().isOpen).toBe(false);
|
||||
expect(findGroupBody(1).scope().isOpen).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is-open attribute with dynamic content', 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>";
|
||||
element = angular.element(tpl);
|
||||
scope.items = ['Item 1', 'Item 2', 'Item 3'];
|
||||
scope.open1 = true;
|
||||
scope.open2 = false;
|
||||
angular.element(document.body).append(element);
|
||||
$compile(element)(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.accordion-group');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
element.remove();
|
||||
});
|
||||
|
||||
it('should have visible group 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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('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>' +
|
||||
'Body' +
|
||||
'</accordion-group>' +
|
||||
'</accordion>';
|
||||
element = $compile(tpl)(scope);
|
||||
scope.$digest();
|
||||
groups = element.find('.accordion-group');
|
||||
});
|
||||
it('transcludes the <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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
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);
|
||||
scope.$digest();
|
||||
groups = element.find('.accordion-group');
|
||||
expect(groups.length).toBe(3);
|
||||
expect(findGroupLink(0).text()).toBe('1');
|
||||
expect(findGroupLink(1).text()).toBe('2');
|
||||
expect(findGroupLink(2).text()).toBe('3');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+29
-10
@@ -1,15 +1,34 @@
|
||||
angular.module("ui.bootstrap.alert", []).directive('alert', function () {
|
||||
angular.module('ui.bootstrap.alert', [])
|
||||
|
||||
.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('uibAlert', function() {
|
||||
return {
|
||||
restrict:'EA',
|
||||
templateUrl:'template/alert/alert.html',
|
||||
transclude:true,
|
||||
replace:true,
|
||||
scope: {
|
||||
type: '=',
|
||||
close: '&'
|
||||
controller: 'UibAlertController',
|
||||
controllerAs: 'alert',
|
||||
restrict: 'A',
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/alert/alert.html';
|
||||
},
|
||||
link: function(scope, iElement, iAttrs, controller) {
|
||||
scope.closeable = "close" in iAttrs;
|
||||
transclude: true,
|
||||
scope: {
|
||||
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' 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,15 +1,14 @@
|
||||
function AlertDemoCtrl($scope) {
|
||||
angular.module('ui.bootstrap.demo').controller('AlertDemoCtrl', function ($scope) {
|
||||
$scope.alerts = [
|
||||
{ type: 'error', msg: 'Oh snap! Change a few things up and try submitting again.' },
|
||||
{ type: 'danger', msg: 'Oh snap! Change a few things up and try submitting again.' },
|
||||
{ type: 'success', msg: 'Well done! You successfully read this important alert message.' }
|
||||
];
|
||||
|
||||
$scope.addAlert = function() {
|
||||
$scope.alerts.push({msg: "Another alert!"});
|
||||
$scope.alerts.push({msg: 'Another alert!'});
|
||||
};
|
||||
|
||||
$scope.closeAlert = function(index) {
|
||||
$scope.alerts.splice(index, 1);
|
||||
};
|
||||
|
||||
}
|
||||
});
|
||||
@@ -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,22 +1,22 @@
|
||||
describe("alert", function () {
|
||||
|
||||
var scope, ctrl, model, $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_, $controller) {
|
||||
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'},
|
||||
@@ -32,48 +32,98 @@ describe("alert", function () {
|
||||
}
|
||||
|
||||
function findCloseButton(index) {
|
||||
return element.find('.alert button').eq(index);
|
||||
return element.find('.close').eq(index);
|
||||
}
|
||||
|
||||
it("should generate alerts using ng-repeat", function () {
|
||||
function findContent(index) {
|
||||
return element.find('div[ng-transclude]').eq(index);
|
||||
}
|
||||
|
||||
it('should expose the controller to the view', function() {
|
||||
$templateCache.put('uib/template/alert/alert.html', '<div>{{alert.text}}</div>');
|
||||
|
||||
element = $compile('<div uib-alert></div>')(scope);
|
||||
scope.$digest();
|
||||
|
||||
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 use correct classes for different alert types", function () {
|
||||
it('should show the alert content', function() {
|
||||
var alerts = createAlerts();
|
||||
expect(alerts.eq(0)).toHaveClass('alert-success');
|
||||
expect(alerts.eq(1)).toHaveClass('alert-error');
|
||||
|
||||
//defaults
|
||||
expect(alerts.eq(2)).toHaveClass('alert');
|
||||
expect(alerts.eq(2)).not.toHaveClass('alert-info');
|
||||
expect(alerts.eq(2)).not.toHaveClass('alert-block');
|
||||
for (var i = 0, n = alerts.length; i < n; i++) {
|
||||
expect(findContent(i).text()).toBe(scope.alerts[i].msg);
|
||||
}
|
||||
});
|
||||
|
||||
it("should fire callback when closed", function () {
|
||||
|
||||
it('should show close buttons and have the dismissible class', function() {
|
||||
var alerts = createAlerts();
|
||||
|
||||
scope.$apply(function () {
|
||||
for (var i = 0, n = alerts.length; i < n; i++) {
|
||||
expect(findCloseButton(i).css('display')).not.toBe('none');
|
||||
expect(alerts.eq(i)).toHaveClass('alert-dismissible');
|
||||
}
|
||||
});
|
||||
|
||||
it('should fire callback when closed', function() {
|
||||
var alerts = createAlerts();
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.removeAlert = jasmine.createSpy();
|
||||
});
|
||||
|
||||
expect(findCloseButton(0).css('display')).not.toBe('none');
|
||||
findCloseButton(1).click();
|
||||
|
||||
expect(scope.removeAlert).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('should not show close buttons if no close callback specified', function () {
|
||||
var 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).length).toEqual(0);
|
||||
expect(findCloseButton(0)).toBeHidden();
|
||||
expect(element).not.toHaveClass('alert-dismissible');
|
||||
});
|
||||
|
||||
it('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();
|
||||
});
|
||||
});
|
||||
|
||||
+94
-76
@@ -1,76 +1,94 @@
|
||||
angular.module('ui.bootstrap.buttons', [])
|
||||
|
||||
.constant('buttonConfig', {
|
||||
activeClass:'active',
|
||||
toggleEvent:'click'
|
||||
})
|
||||
|
||||
.directive('btnRadio', ['buttonConfig', function (buttonConfig) {
|
||||
var activeClass = buttonConfig.activeClass || 'active';
|
||||
var toggleEvent = buttonConfig.toggleEvent || 'click';
|
||||
|
||||
return {
|
||||
|
||||
require:'ngModel',
|
||||
link:function (scope, element, attrs, ngModelCtrl) {
|
||||
|
||||
var value = scope.$eval(attrs.btnRadio);
|
||||
|
||||
//model -> UI
|
||||
scope.$watch(function () {
|
||||
return ngModelCtrl.$modelValue;
|
||||
}, function (modelValue) {
|
||||
if (angular.equals(modelValue, value)){
|
||||
element.addClass(activeClass);
|
||||
} else {
|
||||
element.removeClass(activeClass);
|
||||
}
|
||||
});
|
||||
|
||||
//ui->model
|
||||
element.bind(toggleEvent, function () {
|
||||
if (!element.hasClass(activeClass)) {
|
||||
scope.$apply(function () {
|
||||
ngModelCtrl.$setViewValue(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('btnCheckbox', ['buttonConfig', function (buttonConfig) {
|
||||
|
||||
var activeClass = buttonConfig.activeClass || 'active';
|
||||
var toggleEvent = buttonConfig.toggleEvent || 'click';
|
||||
|
||||
return {
|
||||
require:'ngModel',
|
||||
link:function (scope, element, attrs, ngModelCtrl) {
|
||||
|
||||
var trueValue = scope.$eval(attrs.btnCheckboxTrue);
|
||||
var falseValue = scope.$eval(attrs.btnCheckboxFalse);
|
||||
|
||||
trueValue = angular.isDefined(trueValue) ? trueValue : true;
|
||||
falseValue = angular.isDefined(falseValue) ? falseValue : false;
|
||||
|
||||
//model -> UI
|
||||
scope.$watch(function () {
|
||||
return ngModelCtrl.$modelValue;
|
||||
}, function (modelValue) {
|
||||
if (angular.equals(modelValue, trueValue)) {
|
||||
element.addClass(activeClass);
|
||||
} else {
|
||||
element.removeClass(activeClass);
|
||||
}
|
||||
});
|
||||
|
||||
//ui->model
|
||||
element.bind(toggleEvent, function () {
|
||||
scope.$apply(function () {
|
||||
ngModelCtrl.$setViewValue(element.hasClass(activeClass) ? falseValue : trueValue);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
angular.module('ui.bootstrap.buttons', [])
|
||||
|
||||
.constant('uibButtonConfig', {
|
||||
activeClass: 'active',
|
||||
toggleEvent: 'click'
|
||||
})
|
||||
|
||||
.controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
|
||||
this.activeClass = buttonConfig.activeClass || 'active';
|
||||
this.toggleEvent = buttonConfig.toggleEvent || 'click';
|
||||
}])
|
||||
|
||||
.directive('uibBtnRadio', ['$parse', function($parse) {
|
||||
return {
|
||||
require: ['uibBtnRadio', 'ngModel'],
|
||||
controller: 'UibButtonsController',
|
||||
controllerAs: 'buttons',
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
||||
var uncheckableExpr = $parse(attrs.uibUncheckable);
|
||||
|
||||
element.find('input').css({display: 'none'});
|
||||
|
||||
//model -> UI
|
||||
ngModelCtrl.$render = function() {
|
||||
element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
|
||||
};
|
||||
|
||||
//ui->model
|
||||
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.uibBtnRadio));
|
||||
ngModelCtrl.$render();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (attrs.uibUncheckable) {
|
||||
scope.$watch(uncheckableExpr, function(uncheckable) {
|
||||
attrs.$set('uncheckable', uncheckable ? '' : undefined);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('uibBtnCheckbox', function() {
|
||||
return {
|
||||
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);
|
||||
}
|
||||
|
||||
function getFalseValue() {
|
||||
return getCheckboxValue(attrs.btnCheckboxFalse, false);
|
||||
}
|
||||
|
||||
function getCheckboxValue(attribute, defaultValue) {
|
||||
return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
|
||||
}
|
||||
|
||||
//model -> UI
|
||||
ngModelCtrl.$render = function() {
|
||||
element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
|
||||
};
|
||||
|
||||
//ui->model
|
||||
element.on(buttonsCtrl.toggleEvent, function() {
|
||||
if (attrs.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.$apply(function() {
|
||||
ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
|
||||
ngModelCtrl.$render();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
+32
-21
@@ -1,21 +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">
|
||||
Single Toggle
|
||||
</button>
|
||||
<h4>Checkbox</h4>
|
||||
<pre>{{checkModel}}</pre>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary" ng-model="checkModel.left" btn-checkbox>Left</button>
|
||||
<button type="button" class="btn btn-primary" ng-model="checkModel.middle" btn-checkbox>Middle</button>
|
||||
<button type="button" class="btn btn-primary" ng-model="checkModel.right" btn-checkbox>Right</button>
|
||||
</div>
|
||||
<h4>Radio</h4>
|
||||
<pre>{{radioModel}}</pre>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary" ng-model="radioModel" btn-radio="'Left'">Left</button>
|
||||
<button type="button" class="btn btn-primary" ng-model="radioModel" btn-radio="'Middle'">Middle</button>
|
||||
<button type="button" class="btn btn-primary" ng-model="radioModel" btn-radio="'Right'">Right</button>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-controller="ButtonsCtrl">
|
||||
<h4>Single toggle</h4>
|
||||
<pre>{{singleModel}}</pre>
|
||||
<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>Model: {{checkModel}}</pre>
|
||||
<pre>Results: {{checkResults}}</pre>
|
||||
<div class="btn-group">
|
||||
<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" 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" 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>
|
||||
<button class="btn btn-default" ng-click="uncheckable = !uncheckable">
|
||||
Toggle uncheckable
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+22
-12
@@ -1,12 +1,22 @@
|
||||
var ButtonsCtrl = function ($scope) {
|
||||
|
||||
$scope.singleModel = 1;
|
||||
|
||||
$scope.radioModel = 'Middle';
|
||||
|
||||
$scope.checkModel = {
|
||||
left: false,
|
||||
middle: true,
|
||||
right: false
|
||||
};
|
||||
};
|
||||
angular.module('ui.bootstrap.demo').controller('ButtonsCtrl', function ($scope) {
|
||||
$scope.singleModel = 1;
|
||||
|
||||
$scope.radioModel = 'Middle';
|
||||
|
||||
$scope.checkModel = {
|
||||
left: false,
|
||||
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,2 +1,50 @@
|
||||
There are 2 directives that can make a group of buttons to behave like a set of checkboxes or radio buttons.
|
||||
|
||||
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,94 +1,366 @@
|
||||
describe('buttons', function () {
|
||||
|
||||
var $scope, $compile;
|
||||
|
||||
beforeEach(module('ui.bootstrap.buttons'));
|
||||
beforeEach(inject(function (_$rootScope_, _$compile_) {
|
||||
$scope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
}));
|
||||
|
||||
describe('checkbox', function () {
|
||||
|
||||
var compileButton = function (markup, scope) {
|
||||
var el = $compile(markup)(scope);
|
||||
scope.$digest();
|
||||
return el;
|
||||
};
|
||||
|
||||
//model -> UI
|
||||
it('should work correctly with default model values', function () {
|
||||
$scope.model = false;
|
||||
var btn = compileButton('<button ng-model="model" btn-checkbox>click</button>', $scope);
|
||||
expect(btn).not.toHaveClass('active');
|
||||
|
||||
$scope.model = true;
|
||||
$scope.$digest();
|
||||
expect(btn).toHaveClass('active');
|
||||
});
|
||||
|
||||
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);
|
||||
expect(btn).toHaveClass('active');
|
||||
|
||||
$scope.model = 0;
|
||||
$scope.$digest();
|
||||
expect(btn).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
//UI-> model
|
||||
it('should toggle default model values on click', function () {
|
||||
$scope.model = false;
|
||||
var btn = compileButton('<button ng-model="model" btn-checkbox>click</button>', $scope);
|
||||
|
||||
btn.click();
|
||||
expect($scope.model).toEqual(true);
|
||||
btn.click();
|
||||
expect($scope.model).toEqual(false);
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
btn.click();
|
||||
expect($scope.model).toEqual(1);
|
||||
btn.click();
|
||||
expect($scope.model).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('radio', function () {
|
||||
|
||||
var compileButtons = function (markup, scope) {
|
||||
var el = $compile('<div>'+markup+'</div>')(scope);
|
||||
scope.$digest();
|
||||
return el.find('button');
|
||||
};
|
||||
|
||||
//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);
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
|
||||
$scope.model = 2;
|
||||
$scope.$digest();
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
expect(btns.eq(1)).toHaveClass('active');
|
||||
});
|
||||
|
||||
//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);
|
||||
expect($scope.model).toBeUndefined();
|
||||
|
||||
btns.eq(0).click();
|
||||
expect($scope.model).toEqual(1);
|
||||
|
||||
btns.eq(1).click();
|
||||
expect($scope.model).toEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('buttons', function() {
|
||||
|
||||
var $scope, $compile;
|
||||
|
||||
beforeEach(module('ui.bootstrap.buttons'));
|
||||
beforeEach(inject(function(_$rootScope_, _$compile_) {
|
||||
$scope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
}));
|
||||
|
||||
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() {
|
||||
$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 bind custom model values', function() {
|
||||
$scope.model = 1;
|
||||
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;
|
||||
$scope.$digest();
|
||||
expect(btn).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
//UI-> model
|
||||
it('should toggle default model values on click', function() {
|
||||
$scope.model = false;
|
||||
var btn = compileButton('<button ng-model="model" uib-btn-checkbox>click</button>', $scope);
|
||||
|
||||
btn.click();
|
||||
expect($scope.model).toEqual(true);
|
||||
expect(btn).toHaveClass('active');
|
||||
|
||||
btn.click();
|
||||
expect($scope.model).toEqual(false);
|
||||
expect(btn).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
it('should toggle custom model values on click', function() {
|
||||
$scope.model = 0;
|
||||
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);
|
||||
expect(btn).toHaveClass('active');
|
||||
|
||||
btn.click();
|
||||
expect($scope.model).toEqual(0);
|
||||
expect(btn).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
it('should monitor true / false value changes - issue 666', function() {
|
||||
|
||||
$scope.model = 1;
|
||||
$scope.trueVal = 1;
|
||||
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);
|
||||
|
||||
$scope.model = 2;
|
||||
$scope.trueVal = 2;
|
||||
$scope.$digest();
|
||||
|
||||
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) {
|
||||
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 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');
|
||||
|
||||
$scope.model = 2;
|
||||
$scope.$digest();
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
expect(btns.eq(1)).toHaveClass('active');
|
||||
});
|
||||
|
||||
//UI->model
|
||||
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();
|
||||
expect($scope.model).toEqual(1);
|
||||
expect(btns.eq(0)).toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
|
||||
btns.eq(1).click();
|
||||
expect($scope.model).toEqual(2);
|
||||
expect(btns.eq(1)).toHaveClass('active');
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
it('should watch uib-btn-radio values and update state accordingly', function() {
|
||||
$scope.values = ['value1', 'value2'];
|
||||
|
||||
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');
|
||||
|
||||
$scope.model = 'value2';
|
||||
$scope.$digest();
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
expect(btns.eq(1)).toHaveClass('active');
|
||||
|
||||
$scope.values[1] = 'value3';
|
||||
$scope.model = 'value3';
|
||||
$scope.$digest();
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
expect(btns.eq(1)).toHaveClass('active');
|
||||
});
|
||||
|
||||
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" 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');
|
||||
|
||||
$scope.model = 2;
|
||||
$scope.$digest();
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
expect(btns.eq(1)).toHaveClass('active');
|
||||
});
|
||||
|
||||
//UI->model
|
||||
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();
|
||||
expect($scope.model).toEqual(1);
|
||||
expect(btns.eq(0)).toHaveClass('active');
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
|
||||
btns.eq(0).click();
|
||||
expect($scope.model).toBeNull();
|
||||
expect(btns.eq(1)).not.toHaveClass('active');
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
});
|
||||
|
||||
it('should watch uib-btn-radio values and update state', function() {
|
||||
$scope.values = ['value1', 'value2'];
|
||||
|
||||
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');
|
||||
|
||||
$scope.model = 'value2';
|
||||
$scope.$digest();
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
expect(btns.eq(1)).toHaveClass('active');
|
||||
|
||||
$scope.model = undefined;
|
||||
$scope.$digest();
|
||||
expect(btns.eq(0)).not.toHaveClass('active');
|
||||
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
|
||||
}
|
||||
+303
-143
@@ -1,185 +1,296 @@
|
||||
/*
|
||||
*
|
||||
* AngularJS Bootstrap Carousel
|
||||
*
|
||||
* A pure AngularJS carousel.
|
||||
*
|
||||
* For no interval set the interval to non-number, or milliseconds of desired interval
|
||||
* Template: <carousel interval="none"><slide>{{anything}}</slide></carousel>
|
||||
* To change the carousel's active slide set the active attribute to true
|
||||
* Template: <carousel interval="none"><slide active="someModel">{{anything}}</slide></carousel>
|
||||
*/
|
||||
angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
|
||||
.controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope, $timeout, $transition, $q) {
|
||||
angular.module('ui.bootstrap.carousel', [])
|
||||
|
||||
.controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
|
||||
var self = this,
|
||||
slides = self.slides = [],
|
||||
currentIndex = -1,
|
||||
currentTimeout, isPlaying;
|
||||
self.currentSlide = null;
|
||||
slides = self.slides = $scope.slides = [],
|
||||
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 = function(nextSlide, direction) {
|
||||
var nextIndex = slides.indexOf(nextSlide);
|
||||
self.select = $scope.select = function(nextSlide, direction) {
|
||||
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() {
|
||||
//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);
|
||||
nextSlide.$element[0].offsetWidth = 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);
|
||||
}
|
||||
};
|
||||
|
||||
/* 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;
|
||||
return self.select(slides[newIndex], 'next');
|
||||
};
|
||||
|
||||
$scope.prev = function() {
|
||||
var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1;
|
||||
return self.select(slides[newIndex], 'prev');
|
||||
};
|
||||
|
||||
$scope.select = function(slide) {
|
||||
self.select(slide);
|
||||
$scope.indexOfSlide = function(slide) {
|
||||
return +slide.slide.index;
|
||||
};
|
||||
|
||||
$scope.isActive = function(slide) {
|
||||
return self.currentSlide === slide;
|
||||
return $scope.active === slide.slide.index;
|
||||
};
|
||||
|
||||
$scope.slides = function() {
|
||||
return slides;
|
||||
$scope.isPrevDisabled = function() {
|
||||
return $scope.active === 0 && $scope.noWrap();
|
||||
};
|
||||
|
||||
$scope.$watch('interval', restartTimer);
|
||||
function restartTimer() {
|
||||
if (currentTimeout) {
|
||||
$timeout.cancel(currentTimeout);
|
||||
$scope.isNextDisabled = function() {
|
||||
return $scope.active === slides.length - 1 && $scope.noWrap();
|
||||
};
|
||||
|
||||
$scope.pause = function() {
|
||||
if (!$scope.noPause) {
|
||||
isPlaying = false;
|
||||
resetTimer();
|
||||
}
|
||||
function go() {
|
||||
if (isPlaying) {
|
||||
$scope.next();
|
||||
restartTimer();
|
||||
} else {
|
||||
$scope.pause();
|
||||
}
|
||||
}
|
||||
var interval = +$scope.interval;
|
||||
if (!isNaN(interval) && interval>=0) {
|
||||
currentTimeout = $timeout(go, interval);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.play = function() {
|
||||
if (!isPlaying) {
|
||||
isPlaying = true;
|
||||
restartTimer();
|
||||
}
|
||||
};
|
||||
$scope.pause = function() {
|
||||
isPlaying = false;
|
||||
if (currentTimeout) {
|
||||
$timeout.cancel(currentTimeout);
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
$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;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
slide.active = false;
|
||||
}
|
||||
};
|
||||
|
||||
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 {
|
||||
var slide = slides[index];
|
||||
if (slide) {
|
||||
setActive(index);
|
||||
self.select(slides[index]);
|
||||
currentIndex = index;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function getSlideByIndex(index) {
|
||||
for (var i = 0, l = slides.length; i < l; ++i) {
|
||||
if (slides[i].index === index) {
|
||||
return slides[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setActive(index) {
|
||||
for (var i = 0; i < slides.length; i++) {
|
||||
slides[i].slide.active = i === index;
|
||||
}
|
||||
}
|
||||
|
||||
function goNext(slide, index, direction) {
|
||||
if (destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
angular.extend(slide, {direction: direction});
|
||||
angular.extend(slides[currentIndex].slide || {}, {direction: direction});
|
||||
if ($animate.enabled($element) && !$scope.$currentTransition &&
|
||||
slides[index].element && self.slides.length > 1) {
|
||||
slides[index].element.data(SLIDE_DIRECTION, slide.direction);
|
||||
var currentIdx = self.getCurrentIndex();
|
||||
|
||||
if (angular.isNumber(currentIdx) && slides[currentIdx].element) {
|
||||
slides[currentIdx].element.data(SLIDE_DIRECTION, slide.direction);
|
||||
}
|
||||
|
||||
$scope.$currentTransition = true;
|
||||
$animate.on('addClass', slides[index].element, function(element, phase) {
|
||||
if (phase === 'close') {
|
||||
$scope.$currentTransition = null;
|
||||
$animate.off('addClass', element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.active = slide.index;
|
||||
currentIndex = slide.index;
|
||||
setActive(index);
|
||||
|
||||
//every time you change slides, reset the timer
|
||||
restartTimer();
|
||||
}
|
||||
|
||||
function findSlideIndex(slide) {
|
||||
for (var i = 0; i < slides.length; i++) {
|
||||
if (slides[i].slide === slide) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}])
|
||||
.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: '='
|
||||
noTransition: '=',
|
||||
noPause: '=',
|
||||
noWrap: '&'
|
||||
}
|
||||
};
|
||||
}])
|
||||
.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() {
|
||||
@@ -187,10 +298,59 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -1,3 +1,57 @@
|
||||
Carousel creates a carousel similar to bootstrap's image carousel.
|
||||
|
||||
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.
|
||||
The carousel also offers support for touchscreen devices in the form of swiping. To enable swiping, load the `ngTouch` module as a dependency.
|
||||
|
||||
Use a `<uib-carousel>` element with `<uib-slide>` elements inside it.
|
||||
|
||||
### 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.
|
||||
|
||||
+23
-20
@@ -1,26 +1,29 @@
|
||||
<div ng-controller="CarouselDemoCtrl">
|
||||
<carousel interval="myInterval">
|
||||
<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 style="height: 305px">
|
||||
<div uib-carousel active="active" interval="myInterval" no-wrap="noWrapSlides">
|
||||
<div uib-slide ng-repeat="slide in slides track by slide.id" index="slide.id">
|
||||
<img ng-src="{{slide.image}}" style="margin:auto;">
|
||||
<div class="carousel-caption">
|
||||
<h4>Slide {{slide.id}}</h4>
|
||||
<p>{{slide.text}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</slide>
|
||||
</carousel>
|
||||
<div class="row-fluid">
|
||||
<div class="span6">
|
||||
<ul>
|
||||
<li ng-repeat="slide in slides">
|
||||
<button class="btn btn-mini" ng-class="{'btn-info': !slide.active, 'btn-success': slide.active}" ng-disabled="slide.active" ng-click="slide.active = true">select</button>
|
||||
{{$index}}: {{slide.text}}
|
||||
</li>
|
||||
</ul>
|
||||
<a class="btn" ng-click="addSlide()">Add Slide</a>
|
||||
</div>
|
||||
<div class="span6">
|
||||
Interval, in milliseconds: <input type="number" ng-model="myInterval">
|
||||
<br />Enter a negative number to stop the interval.
|
||||
</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 or 0 to stop the interval.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+53
-12
@@ -1,16 +1,57 @@
|
||||
function CarouselDemoCtrl($scope) {
|
||||
angular.module('ui.bootstrap.demo').controller('CarouselDemoCtrl', function ($scope) {
|
||||
$scope.myInterval = 5000;
|
||||
$scope.slides = [
|
||||
{image: 'http://placekitten.com/200/200',text: 'Kitten.'},
|
||||
{image: 'http://placekitten.com/225/200',text: 'Kitty!'},
|
||||
{image: 'http://placekitten.com/250/200',text: 'Cat.'},
|
||||
{image: 'http://placekitten.com/275/200',text: 'Feline!'}
|
||||
];
|
||||
$scope.noWrapSlides = false;
|
||||
$scope.active = 0;
|
||||
var slides = $scope.slides = [];
|
||||
var currIndex = 0;
|
||||
|
||||
$scope.addSlide = function() {
|
||||
$scope.slides.push({
|
||||
image: 'http://placekitten.com/'+(200+25*Math.floor(Math.random()*4))+'/200',
|
||||
text: ['More','Extra','Lots of','Surplus'][Math.floor(Math.random()*4)] + ' ' +
|
||||
['Cats', 'Kittys', 'Felines', 'Cutes'][Math.floor(Math.random()*4)]
|
||||
var newWidth = 600 + slides.length + 1;
|
||||
slides.push({
|
||||
image: '//unsplash.it/' + newWidth + '/300',
|
||||
text: ['Nice image','Awesome photograph','That is so cool','I love that'][slides.length % 4],
|
||||
id: currIndex++
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
$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');
|
||||
+428
-102
@@ -1,53 +1,76 @@
|
||||
describe('carousel', function() {
|
||||
beforeEach(module('ui.bootstrap.carousel'));
|
||||
beforeEach(module('template/carousel/carousel.html', 'template/carousel/slide.html'));
|
||||
|
||||
var $rootScope, elm, $compile, $controller, $timeout;
|
||||
beforeEach(inject(function(_$rootScope_, _$compile_, _$controller_, _$timeout_) {
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
beforeEach(module('uib/template/carousel/carousel.html', 'uib/template/carousel/slide.html'));
|
||||
|
||||
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() {
|
||||
var elm, scope, carouselScope;
|
||||
var elm, scope;
|
||||
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">' +
|
||||
'<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);
|
||||
carouselScope = elm.scope();
|
||||
scope.interval = 5000;
|
||||
scope.nopause = undefined;
|
||||
scope.$apply();
|
||||
});
|
||||
afterEach(function() {
|
||||
scope.$destroy();
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -63,34 +86,110 @@ describe('carousel', function() {
|
||||
var indicators = elm.find('ol.carousel-indicators > li');
|
||||
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);
|
||||
|
||||
|
||||
var navNext = elm.find('a.right');
|
||||
expect(navNext.length).toBe(0);
|
||||
|
||||
|
||||
var navPrev = elm.find('a.left');
|
||||
expect(navPrev.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should show navigation when there are 3 slides', function () {
|
||||
|
||||
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);
|
||||
|
||||
|
||||
var navNext = elm.find('a.right');
|
||||
expect(navNext.length).not.toBe(0);
|
||||
|
||||
|
||||
var navPrev = elm.find('a.left');
|
||||
expect(navPrev.length).not.toBe(0);
|
||||
});
|
||||
@@ -123,23 +222,28 @@ describe('carousel', function() {
|
||||
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');
|
||||
@@ -159,112 +263,334 @@ 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);
|
||||
});
|
||||
|
||||
it('should not pause on mouseover if noPause', function() {
|
||||
scope.$apply('nopause = true');
|
||||
testSlideActive(0);
|
||||
elm.trigger('mouseenter');
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(1);
|
||||
elm.trigger('mouseleave');
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(2);
|
||||
});
|
||||
|
||||
it('should remove slide from dom and change active slide', function() {
|
||||
scope.$apply('slides[1].active = true');
|
||||
testSlideActive(1);
|
||||
scope.$apply('slides.splice(1,1)');
|
||||
scope.$apply('active = 2');
|
||||
testSlideActive(2);
|
||||
scope.$apply('slides.splice(2,1)');
|
||||
$timeout.flush(0);
|
||||
expect(elm.find('div.item').length).toBe(2);
|
||||
testSlideActive(1);
|
||||
$interval.flush(scope.interval);
|
||||
testSlideActive(0);
|
||||
scope.$apply('slides.splice(1,1)');
|
||||
$timeout.flush(0);
|
||||
expect(elm.find('div.item').length).toBe(1);
|
||||
testSlideActive(0);
|
||||
});
|
||||
|
||||
it('should change dom when you reassign ng-repeat slides array', function() {
|
||||
scope.slides=[{content:'new1'},{content:'new2'},{content:'new3'}];
|
||||
scope.slides = [
|
||||
{content:'new1', index: 4},
|
||||
{content:'new2', index: 5},
|
||||
{content:'new3', index: 6}
|
||||
];
|
||||
scope.$apply();
|
||||
var contents = elm.find('div.item');
|
||||
var contents = elm.find('div.item [ng-transclude]');
|
||||
expect(contents.length).toBe(3);
|
||||
expect(contents.eq(0).text()).toBe('new1');
|
||||
expect(contents.eq(1).text()).toBe('new2');
|
||||
expect(contents.eq(2).text()).toBe('new3');
|
||||
scope.$apply('slides.splice(0,1)');
|
||||
contents = elm.find('div.item');
|
||||
});
|
||||
|
||||
it('should not change if next is clicked while transitioning', function() {
|
||||
var carouselScope = elm.children().scope();
|
||||
var next = elm.find('a.right');
|
||||
|
||||
testSlideActive(0);
|
||||
carouselScope.$currentTransition = true;
|
||||
next.click();
|
||||
|
||||
testSlideActive(0);
|
||||
|
||||
carouselScope.$currentTransition = null;
|
||||
next.click();
|
||||
testSlideActive(1);
|
||||
});
|
||||
|
||||
it('should buffer the slides if transition is clicked and only transition to the last requested', function() {
|
||||
var carouselScope = elm.children().scope();
|
||||
|
||||
testSlideActive(0);
|
||||
carouselScope.$currentTransition = null;
|
||||
carouselScope.select(carouselScope.slides[1]);
|
||||
$animate.flush();
|
||||
|
||||
testSlideActive(1);
|
||||
|
||||
carouselScope.$currentTransition = true;
|
||||
carouselScope.select(carouselScope.slides[2]);
|
||||
scope.$apply();
|
||||
|
||||
testSlideActive(1);
|
||||
|
||||
carouselScope.select(carouselScope.slides[0]);
|
||||
scope.$apply();
|
||||
|
||||
testSlideActive(1);
|
||||
|
||||
carouselScope.$currentTransition = null;
|
||||
$interval.flush(scope.interval);
|
||||
$animate.flush();
|
||||
|
||||
testSlideActive(2);
|
||||
|
||||
$interval.flush(scope.interval);
|
||||
$animate.flush();
|
||||
|
||||
testSlideActive(0);
|
||||
});
|
||||
|
||||
it('issue 1414 - should not continue running timers after scope is destroyed', function() {
|
||||
testSlideActive(0);
|
||||
$interval.flush(scope.interval);
|
||||
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('new2');
|
||||
expect(contents.eq(0)).toHaveClass('active');
|
||||
expect(contents.eq(1).text()).toBe('new3');
|
||||
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]);
|
||||
}
|
||||
});
|
||||
afterEach(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.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);
|
||||
});
|
||||
|
||||
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 be exposed in the template', inject(function($templateCache) {
|
||||
$templateCache.put('uib/template/carousel/carousel.html', '<div>{{carousel.text}}</div>');
|
||||
|
||||
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);
|
||||
});
|
||||
var scope = $rootScope.$new();
|
||||
var elm = $compile('<div uib-carousel interval="bar" no-transition="false" no-pause="true"></div>')(scope);
|
||||
$rootScope.$digest();
|
||||
|
||||
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);
|
||||
});
|
||||
var ctrl = elm.controller('uibCarousel');
|
||||
|
||||
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]);
|
||||
});
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
+124
-86
@@ -1,95 +1,133 @@
|
||||
angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition'])
|
||||
angular.module('ui.bootstrap.collapse', [])
|
||||
|
||||
// The collapsible directive indicates a block of html that will expand and collapse
|
||||
.directive('collapse', ['$transition', function($transition) {
|
||||
// CSS transitions don't work with height: auto, so we have to manually change the height to a
|
||||
// specific value and then once the animation completes, we can reset the height to auto.
|
||||
// Unfortunately if you do this while the CSS transitions are specified (i.e. in the CSS class
|
||||
// "collapse") then you trigger a change to height 0 in between.
|
||||
// The fix is to remove the "collapse" CSS class while changing the height back to auto - phew!
|
||||
var fixUpHeight = function(scope, element, height) {
|
||||
// We remove the collapse CSS class to prevent a transition when we change to height: auto
|
||||
element.removeClass('collapse');
|
||||
element.css({ height: height });
|
||||
// It appears that reading offsetWidth makes the browser realise that we have changed the
|
||||
// height already :-/
|
||||
var x = element[0].offsetWidth;
|
||||
element.addClass('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) {
|
||||
var expandingExpr = $parse(attrs.expanding),
|
||||
expandedExpr = $parse(attrs.expanded),
|
||||
collapsingExpr = $parse(attrs.collapsing),
|
||||
collapsedExpr = $parse(attrs.collapsed),
|
||||
horizontal = false,
|
||||
css = {},
|
||||
cssTo = {};
|
||||
|
||||
return {
|
||||
link: function(scope, element, attrs) {
|
||||
init();
|
||||
|
||||
var isCollapsed;
|
||||
var initialAnimSkip = true;
|
||||
scope.$watch(function (){ return element[0].scrollHeight; }, function (value) {
|
||||
//The listener is called when scollHeight changes
|
||||
//It actually does on 2 scenarios:
|
||||
// 1. Parent is set to display none
|
||||
// 2. angular bindings inside are resolved
|
||||
//When we have a change of scrollHeight we are setting again the correct height if the group is opened
|
||||
if (element[0].scrollHeight !== 0) {
|
||||
if (!isCollapsed) {
|
||||
if (initialAnimSkip) {
|
||||
fixUpHeight(scope, element, element[0].scrollHeight + 'px');
|
||||
} else {
|
||||
fixUpHeight(scope, element, 'auto');
|
||||
}
|
||||
function init() {
|
||||
horizontal = !!('horizontal' in attrs);
|
||||
if (horizontal) {
|
||||
css = {
|
||||
width: ''
|
||||
};
|
||||
cssTo = {width: '0'};
|
||||
} else {
|
||||
css = {
|
||||
height: ''
|
||||
};
|
||||
cssTo = {height: '0'};
|
||||
}
|
||||
if (!scope.$eval(attrs.uibCollapse)) {
|
||||
element.addClass('in')
|
||||
.addClass('collapse')
|
||||
.attr('aria-expanded', true)
|
||||
.attr('aria-hidden', false)
|
||||
.css(css);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch(attrs.collapse, function(value) {
|
||||
if (value) {
|
||||
collapse();
|
||||
} else {
|
||||
expand();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var currentTransition;
|
||||
var doTransition = function(change) {
|
||||
if ( currentTransition ) {
|
||||
currentTransition.cancel();
|
||||
}
|
||||
currentTransition = $transition(element,change);
|
||||
currentTransition.then(
|
||||
function() { currentTransition = undefined; },
|
||||
function() { currentTransition = undefined; }
|
||||
);
|
||||
return currentTransition;
|
||||
};
|
||||
|
||||
var expand = function() {
|
||||
if (initialAnimSkip) {
|
||||
initialAnimSkip = false;
|
||||
if ( !isCollapsed ) {
|
||||
fixUpHeight(scope, element, 'auto');
|
||||
function getScrollFromElement(element) {
|
||||
if (horizontal) {
|
||||
return {width: element.scrollWidth + 'px'};
|
||||
}
|
||||
} else {
|
||||
doTransition({ height : element[0].scrollHeight + 'px' })
|
||||
.then(function() {
|
||||
// This check ensures that we don't accidentally update the height if the user has closed
|
||||
// the group while the animation was still running
|
||||
if ( !isCollapsed ) {
|
||||
fixUpHeight(scope, element, 'auto');
|
||||
}
|
||||
});
|
||||
return {height: element.scrollHeight + 'px'};
|
||||
}
|
||||
isCollapsed = false;
|
||||
};
|
||||
|
||||
var collapse = function() {
|
||||
isCollapsed = true;
|
||||
if (initialAnimSkip) {
|
||||
initialAnimSkip = false;
|
||||
fixUpHeight(scope, element, 0);
|
||||
} else {
|
||||
fixUpHeight(scope, element, element[0].scrollHeight + 'px');
|
||||
doTransition({'height':'0'});
|
||||
|
||||
function expand() {
|
||||
if (element.hasClass('collapse') && element.hasClass('in')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$q.resolve(expandingExpr(scope))
|
||||
.then(function() {
|
||||
element.removeClass('collapse')
|
||||
.addClass('collapsing')
|
||||
.attr('aria-expanded', true)
|
||||
.attr('aria-hidden', false);
|
||||
|
||||
if ($animateCss) {
|
||||
$animateCss(element, {
|
||||
addClass: 'in',
|
||||
easing: 'ease',
|
||||
css: {
|
||||
overflow: 'hidden'
|
||||
},
|
||||
to: getScrollFromElement(element[0])
|
||||
}).start()['finally'](expandDone);
|
||||
} else {
|
||||
$animate.addClass(element, 'in', {
|
||||
css: {
|
||||
overflow: 'hidden'
|
||||
},
|
||||
to: getScrollFromElement(element[0])
|
||||
}).then(expandDone);
|
||||
}
|
||||
}, angular.noop);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
function expandDone() {
|
||||
element.removeClass('collapsing')
|
||||
.addClass('collapse')
|
||||
.css(css);
|
||||
expandedExpr(scope);
|
||||
}
|
||||
|
||||
function collapse() {
|
||||
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.css(cssTo); // Required so that collapse works when animation is disabled
|
||||
element.removeClass('collapsing')
|
||||
.addClass('collapse');
|
||||
collapsedExpr(scope);
|
||||
}
|
||||
|
||||
scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
|
||||
if (shouldCollapse) {
|
||||
collapse();
|
||||
} else {
|
||||
expand();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -1,7 +1,40 @@
|
||||
<style>
|
||||
.horizontal-collapse {
|
||||
height: 70px;
|
||||
}
|
||||
.navbar-collapse.in {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
</style>
|
||||
<div ng-controller="CollapseDemoCtrl">
|
||||
<button class="btn" 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-large">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 @@
|
||||
function CollapseDemoCtrl($scope) {
|
||||
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 twitter'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,19 +1,24 @@
|
||||
describe('collapse directive', function () {
|
||||
|
||||
var scope, $compile, $timeout, $transition;
|
||||
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_;
|
||||
}));
|
||||
|
||||
var element;
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -21,43 +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;
|
||||
it('should not trigger any animation on initialization if isCollapsed = true', function() {
|
||||
var wrapperFn = function() {
|
||||
$animate.flush();
|
||||
};
|
||||
|
||||
scope.isCollapsed = true;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
|
||||
expect(wrapperFn).toThrowError(/No pending animations ready to be closed or flushed/);
|
||||
});
|
||||
|
||||
it('should collapse if isCollapsed = true on subsequent use', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
initCallbacks();
|
||||
scope.isCollapsed = true;
|
||||
scope.$digest();
|
||||
$timeout.flush();
|
||||
$animate.flush();
|
||||
expect(element.height()).toBe(0);
|
||||
assertCallbacks({ collapsing: true, collapsed: true });
|
||||
});
|
||||
|
||||
it('should be shown on initialization if isCollapsed = false without transition', function() {
|
||||
it('should show after toggled from collapsed', function() {
|
||||
initCallbacks();
|
||||
scope.isCollapsed = true;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
expect(element.height()).toBe(0);
|
||||
assertCallbacks({ collapsed: true });
|
||||
scope.collapsed.calls.reset();
|
||||
|
||||
scope.isCollapsed = false;
|
||||
scope.$digest();
|
||||
//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 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();
|
||||
$animate.flush();
|
||||
expect(element.height()).toBe(0);
|
||||
assertCallbacks({ collapsing: true, collapsed: true });
|
||||
});
|
||||
|
||||
it('should change aria-expanded attribute', function() {
|
||||
scope.isCollapsed = false;
|
||||
compileFn(scope);
|
||||
scope.$digest();
|
||||
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);
|
||||
});
|
||||
@@ -85,22 +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();
|
||||
|
||||
it('should shrink accordingly when content size inside collapse decreases on subsequent use', function() {
|
||||
scope.isCollapsed = false;
|
||||
scope.exp = false;
|
||||
scope.$digest();
|
||||
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();
|
||||
$timeout.flush();
|
||||
scope.exp = true;
|
||||
|
||||
// ... 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();
|
||||
var collapseHeight = element.height();
|
||||
scope.exp = false;
|
||||
$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.height()).toBeLessThan(collapseHeight);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,539 @@
|
||||
angular.module('ui.bootstrap.dateparser', [])
|
||||
|
||||
.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;
|
||||
|
||||
var localeId;
|
||||
var formatCodeToRegex;
|
||||
|
||||
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('');
|
||||
|
||||
// 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 + data.key.length; i < n; i++) {
|
||||
regex[i] = '';
|
||||
format[i] = '$';
|
||||
}
|
||||
format = format.join('');
|
||||
|
||||
map.push({
|
||||
index: index,
|
||||
key: data.key,
|
||||
apply: data.apply,
|
||||
matcher: data.regex
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
regex: new RegExp('^' + regex.join('') + '$'),
|
||||
map: orderByFilter(map, 'index')
|
||||
};
|
||||
}
|
||||
|
||||
function createFormatter(format) {
|
||||
var formatters = [];
|
||||
var i = 0;
|
||||
var formatter, literalIdx;
|
||||
while (i < format.length) {
|
||||
if (angular.isNumber(literalIdx)) {
|
||||
if (format.charAt(i) === '\'') {
|
||||
if (i + 1 >= format.length || format.charAt(i + 1) !== '\'') {
|
||||
formatters.push(constructLiteralFormatter(format, literalIdx, i));
|
||||
literalIdx = null;
|
||||
}
|
||||
} else if (i === format.length) {
|
||||
while (literalIdx < format.length) {
|
||||
formatter = constructFormatterFromIdx(format, literalIdx);
|
||||
formatters.push(formatter);
|
||||
literalIdx = formatter.endIdx;
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (format.charAt(i) === '\'') {
|
||||
literalIdx = i;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
formatter = constructFormatterFromIdx(format, i);
|
||||
|
||||
formatters.push(formatter.parser);
|
||||
i = formatter.endIdx;
|
||||
}
|
||||
|
||||
return formatters;
|
||||
}
|
||||
|
||||
function constructLiteralFormatter(format, literalIdx, endIdx) {
|
||||
return function() {
|
||||
return format.substr(literalIdx + 1, endIdx - literalIdx - 1);
|
||||
};
|
||||
}
|
||||
|
||||
function constructFormatterFromIdx(format, i) {
|
||||
var currentPosStr = format.substr(i);
|
||||
for (var j = 0; j < formatCodeToRegex.length; j++) {
|
||||
if (new RegExp('^' + formatCodeToRegex[j].key).test(currentPosStr)) {
|
||||
var data = formatCodeToRegex[j];
|
||||
return {
|
||||
endIdx: i + data.key.length,
|
||||
parser: data.formatter
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
endIdx: i + 1,
|
||||
parser: function() {
|
||||
return currentPosStr.charAt(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.filter = function(date, format) {
|
||||
if (!angular.isDate(date) || isNaN(date) || !format) {
|
||||
return '';
|
||||
}
|
||||
|
||||
format = $locale.DATETIME_FORMATS[format] || format;
|
||||
|
||||
if ($locale.id !== localeId) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
if (!this.formatters[format]) {
|
||||
this.formatters[format] = createFormatter(format);
|
||||
}
|
||||
|
||||
var formatters = this.formatters[format];
|
||||
|
||||
return formatters.reduce(function(str, formatter) {
|
||||
return str + formatter(date);
|
||||
}, '');
|
||||
};
|
||||
|
||||
this.parse = function(input, format, baseDate) {
|
||||
if (!angular.isString(input) || !format) {
|
||||
return input;
|
||||
}
|
||||
|
||||
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),
|
||||
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 };
|
||||
}
|
||||
|
||||
for (var i = 1, n = results.length; i < n; i++) {
|
||||
var mapper = map[i - 1];
|
||||
if (mapper.matcher === 'Z') {
|
||||
tzOffset = true;
|
||||
}
|
||||
|
||||
if (mapper.apply) {
|
||||
mapper.apply.call(fields, results[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
|
||||
Date.prototype.setFullYear;
|
||||
var timesetter = tzOffset ? Date.prototype.setUTCHours :
|
||||
Date.prototype.setHours;
|
||||
|
||||
if (isValid(fields.year, fields.month, fields.date)) {
|
||||
if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !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;
|
||||
}
|
||||
};
|
||||
|
||||
// Check if date is valid for specific month (and year for February).
|
||||
// Month: 0 = Jan, 1 = Feb, etc
|
||||
function isValid(year, month, date) {
|
||||
if (date < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -0,0 +1,818 @@
|
||||
describe('date parser', function() {
|
||||
var dateParser, oldDate;
|
||||
|
||||
beforeEach(module('ui.bootstrap.dateparser'));
|
||||
beforeEach(inject(function (uibDateParser) {
|
||||
dateParser = uibDateParser;
|
||||
oldDate = new Date(1, 2, 6);
|
||||
oldDate.setFullYear(1);
|
||||
}));
|
||||
|
||||
function expectFilter(date, format, display) {
|
||||
expect(dateParser.filter(date, format)).toEqual(display);
|
||||
}
|
||||
|
||||
function expectParse(input, format, date) {
|
||||
expect(dateParser.parse(input, format)).toEqual(date);
|
||||
}
|
||||
|
||||
function expectBaseParse(input, format, baseDate, date) {
|
||||
expect(dateParser.parse(input, format, baseDate)).toEqual(date);
|
||||
}
|
||||
|
||||
describe('filter', function() {
|
||||
it('should work correctly for `dd`, `MM`, `yyyy`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.yyyy', '17.11.2013');
|
||||
expectFilter(new Date(2013, 11, 31, 0), 'dd.MM.yyyy', '31.12.2013');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'dd-MM-yyyy', '08-03-1991');
|
||||
expectFilter(new Date(1980, 2, 5, 0), 'MM/dd/yyyy', '03/05/1980');
|
||||
expectFilter(new Date(1983, 0, 10, 0), 'dd.MM/yyyy', '10.01/1983');
|
||||
expectFilter(new Date(1980, 10, 9, 0), 'MM-dd-yyyy', '11-09-1980');
|
||||
expectFilter(new Date(2011, 1, 5, 0), 'yyyy/MM/dd', '2011/02/05');
|
||||
expectFilter(oldDate, 'yyyy/MM/dd', '0001/03/06');
|
||||
});
|
||||
|
||||
it('should work correctly for `yy`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.yy', '17.11.13');
|
||||
expectFilter(new Date(2011, 4, 2, 0), 'dd-MM-yy', '02-05-11');
|
||||
expectFilter(new Date(2080, 1, 5, 0), 'MM/dd/yy', '02/05/80');
|
||||
expectFilter(new Date(2055, 1, 5, 0), 'yy/MM/dd', '55/02/05');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'dd-MM-yy', '11-08-13');
|
||||
});
|
||||
|
||||
it('should work correctly for `y`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.y', '17.11.2013');
|
||||
expectFilter(new Date(2013, 11, 31, 0), 'dd.MM.y', '31.12.2013');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'dd-MM-y', '08-03-1991');
|
||||
expectFilter(new Date(1980, 2, 5, 0), 'MM/dd/y', '03/05/1980');
|
||||
expectFilter(new Date(1983, 0, 10, 0), 'dd.MM/y', '10.01/1983');
|
||||
expectFilter(new Date(1980, 10, 9, 0), 'MM-dd-y', '11-09-1980');
|
||||
expectFilter(new Date(2011, 1, 5, 0), 'y/MM/dd', '2011/02/05');
|
||||
});
|
||||
|
||||
it('should work correctly for `MMMM`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'dd.MMMM.yy', '17.November.13');
|
||||
expectFilter(new Date(1980, 2, 5, 0), 'dd-MMMM-yyyy', '05-March-1980');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/dd/yyyy', 'February/05/1980');
|
||||
expectFilter(new Date(1949, 11, 20, 0), 'yyyy/MMMM/dd', '1949/December/20');
|
||||
expectFilter(oldDate, 'yyyy/MMMM/dd', '0001/March/06');
|
||||
});
|
||||
|
||||
it('should work correctly for `MMM`', function() {
|
||||
expectFilter(new Date(2010, 8, 30, 0), 'dd.MMM.yy', '30.Sep.10');
|
||||
expectFilter(new Date(2011, 4, 2, 0), 'dd-MMM-yy', '02-May-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMM/dd/yyyy', 'Feb/05/1980');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMM/dd', '1955/Feb/05');
|
||||
expectFilter(oldDate, 'yyyy/MMM/dd', '0001/Mar/06');
|
||||
});
|
||||
|
||||
it('should work correctly for `M`', function() {
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'M/dd/yyyy', '8/11/2013');
|
||||
expectFilter(new Date(2005, 10, 7, 0), 'dd.M.yy', '07.11.05');
|
||||
expectFilter(new Date(2011, 4, 2, 0), 'dd-M-yy', '02-5-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'M/dd/yyyy', '2/05/1980');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/M/dd', '1955/2/05');
|
||||
expectFilter(new Date(2011, 4, 2, 0), 'dd-M-yy', '02-5-11');
|
||||
});
|
||||
|
||||
it('should work correctly for `M!`', function() {
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'M!/dd/yyyy', '08/11/2013');
|
||||
expectFilter(new Date(2005, 10, 7, 0), 'dd.M!.yy', '07.11.05');
|
||||
expectFilter(new Date(2011, 4, 2, 0), 'dd-M!-yy', '02-05-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'M!/dd/yyyy', '02/05/1980');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/M!/dd', '1955/02/05');
|
||||
expectFilter(new Date(2011, 4, 2, 0), 'dd-M!-yy', '02-05-11');
|
||||
expectFilter(oldDate, 'yyyy/M!/dd', '0001/03/06');
|
||||
});
|
||||
|
||||
it('should work correctly for `LLLL`', function() {
|
||||
expectFilter(new Date(2013, 7, 24, 0), 'LLLL/dd/yyyy', 'August/24/2013');
|
||||
expectFilter(new Date(2004, 10, 7, 0), 'dd.LLLL.yy', '07.November.04');
|
||||
expectFilter(new Date(2011, 4, 18, 0), 'dd-LLLL-yy', '18-May-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'LLLL/dd/yyyy', 'February/05/1980');
|
||||
expectFilter(new Date(1955, 2, 5, 0), 'yyyy/LLLL/dd', '1955/March/05');
|
||||
expectFilter(new Date(2011, 5, 2, 0), 'dd-LLLL-yy', '02-June-11');
|
||||
expectFilter(oldDate, 'yyyy/LLLL/dd', '0001/March/06');
|
||||
});
|
||||
|
||||
it('should work correctly for `d`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy', '17.November.13');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'd-MMMM-yyyy', '8-March-1991');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy', 'February/5/1980');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d', '1955/February/5');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy', '11-08-13');
|
||||
expectFilter(oldDate, 'yyyy/MM/d', '0001/03/6');
|
||||
});
|
||||
|
||||
it('should work correctly for `d!`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd!.MMMM.yy', '17.November.13');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'd!-MMMM-yyyy', '08-March-1991');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d!/yyyy', 'February/05/1980');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d!', '1955/February/05');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd!-MM-yy', '11-08-13');
|
||||
expectFilter(oldDate, 'yyyy/MM/d!', '0001/03/06');
|
||||
});
|
||||
|
||||
it('should work correctly for `EEEE`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'EEEE.d.MMMM.yy', 'Sunday.17.November.13');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'd-EEEE-MMMM-yyyy', '8-Friday-March-1991');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/EEEE', 'February/5/1980/Tuesday');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/EEEE/MMMM/d', '1955/Saturday/February/5');
|
||||
});
|
||||
|
||||
it('should work correctly for `EEE`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'EEE.d.MMMM.yy', 'Sun.17.November.13');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'd-EEE-MMMM-yyyy', '8-Fri-March-1991');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/EEE', 'February/5/1980/Tue');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/EEE/MMMM/d', '1955/Sat/February/5');
|
||||
});
|
||||
|
||||
it('should work correctly for `HH`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 22), 'd.MMMM.yy.HH', '22.March.15.22');
|
||||
expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-HH', '8-March-1991-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/HH', 'February/5/1980/00');
|
||||
expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d HH', '1955/February/5 03');
|
||||
expectFilter(new Date(2013, 7, 11, 23), 'd-MM-yy HH', '11-08-13 23');
|
||||
});
|
||||
|
||||
it('should work correctly for `H`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 22), 'd.MMMM.yy.H', '22.March.15.22');
|
||||
expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-H', '8-March-1991-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/H', 'February/5/1980/0');
|
||||
expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d H', '1955/February/5 3');
|
||||
expectFilter(new Date(2013, 7, 11, 23), 'd-MM-yy H', '11-08-13 23');
|
||||
});
|
||||
|
||||
it('should work correctly for `hh`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 12), 'd.MMMM.yy.hh', '22.March.15.12');
|
||||
expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-hh', '8-March-1991-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/hh', 'February/5/1980/12');
|
||||
expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d hh', '1955/February/5 03');
|
||||
expectFilter(new Date(2013, 7, 11, 9), 'd-MM-yy hh', '11-08-13 09');
|
||||
});
|
||||
|
||||
it('should work correctly for `h`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 12), 'd.MMMM.yy.h', '22.March.15.12');
|
||||
expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-h', '8-March-1991-11');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/h', 'February/5/1980/12');
|
||||
expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d h', '1955/February/5 3');
|
||||
expectFilter(new Date(2013, 7, 11, 3), 'd-MM-yy h', '11-08-13 3');
|
||||
});
|
||||
|
||||
it('should work correctly for `mm`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 0, 22), 'd.MMMM.yy.mm', '22.March.15.22');
|
||||
expectFilter(new Date(1991, 2, 8, 0, 59), 'd-MMMM-yyyy-mm', '8-March-1991-59');
|
||||
expectFilter(new Date(1980, 1, 5, 0, 0), 'MMMM/d/yyyy/mm', 'February/5/1980/00');
|
||||
expectFilter(new Date(1955, 1, 5, 0, 3), 'yyyy/MMMM/d mm', '1955/February/5 03');
|
||||
expectFilter(new Date(2013, 7, 11, 0, 46), 'd-MM-yy mm', '11-08-13 46');
|
||||
expectFilter(new Date(2015, 2, 22, 22, 33), 'd.MMMM.yy.HH:mm', '22.March.15.22:33');
|
||||
expectFilter(new Date(2015, 2, 22, 2, 1), 'd.MMMM.yy.H:mm', '22.March.15.2:01');
|
||||
});
|
||||
|
||||
it('should work correctly for `m`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 0, 22), 'd.MMMM.yy.m', '22.March.15.22');
|
||||
expectFilter(new Date(1991, 2, 8, 0, 59), 'd-MMMM-yyyy-m', '8-March-1991-59');
|
||||
expectFilter(new Date(1980, 1, 5, 0, 0), 'MMMM/d/yyyy/m', 'February/5/1980/0');
|
||||
expectFilter(new Date(1955, 1, 5, 0, 3), 'yyyy/MMMM/d m', '1955/February/5 3');
|
||||
expectFilter(new Date(2013, 7, 11, 0, 46), 'd-MM-yy m', '11-08-13 46');
|
||||
expectFilter(new Date(2015, 2, 22, 22, 3), 'd.MMMM.yy.HH:m', '22.March.15.22:3');
|
||||
expectFilter(new Date(2015, 2, 22, 2, 1), 'd.MMMM.yy.H:m', '22.March.15.2:1');
|
||||
});
|
||||
|
||||
it('should work correctly for `sss`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 0, 0, 0, 123), 'd.MMMM.yy.sss', '22.March.15.123');
|
||||
expectFilter(new Date(1991, 2, 8, 0, 0, 0, 59), 'd-MMMM-yyyy-sss', '8-March-1991-059');
|
||||
expectFilter(new Date(1980, 1, 5, 0, 0, 0), 'MMMM/d/yyyy/sss', 'February/5/1980/000');
|
||||
expectFilter(new Date(1955, 1, 5, 0, 0, 0, 3), 'yyyy/MMMM/d sss', '1955/February/5 003');
|
||||
expectFilter(new Date(2013, 7, 11, 0, 0, 0, 46), 'd-MM-yy sss', '11-08-13 046');
|
||||
expectFilter(new Date(2015, 2, 22, 22, 33, 0, 44), 'd.MMMM.yy.HH:mm:sss', '22.March.15.22:33:044');
|
||||
expectFilter(new Date(2015, 2, 22, 0, 0, 0, 1), 'd.MMMM.yy.H:m:sss', '22.March.15.0:0:001');
|
||||
});
|
||||
|
||||
it('should work correctly for `ss`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 0, 0, 22), 'd.MMMM.yy.ss', '22.March.15.22');
|
||||
expectFilter(new Date(1991, 2, 8, 0, 0, 59), 'd-MMMM-yyyy-ss', '8-March-1991-59');
|
||||
expectFilter(new Date(1980, 1, 5, 0, 0, 0), 'MMMM/d/yyyy/ss', 'February/5/1980/00');
|
||||
expectFilter(new Date(1955, 1, 5, 0, 0, 3), 'yyyy/MMMM/d ss', '1955/February/5 03');
|
||||
expectFilter(new Date(2013, 7, 11, 0, 0, 46), 'd-MM-yy ss', '11-08-13 46');
|
||||
expectFilter(new Date(2015, 2, 22, 22, 33, 44), 'd.MMMM.yy.HH:mm:ss', '22.March.15.22:33:44');
|
||||
expectFilter(new Date(2015, 2, 22, 0, 0, 1), 'd.MMMM.yy.H:m:ss', '22.March.15.0:0:01');
|
||||
});
|
||||
|
||||
it('should work correctly for `s`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 0, 0, 22), 'd.MMMM.yy.s', '22.March.15.22');
|
||||
expectFilter(new Date(1991, 2, 8, 0, 0, 59), 'd-MMMM-yyyy-s', '8-March-1991-59');
|
||||
expectFilter(new Date(1980, 1, 5, 0, 0, 0), 'MMMM/d/yyyy/s', 'February/5/1980/0');
|
||||
expectFilter(new Date(1955, 1, 5, 0, 0, 3), 'yyyy/MMMM/d s', '1955/February/5 3');
|
||||
expectFilter(new Date(2013, 7, 11, 0, 0, 46), 'd-MM-yy s', '11-08-13 46');
|
||||
expectFilter(new Date(2015, 2, 22, 22, 33, 4), 'd.MMMM.yy.HH:mm:s', '22.March.15.22:33:4');
|
||||
expectFilter(new Date(2015, 2, 22, 22, 3, 4), 'd.MMMM.yy.HH:m:s', '22.March.15.22:3:4');
|
||||
});
|
||||
|
||||
it('should work correctly for `a`', function() {
|
||||
expectFilter(new Date(2015, 2, 22, 10), 'd.MMMM.yy.hha', '22.March.15.10AM');
|
||||
expectFilter(new Date(2015, 2, 22, 22), 'd.MMMM.yy.hha', '22.March.15.10PM');
|
||||
expectFilter(new Date(1991, 2, 8, 11), 'd-MMMM-yyyy-hha', '8-March-1991-11AM');
|
||||
expectFilter(new Date(1991, 2, 8, 23), 'd-MMMM-yyyy-hha', '8-March-1991-11PM');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/hha', 'February/5/1980/12AM');
|
||||
expectFilter(new Date(1980, 1, 5, 12), 'MMMM/d/yyyy/hha', 'February/5/1980/12PM');
|
||||
expectFilter(new Date(1955, 1, 5, 3), 'yyyy/MMMM/d hha', '1955/February/5 03AM');
|
||||
expectFilter(new Date(1955, 1, 5, 15), 'yyyy/MMMM/d hha', '1955/February/5 03PM');
|
||||
expectFilter(new Date(2013, 7, 11, 9), 'd-MM-yy hha', '11-08-13 09AM');
|
||||
expectFilter(new Date(2013, 7, 11, 21), 'd-MM-yy hha', '11-08-13 09PM');
|
||||
});
|
||||
|
||||
it('should work correctly for `ww`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.ww', '17.November.13.47');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'd-MMMM-yyyy-ww', '8-March-1991-10');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/ww', 'February/5/1980/06');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d/ww', '1955/February/5/05');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy ww', '11-08-13 33');
|
||||
expectFilter(oldDate, 'yyyy/MM/d ww', '0001/03/6 10');
|
||||
});
|
||||
|
||||
it('should work correctly for `w`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.w', '17.November.13.47');
|
||||
expectFilter(new Date(1991, 2, 8, 0), 'd-MMMM-yyyy-w', '8-March-1991-10');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/w', 'February/5/1980/6');
|
||||
expectFilter(new Date(1955, 1, 5, 0), 'yyyy/MMMM/d/w', '1955/February/5/5');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy w', '11-08-13 33');
|
||||
expectFilter(oldDate, 'yyyy/MM/d w', '0001/03/6 10');
|
||||
});
|
||||
|
||||
it('should work correctly for `G`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.G', '17.November.13.AD');
|
||||
expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-G', '8-March-1991-BC');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/G', 'February/5/1980/AD');
|
||||
expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/G', '1955/February/5/BC');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy G', '11-08-13 AD');
|
||||
});
|
||||
|
||||
it('should work correctly for `GG`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.GG', '17.November.13.AD');
|
||||
expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-GG', '8-March-1991-BC');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/GG', 'February/5/1980/AD');
|
||||
expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/GG', '1955/February/5/BC');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy GG', '11-08-13 AD');
|
||||
});
|
||||
|
||||
it('should work correctly for `GGG`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.GGG', '17.November.13.AD');
|
||||
expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-GGG', '8-March-1991-BC');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/GGG', 'February/5/1980/AD');
|
||||
expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/GGG', '1955/February/5/BC');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy GGG', '11-08-13 AD');
|
||||
});
|
||||
|
||||
it('should work correctly for `GGGG`', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'd.MMMM.yy.GGGG', '17.November.13.Anno Domini');
|
||||
expectFilter(new Date(-1991, 2, 8, 0), 'd-MMMM-yyyy-GGGG', '8-March-1991-Before Christ');
|
||||
expectFilter(new Date(1980, 1, 5, 0), 'MMMM/d/yyyy/GGGG', 'February/5/1980/Anno Domini');
|
||||
expectFilter(new Date(-1955, 1, 5, 0), 'yyyy/MMMM/d/GGGG', '1955/February/5/Before Christ');
|
||||
expectFilter(new Date(2013, 7, 11, 0), 'd-MM-yy GGGG', '11-08-13 Anno Domini');
|
||||
});
|
||||
|
||||
it('should work correctly for literal text', function() {
|
||||
expectFilter(new Date(2013, 10, 17, 0), 'dd.MM.yyyy foo', '17.11.2013 foo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with custom formats', function() {
|
||||
it('should work correctly for `dd`, `MM`, `yyyy`', function() {
|
||||
expectParse('17.11.2013', 'dd.MM.yyyy', new Date(2013, 10, 17, 0));
|
||||
expectParse('31.12.2013', 'dd.MM.yyyy', new Date(2013, 11, 31, 0));
|
||||
expectParse('08-03-1991', 'dd-MM-yyyy', new Date(1991, 2, 8, 0));
|
||||
expectParse('03/05/1980', 'MM/dd/yyyy', new Date(1980, 2, 5, 0));
|
||||
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(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));
|
||||
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));
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
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() {
|
||||
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);
|
||||
});
|
||||
|
||||
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('with predefined formats', function() {
|
||||
it('should work correctly for `shortDate`', function() {
|
||||
expectParse('9/3/10', 'shortDate', new Date(2010, 8, 3, 0));
|
||||
});
|
||||
|
||||
it('should work correctly for `mediumDate`', function() {
|
||||
expectParse('Sep 3, 2010', 'mediumDate', new Date(2010, 8, 3, 0));
|
||||
});
|
||||
|
||||
it('should work correctly for `longDate`', function() {
|
||||
expectParse('September 3, 2010', 'longDate', new Date(2010, 8, 3, 0));
|
||||
});
|
||||
|
||||
it('should work correctly for `fullDate`', function() {
|
||||
expectParse('Friday, September 3, 2010', 'fullDate', new Date(2010, 8, 3, 0));
|
||||
});
|
||||
});
|
||||
|
||||
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() {
|
||||
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() {
|
||||
expectParse('29.02.2000', 'dd.MM.yyyy', new Date(2000, 1, 29, 0));
|
||||
});
|
||||
|
||||
it('should not work for 31 days for some months', function() {
|
||||
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() {
|
||||
expectParse(123456, 'dd.MM.yyyy', 123456);
|
||||
var date = new Date();
|
||||
expectParse(date, 'dd.MM.yyyy', date);
|
||||
});
|
||||
|
||||
it('should not parse if no format is specified', function() {
|
||||
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%
|
||||
}
|
||||
@@ -0,0 +1,685 @@
|
||||
angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass'])
|
||||
|
||||
.value('$datepickerSuppressError', false)
|
||||
|
||||
.value('$datepickerLiteralWarning', true)
|
||||
|
||||
.constant('uibDatepickerConfig', {
|
||||
datepickerMode: 'day',
|
||||
formatDay: 'dd',
|
||||
formatMonth: 'MMMM',
|
||||
formatYear: 'yyyy',
|
||||
formatDayHeader: 'EEE',
|
||||
formatDayTitle: 'MMMM yyyy',
|
||||
formatMonthTitle: 'yyyy',
|
||||
maxDate: null,
|
||||
maxMode: 'year',
|
||||
minDate: null,
|
||||
minMode: 'day',
|
||||
monthColumns: 3,
|
||||
ngModelOptions: {},
|
||||
shortcutPropagation: false,
|
||||
showWeeks: true,
|
||||
yearColumns: 5,
|
||||
yearRows: 4
|
||||
})
|
||||
|
||||
.controller('UibDatepickerController', ['$scope', '$element', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerLiteralWarning', '$datepickerSuppressError', 'uibDateParser',
|
||||
function($scope, $element, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerLiteralWarning, $datepickerSuppressError, dateParser) {
|
||||
var self = this,
|
||||
ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
|
||||
ngModelOptions = {},
|
||||
watchListeners = [];
|
||||
|
||||
$element.addClass('uib-datepicker');
|
||||
$attrs.$set('role', 'application');
|
||||
|
||||
if (!$scope.datepickerOptions) {
|
||||
$scope.datepickerOptions = {};
|
||||
}
|
||||
|
||||
// Modes chain
|
||||
this.modes = ['day', 'month', 'year'];
|
||||
|
||||
[
|
||||
'customClass',
|
||||
'dateDisabled',
|
||||
'datepickerMode',
|
||||
'formatDay',
|
||||
'formatDayHeader',
|
||||
'formatDayTitle',
|
||||
'formatMonth',
|
||||
'formatMonthTitle',
|
||||
'formatYear',
|
||||
'maxDate',
|
||||
'maxMode',
|
||||
'minDate',
|
||||
'minMode',
|
||||
'monthColumns',
|
||||
'showWeeks',
|
||||
'shortcutPropagation',
|
||||
'startingDay',
|
||||
'yearColumns',
|
||||
'yearRows'
|
||||
].forEach(function(key) {
|
||||
switch (key) {
|
||||
case 'customClass':
|
||||
case 'dateDisabled':
|
||||
$scope[key] = $scope.datepickerOptions[key] || angular.noop;
|
||||
break;
|
||||
case 'datepickerMode':
|
||||
$scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ?
|
||||
$scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode;
|
||||
break;
|
||||
case 'formatDay':
|
||||
case 'formatDayHeader':
|
||||
case 'formatDayTitle':
|
||||
case 'formatMonth':
|
||||
case 'formatMonthTitle':
|
||||
case 'formatYear':
|
||||
self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
|
||||
$interpolate($scope.datepickerOptions[key])($scope.$parent) :
|
||||
datepickerConfig[key];
|
||||
break;
|
||||
case 'monthColumns':
|
||||
case 'showWeeks':
|
||||
case 'shortcutPropagation':
|
||||
case 'yearColumns':
|
||||
case 'yearRows':
|
||||
self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
|
||||
$scope.datepickerOptions[key] : datepickerConfig[key];
|
||||
break;
|
||||
case 'startingDay':
|
||||
if (angular.isDefined($scope.datepickerOptions.startingDay)) {
|
||||
self.startingDay = $scope.datepickerOptions.startingDay;
|
||||
} else if (angular.isNumber(datepickerConfig.startingDay)) {
|
||||
self.startingDay = datepickerConfig.startingDay;
|
||||
} else {
|
||||
self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'maxDate':
|
||||
case 'minDate':
|
||||
$scope.$watch('datepickerOptions.' + key, function(value) {
|
||||
if (value) {
|
||||
if (angular.isDate(value)) {
|
||||
self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.getOption('timezone'));
|
||||
} else {
|
||||
if ($datepickerLiteralWarning) {
|
||||
$log.warn('Literal date support has been deprecated, please switch to date object usage');
|
||||
}
|
||||
|
||||
self[key] = new Date(dateFilter(value, 'medium'));
|
||||
}
|
||||
} else {
|
||||
self[key] = datepickerConfig[key] ?
|
||||
dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.getOption('timezone')) :
|
||||
null;
|
||||
}
|
||||
|
||||
self.refreshView();
|
||||
});
|
||||
|
||||
break;
|
||||
case 'maxMode':
|
||||
case 'minMode':
|
||||
if ($scope.datepickerOptions[key]) {
|
||||
$scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) {
|
||||
self[key] = $scope[key] = angular.isDefined(value) ? value : $scope.datepickerOptions[key];
|
||||
if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) ||
|
||||
key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) {
|
||||
$scope.datepickerMode = self[key];
|
||||
$scope.datepickerOptions.datepickerMode = self[key];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self[key] = $scope[key] = datepickerConfig[key] || null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
|
||||
|
||||
$scope.disabled = angular.isDefined($attrs.disabled) || false;
|
||||
if (angular.isDefined($attrs.ngDisabled)) {
|
||||
watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
|
||||
$scope.disabled = disabled;
|
||||
self.refreshView();
|
||||
}));
|
||||
}
|
||||
|
||||
$scope.isActive = function(dateObject) {
|
||||
if (self.compare(dateObject.date, self.activeDate) === 0) {
|
||||
$scope.activeDateId = dateObject.uid;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.init = function(ngModelCtrl_) {
|
||||
ngModelCtrl = ngModelCtrl_;
|
||||
ngModelOptions = extractOptions(ngModelCtrl);
|
||||
|
||||
if ($scope.datepickerOptions.initDate) {
|
||||
self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.getOption('timezone')) || new Date();
|
||||
$scope.$watch('datepickerOptions.initDate', function(initDate) {
|
||||
if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
|
||||
self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.getOption('timezone'));
|
||||
self.refreshView();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.activeDate = new Date();
|
||||
}
|
||||
|
||||
var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date();
|
||||
this.activeDate = !isNaN(date) ?
|
||||
dateParser.fromTimezone(date, ngModelOptions.getOption('timezone')) :
|
||||
dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone'));
|
||||
|
||||
ngModelCtrl.$render = function() {
|
||||
self.render();
|
||||
};
|
||||
};
|
||||
|
||||
this.render = function() {
|
||||
if (ngModelCtrl.$viewValue) {
|
||||
var date = new Date(ngModelCtrl.$viewValue),
|
||||
isValid = !isNaN(date);
|
||||
|
||||
if (isValid) {
|
||||
this.activeDate = dateParser.fromTimezone(date, ngModelOptions.getOption('timezone'));
|
||||
} else if (!$datepickerSuppressError) {
|
||||
$log.error('Datepicker directive: "ng-model" value must be a Date object');
|
||||
}
|
||||
}
|
||||
this.refreshView();
|
||||
};
|
||||
|
||||
this.refreshView = function() {
|
||||
if (this.element) {
|
||||
$scope.selectedDt = null;
|
||||
this._refreshView();
|
||||
if ($scope.activeDt) {
|
||||
$scope.activeDateId = $scope.activeDt.uid;
|
||||
}
|
||||
|
||||
var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
|
||||
date = dateParser.fromTimezone(date, ngModelOptions.getOption('timezone'));
|
||||
ngModelCtrl.$setValidity('dateDisabled', !date ||
|
||||
this.element && !this.isDisabled(date));
|
||||
}
|
||||
};
|
||||
|
||||
this.createDateObject = function(date, format) {
|
||||
var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
|
||||
model = dateParser.fromTimezone(model, ngModelOptions.getOption('timezone'));
|
||||
var today = new Date();
|
||||
today = dateParser.fromTimezone(today, ngModelOptions.getOption('timezone'));
|
||||
var time = this.compare(date, today);
|
||||
var dt = {
|
||||
date: date,
|
||||
label: dateParser.filter(date, format),
|
||||
selected: model && this.compare(date, model) === 0,
|
||||
disabled: this.isDisabled(date),
|
||||
past: time < 0,
|
||||
current: time === 0,
|
||||
future: time > 0,
|
||||
customClass: this.customClass(date) || null
|
||||
};
|
||||
|
||||
if (model && this.compare(date, model) === 0) {
|
||||
$scope.selectedDt = dt;
|
||||
}
|
||||
|
||||
if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
|
||||
$scope.activeDt = dt;
|
||||
}
|
||||
|
||||
return dt;
|
||||
};
|
||||
|
||||
this.isDisabled = function(date) {
|
||||
return $scope.disabled ||
|
||||
this.minDate && this.compare(date, this.minDate) < 0 ||
|
||||
this.maxDate && this.compare(date, this.maxDate) > 0 ||
|
||||
$scope.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode});
|
||||
};
|
||||
|
||||
this.customClass = function(date) {
|
||||
return $scope.customClass({date: date, mode: $scope.datepickerMode});
|
||||
};
|
||||
|
||||
// Split array into smaller arrays
|
||||
this.split = function(arr, size) {
|
||||
var arrays = [];
|
||||
while (arr.length > 0) {
|
||||
arrays.push(arr.splice(0, size));
|
||||
}
|
||||
return arrays;
|
||||
};
|
||||
|
||||
$scope.select = function(date) {
|
||||
if ($scope.datepickerMode === self.minMode) {
|
||||
var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.getOption('timezone')) : new Date(0, 0, 0, 0, 0, 0, 0);
|
||||
dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
|
||||
dt = dateParser.toTimezone(dt, ngModelOptions.getOption('timezone'));
|
||||
ngModelCtrl.$setViewValue(dt);
|
||||
ngModelCtrl.$render();
|
||||
} else {
|
||||
self.activeDate = date;
|
||||
setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]);
|
||||
|
||||
$scope.$emit('uib:datepicker.mode');
|
||||
}
|
||||
|
||||
$scope.$broadcast('uib:datepicker.focus');
|
||||
};
|
||||
|
||||
$scope.move = function(direction) {
|
||||
var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
|
||||
month = self.activeDate.getMonth() + direction * (self.step.months || 0);
|
||||
self.activeDate.setFullYear(year, month, 1);
|
||||
self.refreshView();
|
||||
};
|
||||
|
||||
$scope.toggleMode = function(direction) {
|
||||
direction = direction || 1;
|
||||
|
||||
if ($scope.datepickerMode === self.maxMode && direction === 1 ||
|
||||
$scope.datepickerMode === self.minMode && direction === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]);
|
||||
|
||||
$scope.$emit('uib:datepicker.mode');
|
||||
};
|
||||
|
||||
// Key event mapper
|
||||
$scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
|
||||
|
||||
var focusElement = function() {
|
||||
self.element[0].focus();
|
||||
};
|
||||
|
||||
// Listen for focus requests from popup directive
|
||||
$scope.$on('uib:datepicker.focus', focusElement);
|
||||
|
||||
$scope.keydown = function(evt) {
|
||||
var key = $scope.keys[evt.which];
|
||||
|
||||
if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
evt.preventDefault();
|
||||
if (!self.shortcutPropagation) {
|
||||
evt.stopPropagation();
|
||||
}
|
||||
|
||||
if (key === 'enter' || key === 'space') {
|
||||
if (self.isDisabled(self.activeDate)) {
|
||||
return; // do nothing
|
||||
}
|
||||
$scope.select(self.activeDate);
|
||||
} else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
|
||||
$scope.toggleMode(key === 'up' ? 1 : -1);
|
||||
} else {
|
||||
self.handleKeyDown(key, evt);
|
||||
self.refreshView();
|
||||
}
|
||||
};
|
||||
|
||||
$element.on('keydown', function(evt) {
|
||||
$scope.$apply(function() {
|
||||
$scope.keydown(evt);
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
//Clear all watch listeners on destroy
|
||||
while (watchListeners.length) {
|
||||
watchListeners.shift()();
|
||||
}
|
||||
});
|
||||
|
||||
function setMode(mode) {
|
||||
$scope.datepickerMode = mode;
|
||||
$scope.datepickerOptions.datepickerMode = mode;
|
||||
}
|
||||
|
||||
function extractOptions(ngModelCtrl) {
|
||||
var ngModelOptions;
|
||||
|
||||
if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing
|
||||
// guarantee a value
|
||||
ngModelOptions = ngModelCtrl.$options ||
|
||||
$scope.datepickerOptions.ngModelOptions ||
|
||||
datepickerConfig.ngModelOptions ||
|
||||
{};
|
||||
|
||||
// mimic 1.6+ api
|
||||
ngModelOptions.getOption = function (key) {
|
||||
return ngModelOptions[key];
|
||||
};
|
||||
} else { // in angular >=1.6 $options is always present
|
||||
// ng-model-options defaults timezone to null; don't let its precedence squash a non-null value
|
||||
var timezone = ngModelCtrl.$options.getOption('timezone') ||
|
||||
($scope.datepickerOptions.ngModelOptions ? $scope.datepickerOptions.ngModelOptions.timezone : null) ||
|
||||
(datepickerConfig.ngModelOptions ? datepickerConfig.ngModelOptions.timezone : null);
|
||||
|
||||
// values passed to createChild override existing values
|
||||
ngModelOptions = ngModelCtrl.$options // start with a ModelOptions instance
|
||||
.createChild(datepickerConfig.ngModelOptions) // lowest precedence
|
||||
.createChild($scope.datepickerOptions.ngModelOptions)
|
||||
.createChild(ngModelCtrl.$options) // highest precedence
|
||||
.createChild({timezone: timezone}); // to keep from squashing a non-null value
|
||||
}
|
||||
|
||||
return ngModelOptions;
|
||||
}
|
||||
}])
|
||||
|
||||
.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
|
||||
var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
|
||||
this.step = { months: 1 };
|
||||
this.element = $element;
|
||||
function getDaysInMonth(year, month) {
|
||||
return month === 1 && year % 4 === 0 &&
|
||||
(year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
|
||||
}
|
||||
|
||||
this.init = function(ctrl) {
|
||||
angular.extend(ctrl, this);
|
||||
scope.showWeeks = ctrl.showWeeks;
|
||||
ctrl.refreshView();
|
||||
};
|
||||
|
||||
this.getDates = function(startDate, n) {
|
||||
var dates = new Array(n), current = new Date(startDate), i = 0, date;
|
||||
while (i < n) {
|
||||
date = new Date(current);
|
||||
dates[i++] = date;
|
||||
current.setDate(current.getDate() + 1);
|
||||
}
|
||||
return dates;
|
||||
};
|
||||
|
||||
this._refreshView = function() {
|
||||
var year = this.activeDate.getFullYear(),
|
||||
month = this.activeDate.getMonth(),
|
||||
firstDayOfMonth = new Date(this.activeDate);
|
||||
|
||||
firstDayOfMonth.setFullYear(year, month, 1);
|
||||
|
||||
var difference = this.startingDay - firstDayOfMonth.getDay(),
|
||||
numDisplayedFromPreviousMonth = difference > 0 ?
|
||||
7 - difference : - difference,
|
||||
firstDate = new Date(firstDayOfMonth);
|
||||
|
||||
if (numDisplayedFromPreviousMonth > 0) {
|
||||
firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
|
||||
}
|
||||
|
||||
// 42 is the number of days on a six-week calendar
|
||||
var days = this.getDates(firstDate, 42);
|
||||
for (var i = 0; i < 42; i ++) {
|
||||
days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
|
||||
secondary: days[i].getMonth() !== month,
|
||||
uid: scope.uniqueId + '-' + i
|
||||
});
|
||||
}
|
||||
|
||||
scope.labels = new Array(7);
|
||||
for (var j = 0; j < 7; j++) {
|
||||
scope.labels[j] = {
|
||||
abbr: dateFilter(days[j].date, this.formatDayHeader),
|
||||
full: dateFilter(days[j].date, 'EEEE')
|
||||
};
|
||||
}
|
||||
|
||||
scope.title = dateFilter(this.activeDate, this.formatDayTitle);
|
||||
scope.rows = this.split(days, 7);
|
||||
|
||||
if (scope.showWeeks) {
|
||||
scope.weekNumbers = [];
|
||||
var thursdayIndex = (4 + 7 - this.startingDay) % 7,
|
||||
numWeeks = scope.rows.length;
|
||||
for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
|
||||
scope.weekNumbers.push(
|
||||
getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.compare = function(date1, date2) {
|
||||
var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
|
||||
var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
|
||||
_date1.setFullYear(date1.getFullYear());
|
||||
_date2.setFullYear(date2.getFullYear());
|
||||
return _date1 - _date2;
|
||||
};
|
||||
|
||||
function getISO8601WeekNumber(date) {
|
||||
var checkDate = new Date(date);
|
||||
checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
|
||||
var time = checkDate.getTime();
|
||||
checkDate.setMonth(0); // Compare with Jan 1
|
||||
checkDate.setDate(1);
|
||||
return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
|
||||
}
|
||||
|
||||
this.handleKeyDown = function(key, evt) {
|
||||
var date = this.activeDate.getDate();
|
||||
|
||||
if (key === 'left') {
|
||||
date = date - 1;
|
||||
} else if (key === 'up') {
|
||||
date = date - 7;
|
||||
} else if (key === 'right') {
|
||||
date = date + 1;
|
||||
} else if (key === 'down') {
|
||||
date = date + 7;
|
||||
} else if (key === 'pageup' || key === 'pagedown') {
|
||||
var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
|
||||
this.activeDate.setMonth(month, 1);
|
||||
date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
|
||||
} else if (key === 'home') {
|
||||
date = 1;
|
||||
} else if (key === 'end') {
|
||||
date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
|
||||
}
|
||||
this.activeDate.setDate(date);
|
||||
};
|
||||
}])
|
||||
|
||||
.controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
|
||||
this.step = { years: 1 };
|
||||
this.element = $element;
|
||||
|
||||
this.init = function(ctrl) {
|
||||
angular.extend(ctrl, this);
|
||||
ctrl.refreshView();
|
||||
};
|
||||
|
||||
this._refreshView = function() {
|
||||
var months = new Array(12),
|
||||
year = this.activeDate.getFullYear(),
|
||||
date;
|
||||
|
||||
for (var i = 0; i < 12; i++) {
|
||||
date = new Date(this.activeDate);
|
||||
date.setFullYear(year, i, 1);
|
||||
months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
|
||||
uid: scope.uniqueId + '-' + i
|
||||
});
|
||||
}
|
||||
|
||||
scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
|
||||
scope.rows = this.split(months, this.monthColumns);
|
||||
scope.yearHeaderColspan = this.monthColumns > 3 ? this.monthColumns - 2 : 1;
|
||||
};
|
||||
|
||||
this.compare = function(date1, date2) {
|
||||
var _date1 = new Date(date1.getFullYear(), date1.getMonth());
|
||||
var _date2 = new Date(date2.getFullYear(), date2.getMonth());
|
||||
_date1.setFullYear(date1.getFullYear());
|
||||
_date2.setFullYear(date2.getFullYear());
|
||||
return _date1 - _date2;
|
||||
};
|
||||
|
||||
this.handleKeyDown = function(key, evt) {
|
||||
var date = this.activeDate.getMonth();
|
||||
|
||||
if (key === 'left') {
|
||||
date = date - 1;
|
||||
} else if (key === 'up') {
|
||||
date = date - this.monthColumns;
|
||||
} else if (key === 'right') {
|
||||
date = date + 1;
|
||||
} else if (key === 'down') {
|
||||
date = date + this.monthColumns;
|
||||
} else if (key === 'pageup' || key === 'pagedown') {
|
||||
var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
|
||||
this.activeDate.setFullYear(year);
|
||||
} else if (key === 'home') {
|
||||
date = 0;
|
||||
} else if (key === 'end') {
|
||||
date = 11;
|
||||
}
|
||||
this.activeDate.setMonth(date);
|
||||
};
|
||||
}])
|
||||
|
||||
.controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
|
||||
var columns, range;
|
||||
this.element = $element;
|
||||
|
||||
function getStartingYear(year) {
|
||||
return parseInt((year - 1) / range, 10) * range + 1;
|
||||
}
|
||||
|
||||
this.yearpickerInit = function() {
|
||||
columns = this.yearColumns;
|
||||
range = this.yearRows * columns;
|
||||
this.step = { years: range };
|
||||
};
|
||||
|
||||
this._refreshView = function() {
|
||||
var years = new Array(range), date;
|
||||
|
||||
for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
|
||||
date = new Date(this.activeDate);
|
||||
date.setFullYear(start + i, 0, 1);
|
||||
years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
|
||||
uid: scope.uniqueId + '-' + i
|
||||
});
|
||||
}
|
||||
|
||||
scope.title = [years[0].label, years[range - 1].label].join(' - ');
|
||||
scope.rows = this.split(years, columns);
|
||||
scope.columns = columns;
|
||||
};
|
||||
|
||||
this.compare = function(date1, date2) {
|
||||
return date1.getFullYear() - date2.getFullYear();
|
||||
};
|
||||
|
||||
this.handleKeyDown = function(key, evt) {
|
||||
var date = this.activeDate.getFullYear();
|
||||
|
||||
if (key === 'left') {
|
||||
date = date - 1;
|
||||
} else if (key === 'up') {
|
||||
date = date - columns;
|
||||
} else if (key === 'right') {
|
||||
date = date + 1;
|
||||
} else if (key === 'down') {
|
||||
date = date + columns;
|
||||
} else if (key === 'pageup' || key === 'pagedown') {
|
||||
date += (key === 'pageup' ? - 1 : 1) * range;
|
||||
} else if (key === 'home') {
|
||||
date = getStartingYear(this.activeDate.getFullYear());
|
||||
} else if (key === 'end') {
|
||||
date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
|
||||
}
|
||||
this.activeDate.setFullYear(date);
|
||||
};
|
||||
}])
|
||||
|
||||
.directive('uibDatepicker', function() {
|
||||
return {
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';
|
||||
},
|
||||
scope: {
|
||||
datepickerOptions: '=?'
|
||||
},
|
||||
require: ['uibDatepicker', '^ngModel'],
|
||||
restrict: 'A',
|
||||
controller: 'UibDatepickerController',
|
||||
controllerAs: 'datepicker',
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
||||
|
||||
datepickerCtrl.init(ngModelCtrl);
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
.directive('uibDaypicker', function() {
|
||||
return {
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/datepicker/day.html';
|
||||
},
|
||||
require: ['^uibDatepicker', 'uibDaypicker'],
|
||||
restrict: 'A',
|
||||
controller: 'UibDaypickerController',
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
var datepickerCtrl = ctrls[0],
|
||||
daypickerCtrl = ctrls[1];
|
||||
|
||||
daypickerCtrl.init(datepickerCtrl);
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
.directive('uibMonthpicker', function() {
|
||||
return {
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/datepicker/month.html';
|
||||
},
|
||||
require: ['^uibDatepicker', 'uibMonthpicker'],
|
||||
restrict: 'A',
|
||||
controller: 'UibMonthpickerController',
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
var datepickerCtrl = ctrls[0],
|
||||
monthpickerCtrl = ctrls[1];
|
||||
|
||||
monthpickerCtrl.init(datepickerCtrl);
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
.directive('uibYearpicker', function() {
|
||||
return {
|
||||
templateUrl: function(element, attrs) {
|
||||
return attrs.templateUrl || 'uib/template/datepicker/year.html';
|
||||
},
|
||||
require: ['^uibDatepicker', 'uibYearpicker'],
|
||||
restrict: 'A',
|
||||
controller: 'UibYearpickerController',
|
||||
link: function(scope, element, attrs, ctrls) {
|
||||
var ctrl = ctrls[0];
|
||||
angular.extend(ctrl, ctrls[1]);
|
||||
ctrl.yearpickerInit();
|
||||
|
||||
ctrl.refreshView();
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +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;">
|
||||
<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="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,66 @@
|
||||
angular.module('ui.bootstrap.demo').controller('DatepickerDemoCtrl', function ($scope) {
|
||||
$scope.today = function() {
|
||||
$scope.dt = new Date();
|
||||
};
|
||||
$scope.today();
|
||||
|
||||
$scope.clear = function() {
|
||||
$scope.dt = null;
|
||||
};
|
||||
|
||||
$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.options.minDate = $scope.options.minDate ? null : new Date();
|
||||
};
|
||||
|
||||
$scope.toggleMin();
|
||||
|
||||
$scope.setDate = function(year, month, day) {
|
||||
$scope.dt = new Date(year, month, day);
|
||||
};
|
||||
|
||||
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'
|
||||
}
|
||||
];
|
||||
|
||||
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,161 @@
|
||||
Our datepicker is flexible and fully customizable.
|
||||
|
||||
You can navigate through days, months and years.
|
||||
|
||||
The datepicker has 3 modes:
|
||||
|
||||
* `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).
|
||||
|
||||
### uib-datepicker settings
|
||||
|
||||
* `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.
|
||||
|
||||
* `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
|
||||
|
||||
* `template-url`
|
||||
_(Default: `uib/template/datepicker/datepicker.html`)_ -
|
||||
Add the ability to override the template used on the component.
|
||||
|
||||
Apart from the previous settings, to configure the uib-datepicker you need to create an object in Javascript with all the options and use it on the `datepicker-options` attribute:
|
||||
|
||||
* `datepicker-options`
|
||||
<small class="badge">$</small> -
|
||||
An object to configure the datepicker in one place.
|
||||
|
||||
* `customClass ({date: date, mode: mode})` -
|
||||
An optional expression to add classes based on passing an object with date and current mode properties.
|
||||
|
||||
* `dateDisabled ({date: date, mode: mode})` -
|
||||
An optional expression to disable visible options based on passing an object with date and current mode properties.
|
||||
|
||||
* `datepickerMode`
|
||||
<small class="badge">C</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `day`)_ -
|
||||
Current mode of the datepicker _(day|month|year)_. Can be used to initialize the datepicker in a specific mode.
|
||||
|
||||
* `formatDay`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `dd`)_ -
|
||||
Format of day in month.
|
||||
|
||||
* `formatMonth`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `MMMM`)_ -
|
||||
Format of month in year.
|
||||
|
||||
* `formatYear`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `yyyy`)_ -
|
||||
Format of year in year range.
|
||||
|
||||
* `formatDayHeader`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `EEE`)_ -
|
||||
Format of day in week header.
|
||||
|
||||
* `formatDayTitle`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `MMMM yyyy`)_ -
|
||||
Format of title when selecting day.
|
||||
|
||||
* `formatMonthTitle`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `yyyy`)_ -
|
||||
Format of title when selecting month.
|
||||
|
||||
* `initDate`
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `null`)_ -
|
||||
The initial date view when no model value is specified.
|
||||
|
||||
* `maxDate`
|
||||
<small class="badge">C</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `null`)_ -
|
||||
Defines the maximum available date. Requires a Javascript Date object.
|
||||
|
||||
* `maxMode`
|
||||
<small class="badge">C</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `year`)_ -
|
||||
Sets an upper limit for mode.
|
||||
|
||||
* `minDate`
|
||||
<small class="badge">C</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `null`)_ -
|
||||
Defines the minimum available date. Requires a Javascript Date object.
|
||||
|
||||
* `minMode`
|
||||
<small class="badge">C</small>
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
_(Default: `day`)_ -
|
||||
Sets a lower limit for mode.
|
||||
|
||||
* `monthColumns`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `3`)_ -
|
||||
Number of columns displayed in month selection.
|
||||
|
||||
* `ngModelOptions`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `null`)_ -
|
||||
Sets `ngModelOptions` for datepicker. This can be overridden through attribute usage
|
||||
|
||||
* `shortcutPropagation`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `false`)_ -
|
||||
An option to disable the propagation of the keydown event.
|
||||
|
||||
* `showWeeks`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `true`)_ -
|
||||
Whether to display week numbers.
|
||||
|
||||
* `startingDay`
|
||||
<small class="badge">C</small>
|
||||
*(Default: `$locale.DATETIME_FORMATS.FIRSTDAYOFWEEK`)* -
|
||||
Starting day of the week from 0-6 (0=Sunday, ..., 6=Saturday).
|
||||
|
||||
* `yearRows`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `4`)_ -
|
||||
Number of rows displayed in year selection.
|
||||
|
||||
* `yearColumns`
|
||||
<small class="badge">C</small>
|
||||
_(Default: `5`)_ -
|
||||
Number of columns displayed in year selection.
|
||||
|
||||
### Keyboard support
|
||||
|
||||
Depending on datepicker's current mode, the date may refer either to day, month or year. Accordingly, the term view refers either to a month, year or year range.
|
||||
|
||||
* `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.
|
||||
* `Up`: Move focus to the same column of the previous row. Will wrap to the appropriate row in the previous view.
|
||||
* `Down`: Move focus to the same column of the following row. Will wrap to the appropriate row in the following view.
|
||||
* `PgUp`: Move focus to the same date of the previous view. If that date does not exist, focus is placed on the last date of the month.
|
||||
* `PgDn`: Move focus to the same date of the following view. If that date does not exist, focus is placed on the last date of the month.
|
||||
* `Home`: Move to the first date of the view.
|
||||
* `End`: Move to the last date of the view.
|
||||
* `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.
|
||||
|
||||
**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');
|
||||
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]);
|
||||
});
|
||||
});
|
||||
@@ -1,97 +0,0 @@
|
||||
# $dialogProvider <small>(service in ui.bootstrap)</small>
|
||||
|
||||
## Description
|
||||
|
||||
Used for configuring global options for dialogs.
|
||||
|
||||
### Methods
|
||||
|
||||
#### `options(opts)`
|
||||
|
||||
Sets the default global options for your application. Options can be overridden when opening dialogs. Available options are:
|
||||
|
||||
* `backdrop`: a boolean value indicating whether a backdrop should be used or not, defaults to true
|
||||
* `dialogClass`: the css class for the modal div, defaults to 'modal'
|
||||
* `backdropClass`: the css class for the backdrop, defaults to 'modal-backdrop'
|
||||
* `transitionClass`: the css class that applies transitions to the modal and backdrop, defaults to 'fade'
|
||||
* `triggerClass`: the css class that triggers the transitions, defaults to 'in'
|
||||
* `dialogOpenClass`: the css class that is added to body when dialog is opened, defaults to 'modal-open'
|
||||
* `resolve`: members that will be resolved and passed to the controller as locals
|
||||
* `controller`: the controller to associate with the included partial view
|
||||
* `backdropFade`: a boolean value indicating whether the backdrop should fade in and out using a CSS transition, defaults to false
|
||||
* `dialogFade`: a boolean value indicating whether the modal should fade in and out using a CSS transition, defaults to false
|
||||
* `keyboard`: indicates whether the dialog should be closable by hitting the ESC key, defaults to true
|
||||
* `backdropClick`: indicates whether the dialog should be closable by clicking the backdrop area, defaults to true
|
||||
|
||||
Example:
|
||||
|
||||
var app = angular.module('App', ['ui.bootstrap.dialog'] , function($dialogProvider){
|
||||
$dialogProvider.options({backdropClick: false, modalFade: true});
|
||||
});
|
||||
|
||||
# $dialog service
|
||||
|
||||
## Description
|
||||
|
||||
Allows you to open dialogs from within your controller.
|
||||
|
||||
### Methods
|
||||
|
||||
#### `dialog([templateUrl[, controller]])`
|
||||
|
||||
Creates a new dialog, optionally setting the `templateUrl`, and `controller` options.
|
||||
|
||||
Example:
|
||||
|
||||
app.controller('MainCtrl', function($dialog, $scope) {
|
||||
$scope.openItemEditor = function(item){
|
||||
var d = $dialog.dialog({modalFade: false, resolve: {item: function(){ return angular.copy(item); } }});
|
||||
d.open('dialogs/item-editor.html', 'EditItemController');
|
||||
};
|
||||
});
|
||||
|
||||
// note that the resolved item as well as the dialog are injected in the dialog's controller
|
||||
app.controller('EditItemController', ['$scope', 'dialog', 'item', function($scope, dialog, item){
|
||||
$scope.item = item;
|
||||
$scope.submit = function(){
|
||||
dialog.close('ok');
|
||||
};
|
||||
}]);
|
||||
|
||||
#### `messageBox(title, message, buttons)`
|
||||
|
||||
Opens a message box with the specified `title`, `message` and a series of `buttons` can be provided, every button can specify:
|
||||
|
||||
* `label`: the label of the button
|
||||
* `result`: the result used to invoke the close method of the dialog
|
||||
* `cssClass`: optional, the CSS class (e.g. btn-primary) to apply to the button
|
||||
|
||||
Example:
|
||||
|
||||
app.controller('MainCtrl', function($dialog, $scope) {
|
||||
$scope.deleteItem = function(item){
|
||||
var msgbox = $dialog.messageBox('Delete Item', 'Are you sure?', [{label:'Yes, I\'m sure', result: 'yes'},{label:'Nope', result: 'no'}]);
|
||||
msgbox.open().then(function(result){
|
||||
if(result === 'yes') {deleteItem(item);}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
## Dialog class
|
||||
|
||||
The dialog object returned by the `$dialog` service methods `open` and `message`.
|
||||
|
||||
### Methods
|
||||
|
||||
#### `open`
|
||||
|
||||
(Re)Opens the dialog and returns a promise.
|
||||
|
||||
#### `close([result])`
|
||||
|
||||
Closes the dialog. Optionally a result can be specified. The result is used to resolve the promise returned by the `open` method.
|
||||
|
||||
#### `isOpen`
|
||||
|
||||
Returns true if the dialog is shown, else returns false.
|
||||
|
||||
@@ -1,289 +0,0 @@
|
||||
// The `$dialogProvider` can be used to configure global defaults for your
|
||||
// `$dialog` service.
|
||||
var dialogModule = angular.module('ui.bootstrap.dialog', ['ui.bootstrap.transition']);
|
||||
|
||||
dialogModule.controller('MessageBoxController', ['$scope', 'dialog', 'model', function($scope, dialog, model){
|
||||
$scope.title = model.title;
|
||||
$scope.message = model.message;
|
||||
$scope.buttons = model.buttons;
|
||||
$scope.close = function(res){
|
||||
dialog.close(res);
|
||||
};
|
||||
}]);
|
||||
|
||||
dialogModule.provider("$dialog", function(){
|
||||
|
||||
// The default options for all dialogs.
|
||||
var defaults = {
|
||||
backdrop: true,
|
||||
dialogClass: 'modal',
|
||||
backdropClass: 'modal-backdrop',
|
||||
transitionClass: 'fade',
|
||||
triggerClass: 'in',
|
||||
dialogOpenClass: 'modal-open',
|
||||
resolve:{},
|
||||
backdropFade: false,
|
||||
dialogFade:false,
|
||||
keyboard: true, // close with esc key
|
||||
backdropClick: true // only in conjunction with backdrop=true
|
||||
/* other options: template, templateUrl, controller */
|
||||
};
|
||||
|
||||
var globalOptions = {};
|
||||
|
||||
var activeBackdrops = {value : 0};
|
||||
|
||||
// The `options({})` allows global configuration of all dialogs in the application.
|
||||
//
|
||||
// var app = angular.module('App', ['ui.bootstrap.dialog'], function($dialogProvider){
|
||||
// // don't close dialog when backdrop is clicked by default
|
||||
// $dialogProvider.options({backdropClick: false});
|
||||
// });
|
||||
this.options = function(value){
|
||||
globalOptions = value;
|
||||
};
|
||||
|
||||
// Returns the actual `$dialog` service that is injected in controllers
|
||||
this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition", "$injector",
|
||||
function ($http, $document, $compile, $rootScope, $controller, $templateCache, $q, $transition, $injector) {
|
||||
|
||||
var body = $document.find('body');
|
||||
|
||||
function createElement(clazz) {
|
||||
var el = angular.element("<div>");
|
||||
el.addClass(clazz);
|
||||
return el;
|
||||
}
|
||||
|
||||
// The `Dialog` class represents a modal dialog. The dialog class can be invoked by providing an options object
|
||||
// containing at lest template or templateUrl and controller:
|
||||
//
|
||||
// var d = new Dialog({templateUrl: 'foo.html', controller: 'BarController'});
|
||||
//
|
||||
// Dialogs can also be created using templateUrl and controller as distinct arguments:
|
||||
//
|
||||
// var d = new Dialog('path/to/dialog.html', MyDialogController);
|
||||
function Dialog(opts) {
|
||||
|
||||
var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts);
|
||||
this._open = false;
|
||||
|
||||
this.backdropEl = createElement(options.backdropClass);
|
||||
if(options.backdropFade){
|
||||
this.backdropEl.addClass(options.transitionClass);
|
||||
this.backdropEl.removeClass(options.triggerClass);
|
||||
}
|
||||
|
||||
this.modalEl = createElement(options.dialogClass);
|
||||
if(options.dialogFade){
|
||||
this.modalEl.addClass(options.transitionClass);
|
||||
this.modalEl.removeClass(options.triggerClass);
|
||||
}
|
||||
|
||||
this.handledEscapeKey = function(e) {
|
||||
if (e.which === 27) {
|
||||
self.close();
|
||||
e.preventDefault();
|
||||
self.$scope.$apply();
|
||||
}
|
||||
};
|
||||
|
||||
this.handleBackDropClick = function(e) {
|
||||
self.close();
|
||||
e.preventDefault();
|
||||
self.$scope.$apply();
|
||||
};
|
||||
|
||||
this.handleLocationChange = function() {
|
||||
self.close();
|
||||
};
|
||||
}
|
||||
|
||||
// The `isOpen()` method returns wether the dialog is currently visible.
|
||||
Dialog.prototype.isOpen = function(){
|
||||
return this._open;
|
||||
};
|
||||
|
||||
// The `open(templateUrl, controller)` method opens the dialog.
|
||||
// Use the `templateUrl` and `controller` arguments if specifying them at dialog creation time is not desired.
|
||||
Dialog.prototype.open = function(templateUrl, controller){
|
||||
var self = this, options = this.options;
|
||||
|
||||
if(templateUrl){
|
||||
options.templateUrl = templateUrl;
|
||||
}
|
||||
if(controller){
|
||||
options.controller = controller;
|
||||
}
|
||||
|
||||
if(!(options.template || options.templateUrl)) {
|
||||
throw new Error('Dialog.open expected template or templateUrl, neither found. Use options or open method to specify them.');
|
||||
}
|
||||
|
||||
this._loadResolves().then(function(locals) {
|
||||
var $scope = locals.$scope = self.$scope = locals.$scope ? locals.$scope : $rootScope.$new();
|
||||
|
||||
self.modalEl.html(locals.$template);
|
||||
|
||||
if (self.options.controller) {
|
||||
var ctrl = $controller(self.options.controller, locals);
|
||||
self.modalEl.children().data('ngControllerController', ctrl);
|
||||
}
|
||||
|
||||
$compile(self.modalEl)($scope);
|
||||
self._addElementsToDom();
|
||||
body.addClass(self.options.dialogOpenClass);
|
||||
|
||||
// trigger tranisitions
|
||||
setTimeout(function(){
|
||||
if(self.options.dialogFade){ self.modalEl.addClass(self.options.triggerClass); }
|
||||
if(self.options.backdropFade){ self.backdropEl.addClass(self.options.triggerClass); }
|
||||
});
|
||||
|
||||
self._bindEvents();
|
||||
});
|
||||
|
||||
this.deferred = $q.defer();
|
||||
return this.deferred.promise;
|
||||
};
|
||||
|
||||
// closes the dialog and resolves the promise returned by the `open` method with the specified result.
|
||||
Dialog.prototype.close = function(result){
|
||||
var self = this;
|
||||
var fadingElements = this._getFadingElements();
|
||||
|
||||
body.removeClass(self.options.dialogOpenClass);
|
||||
if(fadingElements.length > 0){
|
||||
for (var i = fadingElements.length - 1; i >= 0; i--) {
|
||||
$transition(fadingElements[i], removeTriggerClass).then(onCloseComplete);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this._onCloseComplete(result);
|
||||
|
||||
function removeTriggerClass(el){
|
||||
el.removeClass(self.options.triggerClass);
|
||||
}
|
||||
|
||||
function onCloseComplete(){
|
||||
if(self._open){
|
||||
self._onCloseComplete(result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Dialog.prototype._getFadingElements = function(){
|
||||
var elements = [];
|
||||
if(this.options.dialogFade){
|
||||
elements.push(this.modalEl);
|
||||
}
|
||||
if(this.options.backdropFade){
|
||||
elements.push(this.backdropEl);
|
||||
}
|
||||
|
||||
return elements;
|
||||
};
|
||||
|
||||
Dialog.prototype._bindEvents = function() {
|
||||
if(this.options.keyboard){ body.bind('keydown', this.handledEscapeKey); }
|
||||
if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.bind('click', this.handleBackDropClick); }
|
||||
|
||||
this.$scope.$on('$locationChangeSuccess', this.handleLocationChange);
|
||||
};
|
||||
|
||||
Dialog.prototype._unbindEvents = function() {
|
||||
if(this.options.keyboard){ body.unbind('keydown', this.handledEscapeKey); }
|
||||
if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.unbind('click', this.handleBackDropClick); }
|
||||
};
|
||||
|
||||
Dialog.prototype._onCloseComplete = function(result) {
|
||||
this._removeElementsFromDom();
|
||||
this._unbindEvents();
|
||||
|
||||
this.deferred.resolve(result);
|
||||
};
|
||||
|
||||
Dialog.prototype._addElementsToDom = function(){
|
||||
body.append(this.modalEl);
|
||||
|
||||
if(this.options.backdrop) {
|
||||
if (activeBackdrops.value === 0) {
|
||||
body.append(this.backdropEl);
|
||||
}
|
||||
activeBackdrops.value++;
|
||||
}
|
||||
|
||||
this._open = true;
|
||||
};
|
||||
|
||||
Dialog.prototype._removeElementsFromDom = function(){
|
||||
this.modalEl.remove();
|
||||
|
||||
if(this.options.backdrop) {
|
||||
activeBackdrops.value--;
|
||||
if (activeBackdrops.value === 0) {
|
||||
this.backdropEl.remove();
|
||||
}
|
||||
}
|
||||
this._open = false;
|
||||
};
|
||||
|
||||
// Loads all `options.resolve` members to be used as locals for the controller associated with the dialog.
|
||||
Dialog.prototype._loadResolves = function(){
|
||||
var values = [], keys = [], templatePromise, self = this;
|
||||
|
||||
if (this.options.template) {
|
||||
templatePromise = $q.when(this.options.template);
|
||||
} else if (this.options.templateUrl) {
|
||||
templatePromise = $http.get(this.options.templateUrl, {cache:$templateCache})
|
||||
.then(function(response) { return response.data; });
|
||||
}
|
||||
|
||||
angular.forEach(this.options.resolve || [], function(value, key) {
|
||||
keys.push(key);
|
||||
values.push(angular.isString(value) ? $injector.get(value) : $injector.invoke(value));
|
||||
});
|
||||
|
||||
keys.push('$template');
|
||||
values.push(templatePromise);
|
||||
|
||||
return $q.all(values).then(function(values) {
|
||||
var locals = {};
|
||||
angular.forEach(values, function(value, index) {
|
||||
locals[keys[index]] = value;
|
||||
});
|
||||
locals.dialog = self;
|
||||
return locals;
|
||||
});
|
||||
};
|
||||
|
||||
// The actual `$dialog` service that is injected in controllers.
|
||||
return {
|
||||
// Creates a new `Dialog` with the specified options.
|
||||
dialog: function(opts){
|
||||
return new Dialog(opts);
|
||||
},
|
||||
// creates a new `Dialog` tied to the default message box template and controller.
|
||||
//
|
||||
// Arguments `title` and `message` are rendered in the modal header and body sections respectively.
|
||||
// The `buttons` array holds an object with the following members for each button to include in the
|
||||
// modal footer section:
|
||||
//
|
||||
// * `result`: the result to pass to the `close` method of the dialog when the button is clicked
|
||||
// * `label`: the label of the button
|
||||
// * `cssClass`: additional css class(es) to apply to the button for styling
|
||||
messageBox: function(title, message, buttons){
|
||||
return new Dialog({templateUrl: 'template/dialog/message.html', controller: 'MessageBoxController', resolve:
|
||||
{model: function() {
|
||||
return {
|
||||
title: title,
|
||||
message: message,
|
||||
buttons: buttons
|
||||
};
|
||||
}
|
||||
}});
|
||||
}
|
||||
};
|
||||
}];
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
<div ng-controller="DialogDemoCtrl">
|
||||
<div class="row-fluid">
|
||||
<div class="span6">
|
||||
<label class="checkbox"><input type=checkbox ng-model="opts.backdrop">Show backdrop</label>
|
||||
<label class="checkbox"><input type=checkbox ng-model="opts.dialogFade">Fade modal dialog </label>
|
||||
<label class="checkbox"><input type=checkbox ng-disabled="!opts.backdrop" ng-model="opts.backdropFade">Fade Backdrop</label>
|
||||
<hr>
|
||||
<label class="checkbox"><input type=checkbox ng-model="opts.keyboard">Close on Escape</label>
|
||||
<label class="checkbox"><input type=checkbox ng-disabled="!opts.backdrop" ng-model="opts.backdropClick">Close on backdrop click</label>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<p>Change options at will and press the open dialog button below!</p>
|
||||
<p><button class="btn btn-primary" ng-click="openDialog()">Open Dialog</button></p>
|
||||
|
||||
<p>Alternatively open a simple message box:</p>
|
||||
<p><button class="btn btn-primary" ng-click="openMessageBox()">Open Message Box</button></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,50 +0,0 @@
|
||||
function DialogDemoCtrl($scope, $dialog){
|
||||
|
||||
// Inlined template for demo
|
||||
var t = '<div class="modal-header">'+
|
||||
'<h1>This is the title</h1>'+
|
||||
'</div>'+
|
||||
'<div class="modal-body">'+
|
||||
'<p>Enter a value to pass to <code>close</code> as the result: <input ng-model="result" /></p>'+
|
||||
'</div>'+
|
||||
'<div class="modal-footer">'+
|
||||
'<button ng-click="close(result)" class="btn btn-primary" >Close</button>'+
|
||||
'</div>';
|
||||
|
||||
$scope.opts = {
|
||||
backdrop: true,
|
||||
keyboard: true,
|
||||
backdropClick: true,
|
||||
template: t, // OR: templateUrl: 'path/to/view.html',
|
||||
controller: 'TestDialogController'
|
||||
};
|
||||
|
||||
$scope.openDialog = function(){
|
||||
var d = $dialog.dialog($scope.opts);
|
||||
d.open().then(function(result){
|
||||
if(result)
|
||||
{
|
||||
alert('dialog closed with result: ' + result);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.openMessageBox = function(){
|
||||
var title = 'This is a message box';
|
||||
var msg = 'This is the content of the message box';
|
||||
var btns = [{result:'cancel', label: 'Cancel'}, {result:'ok', label: 'OK', cssClass: 'btn-primary'}];
|
||||
|
||||
$dialog.messageBox(title, msg, btns)
|
||||
.open()
|
||||
.then(function(result){
|
||||
alert('dialog closed with result: ' + result);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// the dialog is injected in the specified controller
|
||||
function TestDialogController($scope, dialog){
|
||||
$scope.close = function(result){
|
||||
dialog.close(result);
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user