From 82dccb450d4f60bcb4a5501c9fa8a00b148e91aa Mon Sep 17 00:00:00 2001 From: ecus6396 Date: Mon, 3 Nov 2014 16:15:36 +0100 Subject: [PATCH 1/2] new revision for maven branch --- .gitignore | 3 +- License | 191 - build.xml | 71 - dsmapi.wadl | 283 - lib/commons-beanutils-1.8.3.jar | Bin 232019 -> 0 bytes ...mmons-beanutils-bean-collections-1.8.3.jar | Bin 26574 -> 0 bytes lib/commons-beanutils-core-1.8.3.jar | Bin 206711 -> 0 bytes lib/commons-logging-1.1.3.jar | Bin 62050 -> 0 bytes nb-configuration.xml | 25 + nbactions.xml | 50 + nbproject/ant-deploy.xml | 111 - nbproject/build-impl.xml | 1422 -- nbproject/genfiles.properties | 11 - nbproject/project.properties | 100 - nbproject/project.xml | 44 - nbproject/rest-build.xml | 65 - pom.xml | 107 + postman/troubleTicket.json.postman_collection | 392 + soapui/DSMAPITT-soapui-project.xml | 1145 - soapui/application.wadl.xml | 84 - soapui/xsd0.xsd | 75 - .../application/config/ApplicationConfig.java | 45 - .../tmf/org/dsmapi/commons/utils/Format.java | 35 - src/java/tmf/org/dsmapi/hub/HubEvent.java | 41 - .../dsmapi/hub/service/AbstractFacade.java | 62 - .../org/dsmapi/hub/service/HubFacadeREST.java | 202 - .../dsmapi/hub/service/PostEventClient.java | 42 - .../tmf/org/dsmapi/hub/service/Publisher.java | 102 - .../dsmapi/hub/service/PublisherLocal.java | 30 - .../hub/service/RESTEventPublisher.java | 36 - .../hub/service/RESTEventPublisherLocal.java | 19 - src/java/tmf/org/dsmapi/tt/IncidentType.java | 13 - .../dsmapi/tt/client/TroubleTicketClient.java | 81 - .../dsmapi/tt/service/AdminFacadeREST.java | 153 - .../tt/service/TroubleTicketFacade.java | 197 - .../tt/service/TroubleTicketFacadeREST.java | 199 - .../dsmapi/tt/service/WorkflowValidator.java | 56 - .../tt/service/mapper/FacadeRestUtil.java | 188 - .../service/mapper/TroubleTicketReader.java | 83 - .../commons/exceptions/BadUsageException.java | 2 +- .../commons/exceptions/ExceptionBean.java | 2 +- .../commons/exceptions/ExceptionType.java | 5 +- .../exceptions/FunctionalException.java | 29 +- .../dsmapi/commons/exceptions/KeyValue.java | 39 + .../exceptions/TechnicalException.java | 113 + .../exceptions/UnknownResourceException.java | 2 +- .../commons/facade}/AbstractFacade.java | 347 +- .../tmf/dsmapi/commons/jaxrs/CorsFilter.java | 32 + .../commons/jaxrs/model}/JsonFault.java | 4 +- .../dsmapi/commons/jaxrs/model}/Report.java | 2 +- .../provider}/BadUsageExceptionMapper.java | 6 +- .../jaxrs/provider}/JacksonConfigurator.java | 2 +- .../provider}/JsonMappingExceptionMapper.java | 6 +- .../UnknowResourceExceptionMapper.java | 6 +- .../tmf/dsmapi/commons/utils/BeanUtils.java | 75 + .../utils/CustomJsonDateSerializer.java.toto | 33 + .../org/tmf/dsmapi/commons/utils/Jackson.java | 158 + .../org/tmf/dsmapi/commons/utils}/PATCH.java | 6 +- .../org/tmf/dsmapi/commons/utils/TMFDate.java | 64 + .../tmf/dsmapi/commons/utils/URIParser.java | 152 + .../java/org/tmf/dsmapi/hub/model/Event.java | 90 + .../tmf/dsmapi/hub/model/EventTypeEnum.java} | 36 +- .../java/org/tmf/dsmapi/hub/model}/Hub.java | 17 +- .../tmf/dsmapi/hub/service/EventFacade.java | 34 + .../dsmapi/hub/service/EventPublisher.java | 105 + .../hub/service/EventPublisherLocal.java | 49 + .../tmf}/dsmapi/hub/service/HubFacade.java | 14 +- .../tmf/dsmapi/hub/service/RESTClient.java | 58 + .../hub/service/RESTEventPublisher.java | 56 + .../hub/service/RESTEventPublisherLocal.java | 16 + .../tmf/dsmapi/jaxrs/ApplicationConfig.java | 33 + .../dsmapi/jaxrs/resource/AdminResource.java | 237 + .../dsmapi/jaxrs/resource/HubResource.java | 177 + .../jaxrs/resource/TroubleTicketResource.java | 284 + .../tmf/dsmapi/subscriber/model/EventBag.java | 51 + .../dsmapi/subscriber/resource/Current.java | 54 + .../tmf/dsmapi/subscriber/resource/Event.java | 92 + .../dsmapi/subscriber/resource/History.java | 54 + .../subscriber/service/EventApiFacade.java | 28 + .../troubleTicket/model/IncidentType.java | 36 + .../dsmapi/troubleTicket/model/JsonError.java | 74 + .../tmf/dsmapi/troubleTicket/model}/Note.java | 26 +- .../troubleTicket/model}/RelatedObject.java | 18 +- .../troubleTicket/model}/RelatedParty.java | 18 +- .../dsmapi/troubleTicket/model/Report.java | 70 + .../dsmapi/troubleTicket/model}/Severity.java | 19 +- .../dsmapi/troubleTicket/model}/Status.java | 35 +- .../troubleTicket/model}/SubStatus.java | 11 +- .../troubleTicket/model}/TroubleTicket.java | 170 +- .../model}/TroubleTicketField.java | 68 +- .../service/TroubleTicketFacade.java | 219 + .../resources/META-INF}/MANIFEST.MF | 0 .../resources/META-INF}/persistence.xml | 9 +- src/main/webapp/WEB-INF/glassfish-web.xml | 21 + {web => src/main/webapp}/WEB-INF/web.xml | 43 +- {web => src/main/webapp}/index.jsp | 10 +- .../webapp/subscriber/ui/css/bootstrap.css | 6307 +++++ .../webapp/subscriber/ui/img/creampaper.png | Bin 0 -> 12618 bytes .../ui/img/glyphicons-halflings-white.png | Bin 0 -> 8777 bytes .../ui/img/glyphicons-halflings.png | Bin 0 -> 12799 bytes src/main/webapp/subscriber/ui/img/people.jpg | Bin 0 -> 95934 bytes .../webapp/subscriber/ui/img/people_small.jpg | Bin 0 -> 25132 bytes src/main/webapp/subscriber/ui/index.html | 48 + .../ui/js/angular-bootstrap/bower.json | 11 + .../js/angular-bootstrap/ui-bootstrap-tpls.js | 4116 ++++ .../ui-bootstrap-tpls.min.js | 10 + .../ui/js/angular-bootstrap/ui-bootstrap.js | 3799 +++ .../js/angular-bootstrap/ui-bootstrap.min.js | 9 + .../subscriber/ui/js/angular-ui/unique.js | 44 + .../ui/js/angular/angular-animate.js | 1483 ++ .../ui/js/angular/angular-cookies.js | 202 + .../ui/js/angular/angular-resource.js | 594 + .../subscriber/ui/js/angular/angular-route.js | 920 + .../subscriber/ui/js/angular/angular.js | 20560 ++++++++++++++++ src/main/webapp/subscriber/ui/js/app/app.js | 19 + .../subscriber/ui/js/app/controllers.js | 39 + .../webapp/subscriber/ui/js/app/filters.js | 54 + .../webapp/subscriber/ui/js/app/services.js | 53 + .../subscriber/ui/js/bootstrap/bootstrap.js | 2279 ++ .../ui/js/bootstrap/bootstrap.min.js | 7 + src/main/webapp/subscriber/ui/js/fuse/fuse.js | 331 + .../webapp/subscriber/ui/js/jquery/jquery.js | 8842 +++++++ .../subscriber/ui/js/jquery/jquery.min.js | 6 + .../subscriber/ui/js/markdown/markdown.js | 1642 ++ .../subscriber/ui/properties/app.properties | 3 + src/main/webapp/subscriber/ui/view/list.html | 58 + .../webapp/subscriber/ui/view/settings.html | 17 + .../subscriber/ui/view/viewCurrent.html | 46 + src/script/admin.sh | 93 - src/script/clean.sh | 7 - src/script/commons/conf.sh | 10 - src/script/commons/curl.sh | 70 - src/script/commons/curl_template | 1 - src/script/json/create-bad-usage-id.json | 45 - .../json/create-bad-usage-mandatory.json | 43 - src/script/json/create.json | 45 - src/script/json/dropped text.txt | 1 - src/script/json/error.json | 8 - src/script/json/list-large.json | 412 - src/script/json/list-small.json | 125 - src/script/json/patch.json | 15 - src/script/json/put.json | 39 - src/script/readme.txt | 55 - src/script/ticket.sh | 104 - web/WEB-INF/glassfish-web.xml | 20 - web/app.gif | Bin 989 -> 0 bytes web/cc.gif | Bin 857 -> 0 bytes web/cg.gif | Bin 890 -> 0 bytes web/collapse.gif | Bin 87 -> 0 bytes web/css_master-all.css | 5088 ---- web/expand.gif | Bin 112 -> 0 bytes web/images/background_border_bottom.gif | Bin 49 -> 0 bytes web/images/bg_gradient.gif | Bin 51 -> 0 bytes web/images/level1_deselect.jpg | Bin 25052 -> 0 bytes web/images/level1_selected-1lvl.jpg | Bin 25052 -> 0 bytes web/images/masthead.png | Bin 2826 -> 0 bytes web/images/masthead_link_enabled.gif | Bin 51 -> 0 bytes web/images/masthead_link_roll.gif | Bin 51 -> 0 bytes web/images/pbdis.png | Bin 2810 -> 0 bytes web/images/pbena.png | Bin 2811 -> 0 bytes web/images/pbmou.png | Bin 2807 -> 0 bytes web/images/pbsel.png | Bin 2809 -> 0 bytes web/images/pname.png | Bin 1963 -> 0 bytes web/images/primary-enabled.gif | Bin 93 -> 0 bytes web/images/primary-roll.gif | Bin 94 -> 0 bytes web/images/secondary-enabled.gif | Bin 93 -> 0 bytes web/images/tbsel.png | Bin 590 -> 0 bytes web/images/tbuns.png | Bin 699 -> 0 bytes web/item.gif | Bin 963 -> 0 bytes web/og.gif | Bin 884 -> 0 bytes web/test-resbeans.css | 320 - web/test-resbeans.html | 56 - web/test-resbeans.js | 2017 -- 173 files changed, 55258 insertions(+), 13995 deletions(-) delete mode 100644 License delete mode 100644 build.xml delete mode 100644 dsmapi.wadl delete mode 100644 lib/commons-beanutils-1.8.3.jar delete mode 100644 lib/commons-beanutils-bean-collections-1.8.3.jar delete mode 100644 lib/commons-beanutils-core-1.8.3.jar delete mode 100644 lib/commons-logging-1.1.3.jar create mode 100644 nb-configuration.xml create mode 100644 nbactions.xml delete mode 100644 nbproject/ant-deploy.xml delete mode 100644 nbproject/build-impl.xml delete mode 100644 nbproject/genfiles.properties delete mode 100644 nbproject/project.properties delete mode 100644 nbproject/project.xml delete mode 100644 nbproject/rest-build.xml create mode 100644 pom.xml create mode 100644 postman/troubleTicket.json.postman_collection delete mode 100644 soapui/DSMAPITT-soapui-project.xml delete mode 100644 soapui/application.wadl.xml delete mode 100644 soapui/xsd0.xsd delete mode 100644 src/java/org/netbeans/rest/application/config/ApplicationConfig.java delete mode 100644 src/java/tmf/org/dsmapi/commons/utils/Format.java delete mode 100644 src/java/tmf/org/dsmapi/hub/HubEvent.java delete mode 100644 src/java/tmf/org/dsmapi/hub/service/AbstractFacade.java delete mode 100644 src/java/tmf/org/dsmapi/hub/service/HubFacadeREST.java delete mode 100644 src/java/tmf/org/dsmapi/hub/service/PostEventClient.java delete mode 100644 src/java/tmf/org/dsmapi/hub/service/Publisher.java delete mode 100644 src/java/tmf/org/dsmapi/hub/service/PublisherLocal.java delete mode 100644 src/java/tmf/org/dsmapi/hub/service/RESTEventPublisher.java delete mode 100644 src/java/tmf/org/dsmapi/hub/service/RESTEventPublisherLocal.java delete mode 100644 src/java/tmf/org/dsmapi/tt/IncidentType.java delete mode 100644 src/java/tmf/org/dsmapi/tt/client/TroubleTicketClient.java delete mode 100644 src/java/tmf/org/dsmapi/tt/service/AdminFacadeREST.java delete mode 100644 src/java/tmf/org/dsmapi/tt/service/TroubleTicketFacade.java delete mode 100644 src/java/tmf/org/dsmapi/tt/service/TroubleTicketFacadeREST.java delete mode 100644 src/java/tmf/org/dsmapi/tt/service/WorkflowValidator.java delete mode 100644 src/java/tmf/org/dsmapi/tt/service/mapper/FacadeRestUtil.java delete mode 100644 src/java/tmf/org/dsmapi/tt/service/mapper/TroubleTicketReader.java rename src/{java/tmf/org => main/java/org/tmf}/dsmapi/commons/exceptions/BadUsageException.java (86%) rename src/{java/tmf/org => main/java/org/tmf}/dsmapi/commons/exceptions/ExceptionBean.java (93%) rename src/{java/tmf/org => main/java/org/tmf}/dsmapi/commons/exceptions/ExceptionType.java (78%) rename src/{java/tmf/org => main/java/org/tmf}/dsmapi/commons/exceptions/FunctionalException.java (71%) create mode 100644 src/main/java/org/tmf/dsmapi/commons/exceptions/KeyValue.java create mode 100644 src/main/java/org/tmf/dsmapi/commons/exceptions/TechnicalException.java rename src/{java/tmf/org => main/java/org/tmf}/dsmapi/commons/exceptions/UnknownResourceException.java (86%) rename src/{java/tmf/org/dsmapi/tt/service => main/java/org/tmf/dsmapi/commons/facade}/AbstractFacade.java (51%) create mode 100644 src/main/java/org/tmf/dsmapi/commons/jaxrs/CorsFilter.java rename src/{java/tmf/org/dsmapi/tt/service => main/java/org/tmf/dsmapi/commons/jaxrs/model}/JsonFault.java (90%) rename src/{java/tmf/org/dsmapi/tt/service => main/java/org/tmf/dsmapi/commons/jaxrs/model}/Report.java (97%) rename src/{java/tmf/org/dsmapi/tt/service/mapper => main/java/org/tmf/dsmapi/commons/jaxrs/provider}/BadUsageExceptionMapper.java (77%) rename src/{java/tmf/org/dsmapi/tt/service/mapper => main/java/org/tmf/dsmapi/commons/jaxrs/provider}/JacksonConfigurator.java (90%) rename src/{java/tmf/org/dsmapi/tt/service/mapper => main/java/org/tmf/dsmapi/commons/jaxrs/provider}/JsonMappingExceptionMapper.java (80%) rename src/{java/tmf/org/dsmapi/tt/service/mapper => main/java/org/tmf/dsmapi/commons/jaxrs/provider}/UnknowResourceExceptionMapper.java (76%) create mode 100644 src/main/java/org/tmf/dsmapi/commons/utils/BeanUtils.java create mode 100644 src/main/java/org/tmf/dsmapi/commons/utils/CustomJsonDateSerializer.java.toto create mode 100644 src/main/java/org/tmf/dsmapi/commons/utils/Jackson.java rename src/{java/tmf/org/dsmapi/commons/jaxrs => main/java/org/tmf/dsmapi/commons/utils}/PATCH.java (82%) create mode 100644 src/main/java/org/tmf/dsmapi/commons/utils/TMFDate.java create mode 100644 src/main/java/org/tmf/dsmapi/commons/utils/URIParser.java create mode 100644 src/main/java/org/tmf/dsmapi/hub/model/Event.java rename src/{java/tmf/org/dsmapi/hub/TroubleTicketEventTypeEnum.java => main/java/org/tmf/dsmapi/hub/model/EventTypeEnum.java} (58%) rename src/{java/tmf/org/dsmapi/hub => main/java/org/tmf/dsmapi/hub/model}/Hub.java (75%) create mode 100644 src/main/java/org/tmf/dsmapi/hub/service/EventFacade.java create mode 100644 src/main/java/org/tmf/dsmapi/hub/service/EventPublisher.java create mode 100644 src/main/java/org/tmf/dsmapi/hub/service/EventPublisherLocal.java rename src/{java/tmf/org => main/java/org/tmf}/dsmapi/hub/service/HubFacade.java (73%) create mode 100644 src/main/java/org/tmf/dsmapi/hub/service/RESTClient.java create mode 100644 src/main/java/org/tmf/dsmapi/hub/service/RESTEventPublisher.java create mode 100644 src/main/java/org/tmf/dsmapi/hub/service/RESTEventPublisherLocal.java create mode 100644 src/main/java/org/tmf/dsmapi/jaxrs/ApplicationConfig.java create mode 100644 src/main/java/org/tmf/dsmapi/jaxrs/resource/AdminResource.java create mode 100644 src/main/java/org/tmf/dsmapi/jaxrs/resource/HubResource.java create mode 100644 src/main/java/org/tmf/dsmapi/jaxrs/resource/TroubleTicketResource.java create mode 100644 src/main/java/org/tmf/dsmapi/subscriber/model/EventBag.java create mode 100644 src/main/java/org/tmf/dsmapi/subscriber/resource/Current.java create mode 100644 src/main/java/org/tmf/dsmapi/subscriber/resource/Event.java create mode 100644 src/main/java/org/tmf/dsmapi/subscriber/resource/History.java create mode 100644 src/main/java/org/tmf/dsmapi/subscriber/service/EventApiFacade.java create mode 100644 src/main/java/org/tmf/dsmapi/troubleTicket/model/IncidentType.java create mode 100644 src/main/java/org/tmf/dsmapi/troubleTicket/model/JsonError.java rename src/{java/tmf/org/dsmapi/tt => main/java/org/tmf/dsmapi/troubleTicket/model}/Note.java (78%) rename src/{java/tmf/org/dsmapi/tt => main/java/org/tmf/dsmapi/troubleTicket/model}/RelatedObject.java (82%) rename src/{java/tmf/org/dsmapi/tt => main/java/org/tmf/dsmapi/troubleTicket/model}/RelatedParty.java (83%) create mode 100644 src/main/java/org/tmf/dsmapi/troubleTicket/model/Report.java rename src/{java/tmf/org/dsmapi/tt => main/java/org/tmf/dsmapi/troubleTicket/model}/Severity.java (72%) rename src/{java/tmf/org/dsmapi/tt => main/java/org/tmf/dsmapi/troubleTicket/model}/Status.java (65%) rename src/{java/tmf/org/dsmapi/tt => main/java/org/tmf/dsmapi/troubleTicket/model}/SubStatus.java (55%) rename src/{java/tmf/org/dsmapi/tt => main/java/org/tmf/dsmapi/troubleTicket/model}/TroubleTicket.java (77%) rename src/{java/tmf/org/dsmapi/tt => main/java/org/tmf/dsmapi/troubleTicket/model}/TroubleTicketField.java (75%) create mode 100644 src/main/java/org/tmf/dsmapi/troubleTicket/service/TroubleTicketFacade.java rename src/{conf => main/resources/META-INF}/MANIFEST.MF (100%) rename src/{conf => main/resources/META-INF}/persistence.xml (57%) create mode 100644 src/main/webapp/WEB-INF/glassfish-web.xml rename {web => src/main/webapp}/WEB-INF/web.xml (52%) rename {web => src/main/webapp}/index.jsp (67%) create mode 100644 src/main/webapp/subscriber/ui/css/bootstrap.css create mode 100644 src/main/webapp/subscriber/ui/img/creampaper.png create mode 100644 src/main/webapp/subscriber/ui/img/glyphicons-halflings-white.png create mode 100644 src/main/webapp/subscriber/ui/img/glyphicons-halflings.png create mode 100644 src/main/webapp/subscriber/ui/img/people.jpg create mode 100644 src/main/webapp/subscriber/ui/img/people_small.jpg create mode 100644 src/main/webapp/subscriber/ui/index.html create mode 100644 src/main/webapp/subscriber/ui/js/angular-bootstrap/bower.json create mode 100644 src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap-tpls.js create mode 100644 src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap-tpls.min.js create mode 100644 src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap.js create mode 100644 src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap.min.js create mode 100644 src/main/webapp/subscriber/ui/js/angular-ui/unique.js create mode 100644 src/main/webapp/subscriber/ui/js/angular/angular-animate.js create mode 100644 src/main/webapp/subscriber/ui/js/angular/angular-cookies.js create mode 100644 src/main/webapp/subscriber/ui/js/angular/angular-resource.js create mode 100644 src/main/webapp/subscriber/ui/js/angular/angular-route.js create mode 100644 src/main/webapp/subscriber/ui/js/angular/angular.js create mode 100644 src/main/webapp/subscriber/ui/js/app/app.js create mode 100644 src/main/webapp/subscriber/ui/js/app/controllers.js create mode 100644 src/main/webapp/subscriber/ui/js/app/filters.js create mode 100644 src/main/webapp/subscriber/ui/js/app/services.js create mode 100644 src/main/webapp/subscriber/ui/js/bootstrap/bootstrap.js create mode 100644 src/main/webapp/subscriber/ui/js/bootstrap/bootstrap.min.js create mode 100644 src/main/webapp/subscriber/ui/js/fuse/fuse.js create mode 100644 src/main/webapp/subscriber/ui/js/jquery/jquery.js create mode 100644 src/main/webapp/subscriber/ui/js/jquery/jquery.min.js create mode 100644 src/main/webapp/subscriber/ui/js/markdown/markdown.js create mode 100644 src/main/webapp/subscriber/ui/properties/app.properties create mode 100644 src/main/webapp/subscriber/ui/view/list.html create mode 100644 src/main/webapp/subscriber/ui/view/settings.html create mode 100644 src/main/webapp/subscriber/ui/view/viewCurrent.html delete mode 100755 src/script/admin.sh delete mode 100755 src/script/clean.sh delete mode 100644 src/script/commons/conf.sh delete mode 100755 src/script/commons/curl.sh delete mode 100644 src/script/commons/curl_template delete mode 100644 src/script/json/create-bad-usage-id.json delete mode 100644 src/script/json/create-bad-usage-mandatory.json delete mode 100644 src/script/json/create.json delete mode 100644 src/script/json/dropped text.txt delete mode 100644 src/script/json/error.json delete mode 100644 src/script/json/list-large.json delete mode 100644 src/script/json/list-small.json delete mode 100644 src/script/json/patch.json delete mode 100644 src/script/json/put.json delete mode 100644 src/script/readme.txt delete mode 100755 src/script/ticket.sh delete mode 100644 web/WEB-INF/glassfish-web.xml delete mode 100644 web/app.gif delete mode 100644 web/cc.gif delete mode 100644 web/cg.gif delete mode 100644 web/collapse.gif delete mode 100644 web/css_master-all.css delete mode 100644 web/expand.gif delete mode 100644 web/images/background_border_bottom.gif delete mode 100644 web/images/bg_gradient.gif delete mode 100644 web/images/level1_deselect.jpg delete mode 100644 web/images/level1_selected-1lvl.jpg delete mode 100644 web/images/masthead.png delete mode 100644 web/images/masthead_link_enabled.gif delete mode 100644 web/images/masthead_link_roll.gif delete mode 100644 web/images/pbdis.png delete mode 100644 web/images/pbena.png delete mode 100644 web/images/pbmou.png delete mode 100644 web/images/pbsel.png delete mode 100644 web/images/pname.png delete mode 100644 web/images/primary-enabled.gif delete mode 100644 web/images/primary-roll.gif delete mode 100644 web/images/secondary-enabled.gif delete mode 100644 web/images/tbsel.png delete mode 100644 web/images/tbuns.png delete mode 100644 web/item.gif delete mode 100644 web/og.gif delete mode 100644 web/test-resbeans.css delete mode 100644 web/test-resbeans.html delete mode 100644 web/test-resbeans.js diff --git a/.gitignore b/.gitignore index a568390..029b281 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /nbproject/private/ /build/ -/dist/ \ No newline at end of file +/dist/ +/target/ \ No newline at end of file diff --git a/License b/License deleted file mode 100644 index 37ec93a..0000000 --- a/License +++ /dev/null @@ -1,191 +0,0 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, "control" means (i) the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising -permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -"Object" form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -"submitted" means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -2. Grant of Copyright License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -3. Grant of Patent License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of -this License; and -You must cause any modified files to carry prominent notices stating that You -changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, -all copyright, patent, trademark, and attribution notices from the Source form -of the Work, excluding those notices that do not pertain to any part of the -Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any -Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of -the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -5. Submission of Contributions. - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -6. Trademarks. - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -8. Limitation of Liability. - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work - -To apply the Apache License to your work, attach the following boilerplate -notice, with the fields enclosed by brackets "[]" replaced with your own -identifying information. (Don't include the brackets!) The text should be -enclosed in the appropriate comment syntax for the file format. We also -recommend that a file or class name and description of purpose be included on -the same "printed page" as the copyright notice for easier identification within -third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/build.xml b/build.xml deleted file mode 100644 index 1bf33b8..0000000 --- a/build.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - Builds, tests, and runs the project DSTroubleTicket. - - - diff --git a/dsmapi.wadl b/dsmapi.wadl deleted file mode 100644 index 0990596..0000000 --- a/dsmapi.wadl +++ /dev/null @@ -1,283 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - The content type. - - - - - - - - - - - - - - - - - - - The content type. - - - - - - - - - The content type. - - - - - - - - - - - - - - - - - - - - - - - - - - - The content type. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/commons-beanutils-1.8.3.jar b/lib/commons-beanutils-1.8.3.jar deleted file mode 100644 index 218510bc5d65ac161aca7e4b2cb575fbbee5d313..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232019 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 UAPWOSdhW&-k7$Q2^duWC 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 z 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 Cq5*~g diff --git a/lib/commons-logging-1.1.3.jar b/lib/commons-logging-1.1.3.jar deleted file mode 100644 index ab5125407a6a77767330e29a028b97c3ac64ce18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62050 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 diff --git a/nb-configuration.xml b/nb-configuration.xml new file mode 100644 index 0000000..6da2fc4 --- /dev/null +++ b/nb-configuration.xml @@ -0,0 +1,25 @@ + + + + + + ide + /less:/css + false + false + + + /scss:/css + subscriber/ui/js/angular + + diff --git a/nbactions.xml b/nbactions.xml new file mode 100644 index 0000000..4a3c4fc --- /dev/null +++ b/nbactions.xml @@ -0,0 +1,50 @@ + + + + run + + war + ear + ejb + + + package + + + true + subscriber/ui/index.html#/list + + + + debug + + war + ear + ejb + + + package + + + true + true + subscriber/ui/index.html#/list + + + + profile + + ejb + ear + war + + + package + + + true + true + subscriber/ui/index.html#/list + + + diff --git a/nbproject/ant-deploy.xml b/nbproject/ant-deploy.xml deleted file mode 100644 index 2d5f877..0000000 --- a/nbproject/ant-deploy.xml +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml deleted file mode 100644 index e034136..0000000 --- a/nbproject/build-impl.xml +++ /dev/null @@ -1,1422 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must set src.dir - Must set test.src.dir - Must set build.dir - Must set build.web.dir - Must set build.generated.dir - Must set dist.dir - Must set build.classes.dir - Must set dist.javadoc.dir - Must set build.test.classes.dir - Must set build.test.results.dir - Must set build.classes.excludes - Must set dist.war - - - - - - - - - -The Java EE server classpath is not correctly set up - server home directory is missing. -Either open the project in the IDE and assign the server or setup the server classpath manually. -For example like this: - ant -Dj2ee.server.home=<app_server_installation_directory> - - -The Java EE server classpath is not correctly set up. Your active server type is ${j2ee.server.type}. -Either open the project in the IDE and assign the server or setup the server classpath manually. -For example like this: - ant -Duser.properties.file=<path_to_property_file> (where you put the property "j2ee.platform.classpath" in a .properties file) -or ant -Dj2ee.platform.classpath=<server_classpath> (where no properties file is used) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must set javac.includes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No tests executed. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -The libs.CopyLibs.classpath property is not set up. -This property must point to -org-netbeans-modules-java-j2seproject-copylibstask.jar file which is part -of NetBeans IDE installation and is usually located at -<netbeans_installation>/java<version>/ant/extra folder. -Either open the project in the IDE and make sure CopyLibs library -exists or setup the property manually. For example like this: - ant -Dlibs.CopyLibs.classpath=a/path/to/org-netbeans-modules-java-j2seproject-copylibstask.jar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must set JVM to use for profiling in profiler.info.jvm - Must set profiler agent JVM arguments in profiler.info.jvmargs.agent - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select some files in the IDE or set javac.includes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select some files in the IDE or set javac.jsp.includes - - - - - - - - - - - - - - - - - - - - - - - - - - Must select a file in the IDE or set jsp.includes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Browser not found, cannot launch the deployed application. Try to set the BROWSER environment variable. - - - Launching ${browse.url} - - - - - - Must select one file in the IDE or set run.class - - - - Must select one file in the IDE or set run.class - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select one file in the IDE or set debug.class - - - - - - - - - - - - Must select one file in the IDE or set debug.class - - - - - Must set fix.includes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This target only works when run from inside the NetBeans IDE. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select some files in the IDE or set javac.includes - - - - - - - - - - - - - - - - - - - Some tests failed; see details above. - - - - - - - - - Must select some files in the IDE or set test.includes - - - - Some tests failed; see details above. - - - - Must select some files in the IDE or set test.class - Must select some method in the IDE or set test.method - - - - Some tests failed; see details above. - - - - - Must select one file in the IDE or set test.class - - - - Must select one file in the IDE or set test.class - Must select some method in the IDE or set test.method - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties deleted file mode 100644 index 8f83fb5..0000000 --- a/nbproject/genfiles.properties +++ /dev/null @@ -1,11 +0,0 @@ -build.xml.data.CRC32=42cd1da8 -build.xml.script.CRC32=764136d4 -build.xml.stylesheet.CRC32=651128d4@1.43.0.1 -# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. -# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. -nbproject/build-impl.xml.data.CRC32=42cd1da8 -nbproject/build-impl.xml.script.CRC32=312db2ff -nbproject/build-impl.xml.stylesheet.CRC32=5459df51@1.43.0.1 -nbproject/rest-build.xml.data.CRC32=86f5e137 -nbproject/rest-build.xml.script.CRC32=95f17fca -nbproject/rest-build.xml.stylesheet.CRC32=5f13befe@1.18.1 diff --git a/nbproject/project.properties b/nbproject/project.properties deleted file mode 100644 index abb56b6..0000000 --- a/nbproject/project.properties +++ /dev/null @@ -1,100 +0,0 @@ -annotation.processing.enabled=true -annotation.processing.enabled.in.editor=true -annotation.processing.processor.options=-Aeclipselink.canonicalmodel.use_static_factory=false -annotation.processing.processors.list= -annotation.processing.run.all.processors=true -annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output -auxiliary.org-netbeans-modules-projectapi.jsf_2e_language=Facelets -build.classes.dir=${build.web.dir}/WEB-INF/classes -build.classes.excludes=**/*.java,**/*.form -build.dir=build -build.generated.dir=${build.dir}/generated -build.generated.sources.dir=${build.dir}/generated-sources -build.test.classes.dir=${build.dir}/test/classes -build.test.results.dir=${build.dir}/test/results -build.web.dir=${build.dir}/web -build.web.excludes=${build.classes.excludes} -client.urlPart= -compile.jsps=false -conf.dir=${source.root}/conf -debug.classpath=${build.classes.dir}:${javac.classpath} -debug.test.classpath=\ - ${run.test.classpath} -display.browser=true -dist.dir=dist -dist.ear.war=${dist.dir}/${war.ear.name} -dist.javadoc.dir=${dist.dir}/javadoc -dist.war=${dist.dir}/${war.name} -endorsed.classpath=\ - ${libs.javaee-endorsed-api-6.0.classpath} -excludes= -file.reference.commons-beanutils-1.8.3.jar=lib/commons-beanutils-1.8.3.jar -file.reference.commons-beanutils-bean-collections-1.8.3.jar=lib/commons-beanutils-bean-collections-1.8.3.jar -file.reference.commons-beanutils-core-1.8.3.jar=lib/commons-beanutils-core-1.8.3.jar -file.reference.commons-logging-1.1.3.jar=lib/commons-logging-1.1.3.jar -includes=** -j2ee.compile.on.save=true -j2ee.copy.static.files.on.save=true -j2ee.deploy.on.save=true -j2ee.platform=1.6-web -j2ee.platform.classpath=${j2ee.server.middleware}/mq/lib/jaxm-api.jar:${j2ee.server.home}/modules/endorsed/webservices-api-osgi.jar:${j2ee.server.home}/modules/endorsed/jaxb-api-osgi.jar:${j2ee.server.home}/modules/endorsed/javax.annotation.jar:${j2ee.server.home}/modules/javax.faces.jar:${j2ee.server.home}/modules/bean-validator.jar:${j2ee.server.home}/modules/javax.el-api.jar:${j2ee.server.home}/modules/jersey-core.jar:${j2ee.server.home}/modules/javax.servlet.jsp.jstl.jar:${j2ee.server.home}/modules/javax.servlet.jsp.jar:${j2ee.server.home}/modules/javax.persistence.jar:${j2ee.server.home}/modules/javax.resource.jar:${j2ee.server.home}/modules/javax.servlet-api.jar:${j2ee.server.home}/modules/javax.servlet.jsp-api.jar:${j2ee.server.home}/modules/weld-osgi-bundle.jar:${j2ee.server.home}/modules/javax.security.auth.message.jar:${j2ee.server.home}/modules/javax.management.j2ee.jar:${j2ee.server.home}/modules/javax.enterprise.deploy.jar:${j2ee.server.home}/modules/javax.servlet.jsp.jstl-api.jar:${j2ee.server.home}/modules/jaxb-osgi.jar:${j2ee.server.home}/modules/javax.mail.jar:${j2ee.server.home}/modules/javax.security.jacc.jar:${j2ee.server.home}/modules/javax.el.jar:${j2ee.server.home}/modules/webservices-osgi.jar:${j2ee.server.home}/modules/javax.ejb.jar:${j2ee.server.home}/modules/javax.transaction.jar:${j2ee.server.home}/modules/javax.jms.jar -j2ee.platform.embeddableejb.classpath=${j2ee.server.home}/lib/embedded/glassfish-embedded-static-shell.jar -j2ee.platform.wscompile.classpath=${j2ee.server.home}/modules/webservices-osgi.jar -j2ee.platform.wsgen.classpath=${j2ee.server.home}/modules/webservices-osgi.jar:${j2ee.server.home}/modules/endorsed/webservices-api-osgi.jar:${j2ee.server.home}/modules/jaxb-osgi.jar:${j2ee.server.home}/modules/endorsed/jaxb-api-osgi.jar:${j2ee.server.home}/modules/javax.ejb.jar -j2ee.platform.wsimport.classpath=${j2ee.server.home}/modules/webservices-osgi.jar:${j2ee.server.home}/modules/endorsed/webservices-api-osgi.jar:${j2ee.server.home}/modules/jaxb-osgi.jar:${j2ee.server.home}/modules/endorsed/jaxb-api-osgi.jar:${j2ee.server.home}/modules/javax.ejb.jar -j2ee.platform.wsit.classpath= -j2ee.server.type=gfv3ee6 -jar.compress=false -javac.classpath=\ - ${libs.restlib.classpath}:\ - ${file.reference.commons-beanutils-1.8.3.jar}:\ - ${file.reference.commons-beanutils-bean-collections-1.8.3.jar}:\ - ${file.reference.commons-beanutils-core-1.8.3.jar}:\ - ${file.reference.commons-logging-1.1.3.jar} -# Space-separated list of extra javac options -javac.compilerargs= -javac.debug=true -javac.deprecation=false -javac.processorpath=\ - ${javac.classpath}:\ - ${libs.eclipselink.classpath}:\ - ${libs.eclipselinkmodelgen.classpath} -javac.source=1.6 -javac.target=1.6 -javac.test.classpath=\ - ${javac.classpath}:\ - ${build.classes.dir} -javac.test.processorpath=\ - ${javac.test.classpath} -javadoc.additionalparam= -javadoc.author=false -javadoc.encoding=${source.encoding} -javadoc.noindex=false -javadoc.nonavbar=false -javadoc.notree=false -javadoc.preview=true -javadoc.private=false -javadoc.splitindex=true -javadoc.use=true -javadoc.version=false -javadoc.windowtitle= -lib.dir=${web.docbase.dir}/WEB-INF/lib -persistence.xml.dir=${conf.dir} -platform.active=default_platform -resource.dir=setup -rest.config.type=ide -run.test.classpath=\ - ${javac.test.classpath}:\ - ${build.test.classes.dir} -# Space-separated list of JVM arguments used when running a class with a main method or a unit test -# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value): -runmain.jvmargs= -source.encoding=UTF-8 -source.root=src -src.dir=${source.root}/java -test.src.dir=test -war.content.additional= -war.ear.name=${war.name} -war.name=DSTroubleTicket.war -web.docbase.dir=web -webinf.dir=web/WEB-INF diff --git a/nbproject/project.xml b/nbproject/project.xml deleted file mode 100644 index d2e6759..0000000 --- a/nbproject/project.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - org.netbeans.modules.web.project - - - - - - - - DSTroubleTicket - 1.6.5 - - - ${libs.restlib.classpath} - WEB-INF/lib - - - ${file.reference.commons-beanutils-1.8.3.jar} - WEB-INF/lib - - - ${file.reference.commons-beanutils-bean-collections-1.8.3.jar} - WEB-INF/lib - - - ${file.reference.commons-beanutils-core-1.8.3.jar} - WEB-INF/lib - - - ${file.reference.commons-logging-1.1.3.jar} - WEB-INF/lib - - - - - - - - - - - - diff --git a/nbproject/rest-build.xml b/nbproject/rest-build.xml deleted file mode 100644 index eeca655..0000000 --- a/nbproject/rest-build.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ - -package org.netbeans.rest.application.config; -/** - * This class is generated by the NetBeans IDE, - * and registers all REST root resources created in the project. - * Please, DO NOT EDIT this class unless you really need and - * understand the results of changes. - * It is safe to change REST resources path value of - * ApplicationPath annotation. It can be done also via UI action - * called on RESTful Web Services node ( REST Resources Configuration dialog ). - * - */ -@javax.ws.rs.ApplicationPath("${rest.resources.path}") -public class ApplicationConfig extends javax.ws.rs.core.Application { -} - - diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..52da461 --- /dev/null +++ b/pom.xml @@ -0,0 +1,107 @@ + + 4.0.0 + + org.tmf.dsmapi + DSTROUBLETICKET + 1.0-SNAPSHOT + war + + DSTROUBLETICKET + + + ${project.build.directory}/endorsed + UTF-8 + + + + + + javax + javaee-web-api + 6.0 + provided + + + + commons-beanutils + commons-beanutils + 1.8.3 + + + + com.sun.jersey + jersey-server + 1.18 + provided + + + + com.sun.jersey + jersey-client + 1.18 + provided + + + + org.codehaus.jackson + jackson-jaxrs + 1.9.13 + provided + + + + + + + DStroubleTicket + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + ${endorsed.dir} + + + + + org.apache.maven.plugins + maven-war-plugin + 2.1.1 + + false + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.1 + + + validate + + copy + + + ${endorsed.dir} + true + + + javax + javaee-endorsed-api + 6.0 + jar + + + + + + + + + diff --git a/postman/troubleTicket.json.postman_collection b/postman/troubleTicket.json.postman_collection new file mode 100644 index 0000000..a4144ac --- /dev/null +++ b/postman/troubleTicket.json.postman_collection @@ -0,0 +1,392 @@ +{ + "id": "82b057ef-beff-3023-2784-a270a774cc3e", + "name": "troubleTicket", + "description": "", + "order": [], + "folders": [ + { + "id": "c42e5060-4b13-cd9c-410a-a41e93809142", + "name": "adminApi", + "description": "", + "order": [ + "5bcc9625-409d-305a-5a1e-84d99e59d3c3", + "7e097b74-1618-2984-5d2d-d4a00a9af5a3", + "4ab754d4-0529-6b30-eea0-e88269ca5c5e", + "2f1dfebe-4520-5827-4b0d-23814fce68f4", + "86b41377-86a6-1453-ddfe-94e801cdb332", + "c4723f28-3cee-29ca-ec87-1bcda5a02960" + ], + "collection_name": "troubleTicket", + "collection_id": "82b057ef-beff-3023-2784-a270a774cc3e" + }, + { + "id": "81228462-7b23-822c-a901-74ac184b21f3", + "name": "eventApi", + "description": "", + "order": [ + "7b8e3e57-e26a-418b-a1cc-f492566e85df", + "aa98f0eb-a59b-ffe7-35b3-452dc0067f8f", + "33b90baa-9fd6-ed56-f426-4e928765662d", + "57cd3c2a-8a65-840b-3bc1-4ff2795b4ec5" + ], + "collection_name": "troubleTicket", + "collection_id": "82b057ef-beff-3023-2784-a270a774cc3e" + }, + { + "id": "48d9d2b0-b8ff-0c5d-add2-1e04abfbcf79", + "name": "troubleTicketApi", + "description": "", + "order": [ + "9d7bde91-b7d9-1647-552a-f12054defd59", + "c5623ec5-a2ae-f76e-7009-376666e30075", + "bc05a105-9a51-1c96-99dd-9fb5e17bb922", + "c06ba100-36a6-ebca-6cc6-62c4e698a321", + "d2c04e5e-fe61-5f9a-3647-5c8fca51397f", + "3a88f590-0be1-2bb9-e369-55f9e1c12919", + "36cc84c8-d70f-cab4-f62a-246e9451b3d9", + "7d938bd8-07db-ddf9-2c89-1ccdd118ea1f" + ], + "collection_name": "troubleTicket", + "collection_id": "82b057ef-beff-3023-2784-a270a774cc3e" + } + ], + "timestamp": 1413895436131, + "synced": false, + "requests": [ + { + "id": "2f1dfebe-4520-5827-4b0d-23814fce68f4", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketDomain}}/admin/count", + "preRequestScript": "", + "pathVariables": {}, + "method": "GET", + "data": [], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415009419976, + "name": "count", + "description": "count products", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false + }, + { + "id": "33b90baa-9fd6-ed56-f426-4e928765662d", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketSubscriberDomain}}/history", + "preRequestScript": "", + "pathVariables": {}, + "method": "GET", + "data": [], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415009582268, + "name": "/history", + "description": "List all", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false + }, + { + "id": "36cc84c8-d70f-cab4-f62a-246e9451b3d9", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketDomain}}/troubleTicket/:id", + "preRequestScript": "", + "pathVariables": { + "id": "1" + }, + "method": "PUT", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "", + "time": 1415009533851, + "name": "/troubleTicket/{id}", + "description": "", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false, + "rawModeData": " {\n \"description\": \"trouble ticket MODIFIED\",\n \"severity\": \"Medium\",\n \"type\": \"Bills, charges or payment\",\n \"creationDate\": \"2013-08-20T18:18:40UTC\",\n \"status\": \"InProgress\",\n \"statusChangeReason\": \"Waiting for invoicing expert\",\n \"statusChangeDate\": \"2013-09-04T12:49:47UTC\",\n \"relatedObjects\": [\n {\n \"involvement\": \"involvment\",\n \"reference\": \"referenceobject\"\n },\n {\n \"involvement\": \"involvment\",\n \"reference\": \"referenceobject\"\n }\n ],\n \"notes\": [\n {\n \"date\": \"date\",\n \"author\": \"author\",\n \"text\": \"text MODIFIED\"\n },\n {\n \"date\": \"date\",\n \"author\": \"author\",\n \"text\": \"text\"\n }\n ],\n \"relatedParties\": [\n {\n \"role\": \"role\",\n \"reference\": \"reference party MODIFIED\"\n },\n {\n \"role\": \"role\",\n \"reference\": \"reference party\"\n }\n ]\n }" + }, + { + "id": "3a88f590-0be1-2bb9-e369-55f9e1c12919", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketDomain}}/troubleTicket", + "preRequestScript": "", + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "", + "time": 1415009519218, + "name": "/troubleTicket - ko", + "description": "create a product", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false, + "rawModeData": "" + }, + { + "id": "4ab754d4-0529-6b30-eea0-e88269ca5c5e", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketDomain}}/admin", + "preRequestScript": "", + "pathVariables": {}, + "method": "DELETE", + "dataMode": "binary", + "version": 2, + "tests": "", + "time": 1415009411470, + "name": "delete all", + "description": "delete all products", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false + }, + { + "id": "57cd3c2a-8a65-840b-3bc1-4ff2795b4ec5", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketSubscriberDomain}}/history", + "preRequestScript": "", + "pathVariables": {}, + "method": "DELETE", + "data": [], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415009592538, + "name": "/history", + "description": "delete all", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false + }, + { + "id": "5bcc9625-409d-305a-5a1e-84d99e59d3c3", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketDomain}}/admin", + "preRequestScript": "", + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "", + "time": 1415009391855, + "name": "create list", + "description": "create a list off products", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false, + "rawModeData": "[\n {\n \"description\": \"trouble ticket 1\",\n \"severity\": \"Medium\",\n \"type\": \"Bills, charges or payment\",\n \"creationDate\": \"2013-08-20T18:18:40UTC\",\n \"status\": \"Submitted\",\n \"statusChangeReason\": \"Creation\",\n \"statusChangeDate\": \"2013-09-04T12:49:47UTC\",\n \"relatedObjects\": [\n {\n \"involvement\": \"involvment\",\n \"reference\": \"referenceobject\"\n },\n {\n \"involvement\": \"involvment\",\n \"reference\": \"referenceobject\"\n }\n ],\n \"notes\": [\n {\n \"date\": \"date\",\n \"author\": \"author\",\n \"text\": \"text\"\n },\n {\n \"date\": \"date\",\n \"author\": \"author\",\n \"text\": \"text\"\n }\n ],\n \"relatedParties\": [\n {\n \"role\": \"role\",\n \"reference\": \"reference party\"\n },\n {\n \"role\": \"role\",\n \"reference\": \"reference party\"\n }\n ]\n },\n {\n \"description\": \"trouble ticket 2\",\n \"severity\": \"Medium\",\n \"type\": \"Bills, charges or payment\",\n \"creationDate\": \"2013-08-20T18:18:40UTC\",\n \"status\": \"Submitted\",\n \"statusChangeReason\": \"Creation\",\n \"statusChangeDate\": \"2013-09-04T12:49:47UTC\",\n \"relatedObjects\": [\n {\n \"involvement\": \"involvment\",\n \"reference\": \"referenceobject\"\n },\n {\n \"involvement\": \"involvment\",\n \"reference\": \"referenceobject\"\n }\n ],\n \"notes\": [\n {\n \"date\": \"date\",\n \"author\": \"author\",\n \"text\": \"text\"\n },\n {\n \"date\": \"date\",\n \"author\": \"author\",\n \"text\": \"text\"\n }\n ],\n \"relatedParties\": [\n {\n \"role\": \"role\",\n \"reference\": \"reference party\"\n },\n {\n \"role\": \"role\",\n \"reference\": \"reference party\"\n }\n ]\n },\n {\n \"description\": \"trouble ticket 3\",\n \"severity\": \"Medium\",\n \"type\": \"Bills, charges or payment\",\n \"creationDate\": \"2013-08-20T18:18:40UTC\",\n \"status\": \"Submitted\",\n \"statusChangeReason\": \"Creation\",\n \"statusChangeDate\": \"2013-09-04T12:49:47UTC\",\n \"relatedObjects\": [\n {\n \"involvement\": \"involvment\",\n \"reference\": \"referenceobject\"\n },\n {\n \"involvement\": \"involvment\",\n \"reference\": \"referenceobject\"\n }\n ],\n \"notes\": [\n {\n \"date\": \"date\",\n \"author\": \"author\",\n \"text\": \"text\"\n },\n {\n \"date\": \"date\",\n \"author\": \"author\",\n \"text\": \"text\"\n }\n ],\n \"relatedParties\": [\n {\n \"role\": \"role\",\n \"reference\": \"reference party\"\n },\n {\n \"role\": \"role\",\n \"reference\": \"reference party\"\n }\n ]\n }\n]\n" + }, + { + "id": "7b8e3e57-e26a-418b-a1cc-f492566e85df", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketSubscriberDomain}}/event", + "preRequestScript": "", + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "", + "time": 1415009560001, + "name": "/event", + "description": "", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false, + "rawModeData": "\n{\n \"reason\" : \"New TroubleTicket\",\n \"date\" : 1414576200588,\n \"eventType\" : \"TicketCreateNotification\",\n \"dateEvent\" : \"2014-10-29T10:50:00\",\n \"resource\" : {\n \"id\" : \"55\",\n \"description\" : \"trouble ticket\",\n \"severity\" : \"Medium\",\n \"type\" : \"Bills, charges or payment\",\n \"creationDate\" : \"2013-08-20T18:18:40UTC\",\n \"status\" : \"Submitted\",\n \"statusChangeReason\" : \"Creation\",\n \"statusChangeDate\" : \"2014-10-29T09:50:00UTC\",\n \"relatedObjects\" : [ {\n \"reference\" : \"referenceobject\",\n \"involvement\" : \"involvment\"\n }, {\n \"reference\" : \"referenceobject\",\n \"involvement\" : \"involvment\"\n } ],\n \"notes\" : [ {\n \"date\" : \"date\",\n \"author\" : \"author\",\n \"text\" : \"text\"\n }, {\n \"date\" : \"date\",\n \"author\" : \"author\",\n \"text\" : \"text\"\n } ],\n \"relatedParties\" : [ {\n \"role\" : \"role\",\n \"reference\" : \"reference party\"\n }, {\n \"role\" : \"role\",\n \"reference\" : \"reference party\"\n } ]\n }\n}" + }, + { + "id": "7d938bd8-07db-ddf9-2c89-1ccdd118ea1f", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://localhost:8080/DSTroubleTicket/troubleTicketApi/troubleTicket/:id", + "preRequestScript": "", + "pathVariables": { + "id": "1" + }, + "method": "PATCH", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "", + "time": 1413963896042, + "name": "/troubleTicket/{id}", + "description": "", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false, + "rawModeData": " {\n \"status\": \"InProgress_Held\",\n \"statusChangeReason\": \"Waiting for invoicing expert\",\n \"statusChangeDate\": \"2014-10-22T09:00:00UTC\",\n \"relatedObjects\": [\n {\n \"involvement\": \"involvment\",\n \"reference\": \"referenceobject PARTIAL UPDATE\"\n }\n ],\n \"notes\": [\n {\n \"date\": \"date\",\n \"author\": \"author\",\n \"text\": \"text PARTIAL UPDATE\"\n }\n ]\n }" + }, + { + "id": "7e097b74-1618-2984-5d2d-d4a00a9af5a3", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketDomain}}/admin/:id", + "preRequestScript": "", + "pathVariables": { + "id": "2" + }, + "method": "DELETE", + "dataMode": "binary", + "version": 2, + "tests": "", + "time": 1415009403828, + "name": "delete one", + "description": "delete a product", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false + }, + { + "id": "86b41377-86a6-1453-ddfe-94e801cdb332", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketDomain}}/admin/event", + "preRequestScript": "", + "pathVariables": {}, + "method": "DELETE", + "dataMode": "binary", + "version": 2, + "tests": "", + "time": 1415009432473, + "name": "delete all events", + "description": "delete all events", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false + }, + { + "id": "9d7bde91-b7d9-1647-552a-f12054defd59", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketDomain}}/hub", + "preRequestScript": "", + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "", + "time": 1415009456428, + "name": "/hub", + "description": "create a hub", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false, + "rawModeData": "{\n \"callback\":\"http://localhost:8080/DSTroubleTicket/subscriber/eventApi/event\",\n \"query\":\"fields=resource.id,resource.creationDate,resource.description&eventType=TicketCreateNotification,TicketStatusChangedNotification,TicketChangedNotification,TicketClearanceRequestNotification,InformationRequiredNotification\"\n}" + }, + { + "id": "aa98f0eb-a59b-ffe7-35b3-452dc0067f8f", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketSubscriberDomain}}/current", + "preRequestScript": "", + "pathVariables": {}, + "method": "GET", + "data": [], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415009570295, + "name": "/current", + "description": "get last event", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false + }, + { + "id": "bc05a105-9a51-1c96-99dd-9fb5e17bb922", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketDomain}}/troubleTicket", + "preRequestScript": "", + "pathVariables": {}, + "method": "GET", + "data": [], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415009478375, + "name": "/troubleTicket", + "description": "select all troubleTickets", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false + }, + { + "id": "c06ba100-36a6-ebca-6cc6-62c4e698a321", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketDomain}}/troubleTicket/:id", + "preRequestScript": "", + "pathVariables": { + "id": "1" + }, + "method": "GET", + "data": [], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415009492769, + "name": "/troubleTicket/{id}", + "description": "select one troubleTickets with id", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false + }, + { + "id": "c4723f28-3cee-29ca-ec87-1bcda5a02960", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketDomain}}/admin/hub", + "preRequestScript": "", + "pathVariables": {}, + "method": "DELETE", + "dataMode": "binary", + "version": 2, + "tests": "", + "time": 1415009445065, + "name": "delete all hubs", + "description": "delete all hubs", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false + }, + { + "id": "c5623ec5-a2ae-f76e-7009-376666e30075", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketDomain}}/hub", + "preRequestScript": "", + "pathVariables": {}, + "method": "GET", + "data": [], + "dataMode": "params", + "version": 2, + "tests": "", + "time": 1415009465105, + "name": "/hub", + "description": "create a hub", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false + }, + { + "id": "d2c04e5e-fe61-5f9a-3647-5c8fca51397f", + "headers": "Content-Type: application/json\nAccept: application/json\n", + "url": "http://{{RitmfDevHost}}/{{TroubleTicketDomain}}/troubleTicket", + "preRequestScript": "", + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": "", + "time": 1415009503555, + "name": "/troubleTicket", + "description": "create a troubleTicket", + "collectionId": "82b057ef-beff-3023-2784-a270a774cc3e", + "responses": [], + "synced": false, + "rawModeData": " {\n \"description\": \"trouble ticket\",\n \"severity\": \"Medium\",\n \"type\": \"Bills, charges or payment\",\n \"creationDate\": \"2013-08-20T18:18:40UTC\",\n \"status\": \"Submitted\",\n \"statusChangeReason\": \"Creation\",\n \"statusChangeDate\": \"2013-09-04T12:49:47UTC\",\n \"relatedObjects\": [\n {\n \"involvement\": \"involvment\",\n \"reference\": \"referenceobject\"\n },\n {\n \"involvement\": \"involvment\",\n \"reference\": \"referenceobject\"\n }\n ],\n \"notes\": [\n {\n \"date\": \"date\",\n \"author\": \"author\",\n \"text\": \"text\"\n },\n {\n \"date\": \"date\",\n \"author\": \"author\",\n \"text\": \"text\"\n }\n ],\n \"relatedParties\": [\n {\n \"role\": \"role\",\n \"reference\": \"reference party\"\n },\n {\n \"role\": \"role\",\n \"reference\": \"reference party\"\n }\n ]\n }" + } + ] +} \ No newline at end of file diff --git a/soapui/DSMAPITT-soapui-project.xml b/soapui/DSMAPITT-soapui-project.xml deleted file mode 100644 index 241b962..0000000 --- a/soapui/DSMAPITT-soapui-project.xml +++ /dev/null @@ -1,1145 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - - - - - file:application.wadl.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - http://wadl.dev.java.net/2009/02 - - - file:\C:\Users\MAIG7313\Documents\home\src\DSMAPITT\xsd0.xsd - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - http://www.w3.org/2001/XMLSchema - - - - - http://localhost:8081 - - - - - - - - id - - TEMPLATE - xs:string - - - - - - - - - - - application/json - - troubleTicket - - - - application/json - - troubleTicket - - - - <xml-fragment/> - http://localhost:8081 - { - "id": "1", - "severity": "High", - "relatedParties": null -}adminadmin2014!Global HTTP Settings - - - <xml-fragment/> - http://localhost:8081 - { - "id": "1", - "severity": "Low", - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] -}adminadmin2014!Global HTTP Settings - - <xml-fragment/> - http://localhost:8081 - { - "id": "9999", - "severity": "High", - "relatedParties": null -}adminadmin2014!Global HTTP Settings - - <xml-fragment/> - http://localhost:8081 - { - "id": "2", - "severity": "High", - "relatedParties": null -}adminadmin2014!Global HTTP Settings - - <xml-fragment/> - http://localhost:8081 - { - "id": "1", - "status": "Acknowledged", - "statusChangeReason": "Acknowledged" -}adminadmin2014!Global HTTP Settings - - <xml-fragment/> - http://localhost:8081 - { - "id": "1", - "status": "InProgress_Pending", - "statusChangeReason": "InProgress_Pending" -}adminadmin2014!Global HTTP Settings - - <xml-fragment/> - http://localhost:8081 - { - "id": "1", - "status": "Closed", - "statusChangeReason": "Closed" -}adminadmin2014!Global HTTP Settings - - <xml-fragment/> - http://localhost:8081 - { - "id": "1", - "status": "Cancelled", - "statusChangeReason": "Cancelled" -}adminadmin2014!Global HTTP Settings - - <xml-fragment/> - http://localhost:8081 - { - "id": "1", - "status": "InProgress_Held", - "statusChangeReason": "InProgress_Held" -}adminadmin2014!Global HTTP Settings - - <xml-fragment/> - http://localhost:8081 - { - "id": "1", - "status": "Resolved", - "statusChangeReason": "Resolved" -}adminadmin2014!Global HTTP Settings - - - - - - application/json - - troubleTicket - - - - application/json - - troubleTicket - - - - <xml-fragment/> - http://localhost:8081 - { - "id": "1", - "severity": "Low", - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T12:39:48UTC", - "status": "Acknowledged", - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] -}adminadmin2014!Global HTTP Settings - - - <xml-fragment/> - http://localhost:8081 - { - "id": "1", - "severity": "Low", - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T12:39:48UTC", - "status": "Acknowledged", - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] -}adminadmin2014!Global HTTP Settings - - <xml-fragment/> - http://localhost:8081 - { - "id": "1", - "severity": "Low", - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T12:39:48UTC", - "status": "Acknowledged", - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] -}adminadmin2014!Global HTTP Settings - - <xml-fragment/> - http://localhost:8081 - { - "id": "1", - "severity": "Low", - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T12:39:48UTC", - "status": "Acknowledged", - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] -}adminadmin2014!Global HTTP Settings - - :fieldsQUERYxs:stringstatusQUERYxs:stringseverityQUERYxs:string<xml-fragment/>http://localhost:8081adminadmin2014!Global HTTP Settings<xml-fragment/>http://localhost:8081adminadmin2014!Global HTTP Settings<xml-fragment/>http://localhost:8081adminadmin2014!Global HTTP Settings - - -<xml-fragment/>http://localhost:8081adminadmin2014!Global HTTP Settings - - - - - - - - - - - - application/json - - troubleTicket - - - - - <xml-fragment/> - - http://localhost:8081 - - - adminadmin2014!Global HTTP Settings - - - - - - - - - - - - - - - application/json - - troubleTicket - - - - - <xml-fragment/> - - http://localhost:8081 - { - "id": null, - "correlationId": null, - "description": "Description", - "severity": "Medium", - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T18:18:40UTC", - "targetResolutionDate": null, - "status": "Submitted", - "subStatus": null, - "statusChangeReason": "Creation", - "resolutionDate": null, - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] - } - - adminadmin2014!Global HTTP Settings - - - - - - - - <xml-fragment/> - - http://localhost:8081 - { - "id": null, - "correlationId": null, - "description": "Description", - "severity": null, - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T18:18:40UTC", - "targetResolutionDate": null, - "status": "Acknowledged", - "subStatus": null, - "statusChangeReason": null, - "statusChangeDate": null, - "resolutionDate": null, - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] - } - - adminadmin2014!Global HTTP Settings - - - - - - - - <xml-fragment/> - - http://localhost:8081 - { - "id": null, - "correlationId": null, - "description": null, - "severity": "Medium", - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T18:18:40UTC", - "targetResolutionDate": null, - "status": "Acknowledged", - "subStatus": null, - "statusChangeReason": null, - "statusChangeDate": null, - "resolutionDate": null, - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] - } - - adminadmin2014!Global HTTP Settings - - - - - - - - <xml-fragment/> - - http://localhost:8081 - { - "id": null, - "correlationId": null, - "description": "Description", - "severity": "Medium", - "type": null, - "creationDate": "2013-08-20T18:18:40UTC", - "targetResolutionDate": null, - "status": "Acknowledged", - "subStatus": null, - "statusChangeReason": null, - "statusChangeDate": null, - "resolutionDate": null, - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] - } - - adminadmin2014!Global HTTP Settings - - - - - - - - <xml-fragment/> - - http://localhost:8081 - { - "id": "7667474445", - "correlationId": null, - "description": "Description", - "severity": "Medium", - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T18:18:40UTC", - "targetResolutionDate": null, - "status": "Acknowledged", - "subStatus": null, - "statusChangeReason": null, - "statusChangeDate": null, - "resolutionDate": null, - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] - } - - adminadmin2014!Global HTTP Settings - - - - - - - - <xml-fragment/> - - http://localhost:8081 - { - "id": "7667474445", - "badAttribute": badAttribute, - "correlationId": null, - "description": null, - "severity": "Medium", - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T18:18:40UTC", - "targetResolutionDate": null, - "status": "Acknowledged", - "subStatus": null, - "statusChangeReason": null, - "statusChangeDate": null, - "resolutionDate": null, - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] - } - - adminadmin2014!Global HTTP Settings - - - - - - - statussubmittedQUERYsubmittedseveritymediumQUERYmedium<xml-fragment/>http://localhost:8081adminadmin2014!Global HTTP Settings - - -<xml-fragment/>http://localhost:8081adminadmin2014!Global HTTP Settings<xml-fragment/>http://localhost:8081adminadmin2014!Global HTTP Settings - <xml-fragment/>http://localhost:8081adminadmin2014!Global HTTP Settings<xml-fragment/>http://localhost:8081adminadmin2014!Global HTTP SettingsididTEMPLATEid<xml-fragment/>http://localhost:8081adminadmin2014!Global HTTP Settings<xml-fragment/>http://localhost:8081[ - { - "description": "trouble ticket 1", - "severity": "Medium", - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T18:18:40UTC", - "status": "Submitted", - "statusChangeReason": "Creation", - "statusChangeDate": "2013-09-04T12:49:47UTC", - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] - }, - { - "description": "trouble ticket 2", - "severity": "Medium", - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T18:18:40UTC", - "status": "Submitted", - "statusChangeReason": "Creation", - "statusChangeDate": "2013-09-04T12:49:47UTC", - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] - }, -{ - "description": "trouble ticket 3", - "severity": "Medium", - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T18:18:40UTC", - "status": "Submitted", - "statusChangeReason": "Creation", - "statusChangeDate": "2013-09-04T12:49:47UTC", - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] - } -] -adminadmin2014!Global HTTP Settings<xml-fragment/>http://localhost:8081adminadmin2014!Global HTTP Settings - SEQUENTIAL - - <xml-fragment/> - - http://localhost:8081 - { - "id": null, - "correlationId": null, - "description": "Description", - "severity": "Medium", - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T18:18:40UTC", - "targetResolutionDate": null, - "status": "Submitted", - "subStatus": null, - "statusChangeReason": "Creation", - "resolutionDate": null, - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] - } - - adminadmin2014!Global HTTP Settings - - - - - - - <xml-fragment/> - - http://localhost:8081 - { - "id": null, - "correlationId": null, - "description": "Description", - "severity": "Medium", - "type": "Bills, charges or payment", - "creationDate": "2013-08-20T18:18:40UTC", - "targetResolutionDate": null, - "status": "Submitted", - "subStatus": null, - "statusChangeReason": "Creation", - "resolutionDate": null, - "relatedObjects": [ - { - "involvement": "involvment", - "reference": "referenceobject" - }, - { - "involvement": "involvment", - "reference": "referenceobject" - } - ], - "notes": [ - { - "date": "date", - "author": "author", - "text": "text" - }, - { - "date": "date", - "author": "author", - "text": "text" - } - ], - "relatedParties": [ - { - "role": "role", - "reference": "reference party" - }, - { - "role": "role", - "reference": "reference party" - } - ] - } - - adminadmin2014!Global HTTP Settings - - - - - <xml-fragment/>http://localhost:8081adminadmin2014!Global HTTP Settings - - -falsehttp://localhost:8080http://localhost:8081 - - \ No newline at end of file diff --git a/soapui/application.wadl.xml b/soapui/application.wadl.xml deleted file mode 100644 index 269d579..0000000 --- a/soapui/application.wadl.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/soapui/xsd0.xsd b/soapui/xsd0.xsd deleted file mode 100644 index bc72c96..0000000 --- a/soapui/xsd0.xsd +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/java/org/netbeans/rest/application/config/ApplicationConfig.java b/src/java/org/netbeans/rest/application/config/ApplicationConfig.java deleted file mode 100644 index afc1b35..0000000 --- a/src/java/org/netbeans/rest/application/config/ApplicationConfig.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package org.netbeans.rest.application.config; - -import java.util.Set; -import javax.ws.rs.core.Application; - -/** - * - * @author pierregauthier - */ -@javax.ws.rs.ApplicationPath("api") -public class ApplicationConfig extends Application { - - @Override - public Set> getClasses() { - return getRestResourceClasses(); - } - - /** - * Do not modify this method. It is automatically generated by NetBeans REST support. - */ - private Set> getRestResourceClasses() { - Set> resources = new java.util.HashSet>(); - resources.add(tmf.org.dsmapi.hub.service.HubFacadeREST.class); - resources.add(tmf.org.dsmapi.tt.service.AdminFacadeREST.class); - resources.add(tmf.org.dsmapi.tt.service.TroubleTicketFacadeREST.class); - resources.add(tmf.org.dsmapi.tt.service.mapper.BadUsageExceptionMapper.class); - resources.add(tmf.org.dsmapi.tt.service.mapper.JacksonConfigurator.class); - resources.add(tmf.org.dsmapi.tt.service.mapper.JsonMappingExceptionMapper.class); - resources.add(tmf.org.dsmapi.tt.service.mapper.TroubleTicketReader.class); - resources.add(tmf.org.dsmapi.tt.service.mapper.UnknowResourceExceptionMapper.class); - // following code can be used to customize Jersey 1.x JSON provider: - try { - Class jacksonProvider = Class.forName("org.codehaus.jackson.jaxrs.JacksonJsonProvider"); - resources.add(jacksonProvider); - } catch (ClassNotFoundException ex) { - java.util.logging.Logger.getLogger(getClass().getName()).log(java.util.logging.Level.SEVERE, null, ex); - } - return resources; - } - -} diff --git a/src/java/tmf/org/dsmapi/commons/utils/Format.java b/src/java/tmf/org/dsmapi/commons/utils/Format.java deleted file mode 100644 index 84c4619..0000000 --- a/src/java/tmf/org/dsmapi/commons/utils/Format.java +++ /dev/null @@ -1,35 +0,0 @@ -package tmf.org.dsmapi.commons.utils; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; - -public class Format { - - public static String toString(Date date) { - - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"); - - TimeZone tz = TimeZone.getTimeZone("UTC"); - - df.setTimeZone(tz); - - String output = df.format(date); - - return output; - /* - int inset0 = 9; - int inset1 = 6; - - String s0 = output.substring( 0, output.length() - inset0 ); - String s1 = output.substring( output.length() - inset1, output.length() ); - - String result = s0 + s1; - - result = result.replaceAll( "UTC", "+00:00" ); - - return result; */ - - } - -} diff --git a/src/java/tmf/org/dsmapi/hub/HubEvent.java b/src/java/tmf/org/dsmapi/hub/HubEvent.java deleted file mode 100644 index f2fc33d..0000000 --- a/src/java/tmf/org/dsmapi/hub/HubEvent.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.hub; - -import java.io.Serializable; -import javax.xml.bind.annotation.XmlRootElement; -import tmf.org.dsmapi.tt.TroubleTicket; - -/** - * - * @author pierregauthier - */ -@XmlRootElement -public class HubEvent implements Serializable { - - private TroubleTicket event; //checl for object - private TroubleTicketEventTypeEnum eventType; - - public TroubleTicket getEvent() { - return event; - } - - public void setEvent(TroubleTicket event) { - this.event = event; - } - - public TroubleTicketEventTypeEnum getEventType() { - return eventType; - } - - public void setEventType(TroubleTicketEventTypeEnum eventType) { - this.eventType = eventType; - } - - @Override - public String toString() { - return "HubEvent{" + "event=" + event + ", eventType=" + eventType + '}'; - } -} diff --git a/src/java/tmf/org/dsmapi/hub/service/AbstractFacade.java b/src/java/tmf/org/dsmapi/hub/service/AbstractFacade.java deleted file mode 100644 index fe63897..0000000 --- a/src/java/tmf/org/dsmapi/hub/service/AbstractFacade.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.hub.service; - -import java.util.List; -import javax.persistence.EntityManager; - -/** - * - * @author pierregauthier - */ -public abstract class AbstractFacade { - private Class entityClass; - - public AbstractFacade(Class entityClass) { - this.entityClass = entityClass; - } - - protected abstract EntityManager getEntityManager(); - - public void create(T entity) { - getEntityManager().persist(entity); - } - - public void edit(T entity) { - getEntityManager().merge(entity); - } - - public void remove(T entity) { - getEntityManager().remove(getEntityManager().merge(entity)); - } - - public T find(Object id) { - return getEntityManager().find(entityClass, id); - } - - public List findAll() { - javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery(); - cq.select(cq.from(entityClass)); - return getEntityManager().createQuery(cq).getResultList(); - } - - public List findRange(int[] range) { - javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery(); - cq.select(cq.from(entityClass)); - javax.persistence.Query q = getEntityManager().createQuery(cq); - q.setMaxResults(range[1] - range[0]); - q.setFirstResult(range[0]); - return q.getResultList(); - } - - public int count() { - javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery(); - javax.persistence.criteria.Root rt = cq.from(entityClass); - cq.select(getEntityManager().getCriteriaBuilder().count(rt)); - javax.persistence.Query q = getEntityManager().createQuery(cq); - return ((Long) q.getSingleResult()).intValue(); - } - -} diff --git a/src/java/tmf/org/dsmapi/hub/service/HubFacadeREST.java b/src/java/tmf/org/dsmapi/hub/service/HubFacadeREST.java deleted file mode 100644 index 62f4ca5..0000000 --- a/src/java/tmf/org/dsmapi/hub/service/HubFacadeREST.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.hub.service; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import javax.ejb.Stateless; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import tmf.org.dsmapi.commons.utils.Format; -import tmf.org.dsmapi.hub.Hub; -import tmf.org.dsmapi.hub.HubEvent; -import tmf.org.dsmapi.hub.TroubleTicketEventTypeEnum; -import tmf.org.dsmapi.tt.Note; -import tmf.org.dsmapi.tt.RelatedObject; -import tmf.org.dsmapi.tt.RelatedParty; -import tmf.org.dsmapi.tt.Severity; -import tmf.org.dsmapi.tt.Status; -import tmf.org.dsmapi.tt.TroubleTicket; - -/** - * - * @author pierregauthier - */ -@Stateless -@Path("tmf.org.dsmapi.hub.hub") -public class HubFacadeREST extends AbstractFacade { - @PersistenceContext(unitName = "DSTroubleTicketPU") - private EntityManager em; - - public HubFacadeREST() { - super(Hub.class); - } - - @POST - @Override - @Consumes({"application/json"}) - public void create(Hub entity) { - super.create(entity); - } - - @PUT - @Override - @Consumes({"application/xml", "application/json"}) - public void edit(Hub entity) { - super.edit(entity); - } - - @DELETE - @Path("{id}") - public void remove(@PathParam("id") String id) { - super.remove(super.find(id)); - } - - @GET - @Path("{id}") - @Produces({ "application/json"}) - public Hub find(@PathParam("id") String id) { - return super.find(id); - } - - @GET - @Override - @Produces({"application/json"}) - public List findAll() { - return super.findAll(); - } - - @GET - @Path("{from}/{to}") - @Produces({"application/json"}) - public List findRange(@PathParam("from") Integer from, @PathParam("to") Integer to) { - return super.findRange(new int[]{from, to}); - } - - @GET - @Path("count") - @Produces("text/plain") - public String countREST() { - return String.valueOf(super.count()); - } - - @Override - protected EntityManager getEntityManager() { - return em; - } - - @POST - @Path("listener") - @Consumes({"application/json"}) - @Produces({"application/json"}) - public void publishEvent(HubEvent event) { - - System.out.println("HubEvent =" + event ); - System.out.println("Event = " + event.getEvent().toString()); - System.out.println("Event type = " + event.getEventType().getText()); - - } - - @GET - @Path("proto") - @Produces({ "application/json"}) - public Hub hubProto() { - Hub hub = new Hub(); - hub.setCallback("callback"); - hub.setQuery("queryString"); - hub.setId("id"); - return hub; - } - - @GET - @Path("eventProto") - @Produces({ "application/json"}) - public HubEvent eventProto() { - HubEvent event = new HubEvent(); - event.setEvent(proto()); - event.setEventType(TroubleTicketEventTypeEnum.TicketCreateNotification); - System.out.println("Event = " + event.getEvent().toString()); - System.out.println("Event type = " + event.getEventType().getText()); - return event; - } - - public TroubleTicket proto() { - TroubleTicket tt = new TroubleTicket(); - tt.setId("id"); - Date dt = new Date(); - String dts = Format.toString(dt); - tt.setDescription("Some Description"); - - - tt.setCreationDate(dts); - tt.setStatus(Status.Acknowledged); - tt.setSeverity(Severity.Medium); - tt.setType("Bills, charges or payment"); - tt.setResolutionDate(dts); - tt.setTargetResolutionDate(dts); - - RelatedObject ro = new RelatedObject(); - ro.setInvolvement("involvment"); - ro.setReference("referenceobject"); - - List relatedObjects = new ArrayList (); - relatedObjects.add(ro); - relatedObjects.add(ro); - tt.setRelatedObjects(relatedObjects); - - RelatedParty rp = new RelatedParty(); - rp.setRole("role"); - rp.setReference("reference party"); - - List relatedParties = new ArrayList (); - relatedParties.add(rp); - relatedParties.add(rp); - tt.setRelatedParties(relatedParties); - - Note note = new Note(); - note.setAuthor("author"); - note.setDate(dts); - note.setText("text"); - List notes = new ArrayList (); - notes.add(note); - notes.add(note); - tt.setNotes(notes); - return tt; - - } - - public static Date parse(String input) throws java.text.ParseException { - - //NOTE: SimpleDateFormat uses GMT[-+]hh:mm for the TZ which breaks - //things a bit. Before we go on we have to repair this. - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"); - - //this is zero time so we need to add that TZ indicator for - if (input.endsWith("Z")) { - input = input.substring(0, input.length() - 1) + "GMT-00:00"; - } else { - int inset = 6; - - String s0 = input.substring(0, input.length() - inset); - String s1 = input.substring(input.length() - inset, input.length()); - - input = s0 + "GMT" + s1; - } - - return df.parse(input); - - } - -} diff --git a/src/java/tmf/org/dsmapi/hub/service/PostEventClient.java b/src/java/tmf/org/dsmapi/hub/service/PostEventClient.java deleted file mode 100644 index a815378..0000000 --- a/src/java/tmf/org/dsmapi/hub/service/PostEventClient.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.hub.service; - -import com.sun.jersey.api.client.Client; -import com.sun.jersey.api.client.UniformInterfaceException; -import com.sun.jersey.api.client.WebResource; - -/** - * Jersey REST client generated for REST resource:HubFacadeREST - * [tmf.org.dsmapi.hub.hub]
- * USAGE: - *

- *        PostEventClient client = new PostEventClient();
- *        Object response = client.XXX(...);
- *        // do whatever with response
- *        client.close();
- * 
- * - * @author pierregauthier - */ -public class PostEventClient { - - private WebResource webResource; - private Client client; - - public PostEventClient(String callbackURL) { - com.sun.jersey.api.client.config.ClientConfig config = new com.sun.jersey.api.client.config.DefaultClientConfig(); - client = Client.create(config); - webResource = client.resource(callbackURL).path(""); - } - - public void publishEvent(Object requestEntity) throws UniformInterfaceException { - webResource.path("").type(javax.ws.rs.core.MediaType.APPLICATION_JSON).post(requestEntity); - } - - public void close() { - client.destroy(); - } -} diff --git a/src/java/tmf/org/dsmapi/hub/service/Publisher.java b/src/java/tmf/org/dsmapi/hub/service/Publisher.java deleted file mode 100644 index e3a5694..0000000 --- a/src/java/tmf/org/dsmapi/hub/service/Publisher.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.hub.service; - -import java.util.Iterator; -import java.util.List; -import javax.ejb.Asynchronous; -import javax.ejb.EJB; -import javax.ejb.Stateless; -import tmf.org.dsmapi.hub.Hub; -import tmf.org.dsmapi.hub.HubEvent; -import tmf.org.dsmapi.hub.TroubleTicketEventTypeEnum; -import tmf.org.dsmapi.tt.TroubleTicket; - -/** - * - * @author pierregauthier should be async or called with MDB - */ -@Stateless -//@Asynchronous bug in 7.3 -@Asynchronous -public class Publisher implements PublisherLocal { - - @EJB - HubFacade hubFacade; - @EJB - RESTEventPublisherLocal restEventPublisher; - - // Add business logic below. (Right-click in editor and choose - // "Insert Code > Add Business Method") - //Access Hubs using callbacks and send to http publisher - //(pool should be configured around the RESTEventPublisher bean) - //Loop into array of Hubs - //Call RestEventPublisher - Need to implement resend policy plus eviction - //Filtering is done in RestEventPublisher based on query expression - @Override - public void publish(Object event) { - System.out.println("Sending Event"); - - List hubList = hubFacade.findAll(); - Iterator it = hubList.iterator(); - while (it.hasNext()) { - Hub hub = it.next(); - restEventPublisher.publish(hub, event); - - - //Thread.currentThread().sleep(1000); - } - System.out.println("Sending Event After"); - } - - @Override - public void publishTicketCreateNotification(TroubleTicket tt) { - HubEvent event = new HubEvent(); - event.setEvent(tt); - event.setEventType(TroubleTicketEventTypeEnum.TicketCreateNotification); - publish(event); - - } - - @Override - public void publishTicketStatusChangedNotification(TroubleTicket tt) { - - HubEvent event = new HubEvent(); - event.setEvent(tt); - event.setEventType(TroubleTicketEventTypeEnum.TicketStatusChangedNotification); - publish(event); - - } - - @Override - public void publishTicketChangedNotification(TroubleTicket tt) { - - HubEvent event = new HubEvent(); - event.setEvent(tt); - event.setEventType(TroubleTicketEventTypeEnum.TicketChangedNotification); - publish(event); - - } - - @Override - public void publishTicketClearanceRequestNotification(TroubleTicket tt) { - - HubEvent event = new HubEvent(); - event.setEvent(tt); - event.setEventType(TroubleTicketEventTypeEnum.TicketClearanceRequestNotification); - publish(event); - - } - - @Override - public void publishInformationRequiredNotification(TroubleTicket tt) { - - HubEvent event = new HubEvent(); - event.setEvent(tt); - event.setEventType(TroubleTicketEventTypeEnum.InformationRequiredNotification); - publish(event); - - } -} diff --git a/src/java/tmf/org/dsmapi/hub/service/PublisherLocal.java b/src/java/tmf/org/dsmapi/hub/service/PublisherLocal.java deleted file mode 100644 index bf0966e..0000000 --- a/src/java/tmf/org/dsmapi/hub/service/PublisherLocal.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.hub.service; - -import javax.ejb.Local; -import tmf.org.dsmapi.tt.TroubleTicket; - -/** - * - * @author pierregauthier - */ -@Local -public interface PublisherLocal { - - void publish(Object event); - - public void publishTicketCreateNotification(TroubleTicket tt); - - public void publishTicketStatusChangedNotification(TroubleTicket tt); - - public void publishTicketChangedNotification(TroubleTicket tt); - - public void publishTicketClearanceRequestNotification(TroubleTicket tt); - - public void publishInformationRequiredNotification(TroubleTicket tt); - - -} diff --git a/src/java/tmf/org/dsmapi/hub/service/RESTEventPublisher.java b/src/java/tmf/org/dsmapi/hub/service/RESTEventPublisher.java deleted file mode 100644 index 88e3bd1..0000000 --- a/src/java/tmf/org/dsmapi/hub/service/RESTEventPublisher.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.hub.service; - -import java.util.Iterator; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.ejb.Asynchronous; -import javax.ejb.Stateless; -import tmf.org.dsmapi.hub.Hub; - -/** - * - * @author pierregauthier - */ -@Stateless -public class RESTEventPublisher implements RESTEventPublisherLocal { - - // Add business logic below. (Right-click in editor and choose - // "Insert Code > Add Business Method") - - @Override - @Asynchronous - public void publish( Hub hub, Object event) { - - PostEventClient client = new PostEventClient(hub.getCallback()); - - client.publishEvent(event); - - System.out.println("Sending Event"); - } - -} diff --git a/src/java/tmf/org/dsmapi/hub/service/RESTEventPublisherLocal.java b/src/java/tmf/org/dsmapi/hub/service/RESTEventPublisherLocal.java deleted file mode 100644 index c4206f0..0000000 --- a/src/java/tmf/org/dsmapi/hub/service/RESTEventPublisherLocal.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.hub.service; - -import javax.ejb.Local; -import tmf.org.dsmapi.hub.Hub; - -/** - * - * @author pierregauthier - */ -@Local -public interface RESTEventPublisherLocal { - - public void publish(Hub hub, Object event); - -} diff --git a/src/java/tmf/org/dsmapi/tt/IncidentType.java b/src/java/tmf/org/dsmapi/tt/IncidentType.java deleted file mode 100644 index 466301c..0000000 --- a/src/java/tmf/org/dsmapi/tt/IncidentType.java +++ /dev/null @@ -1,13 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.tt; - -/** - * none that i know - * @author pierregauthier ATMFORUM - */ -public enum IncidentType { - Submitted, Acknowledged, InProgress, Resolved, Closed, Rejected; //; is optional -} diff --git a/src/java/tmf/org/dsmapi/tt/client/TroubleTicketClient.java b/src/java/tmf/org/dsmapi/tt/client/TroubleTicketClient.java deleted file mode 100644 index a9144c0..0000000 --- a/src/java/tmf/org/dsmapi/tt/client/TroubleTicketClient.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.tt.client; - -import com.sun.jersey.api.client.Client; -import com.sun.jersey.api.client.UniformInterfaceException; -import com.sun.jersey.api.client.WebResource; - -/** - * Jersey REST client generated for REST resource:TroubleTicketFacadeREST - * [tmf.org.dsmapi.tt.troubleticket]
- * USAGE: - *
- *        TroubleTicketClient client = new TroubleTicketClient();
- *        Object response = client.XXX(...);
- *        // do whatever with response
- *        client.close();
- * 
- * - * @author pierregauthier - */ -public class TroubleTicketClient { - private WebResource webResource; - private Client client; - private static final String BASE_URI = "http://localhost:8080/DSTroubleTicket/webresources"; - - public TroubleTicketClient() { - com.sun.jersey.api.client.config.ClientConfig config = new com.sun.jersey.api.client.config.DefaultClientConfig(); - client = Client.create(config); - webResource = client.resource(BASE_URI).path("tmf.org.dsmapi.tt.troubleticket"); - } - - public void remove(String id) throws UniformInterfaceException { - webResource.path(java.text.MessageFormat.format("{0}", new Object[]{id})).delete(); - - } - - public String countREST() throws UniformInterfaceException { - WebResource resource = webResource; - resource = resource.path("count"); - return resource.accept(javax.ws.rs.core.MediaType.TEXT_PLAIN).get(String.class); - } - - public T findAll(Class responseType) throws UniformInterfaceException { - WebResource resource = webResource; - return resource.accept(javax.ws.rs.core.MediaType.APPLICATION_JSON).get(responseType); - } - - public T edit(Class responseType, Object requestEntity) throws UniformInterfaceException { - return webResource.type(javax.ws.rs.core.MediaType.APPLICATION_JSON).put(responseType, requestEntity); - } - - public void create(Object requestEntity) throws UniformInterfaceException { - webResource.type(javax.ws.rs.core.MediaType.APPLICATION_JSON).post(requestEntity); - } - - public T proto(Class responseType) throws UniformInterfaceException { - WebResource resource = webResource; - resource = resource.path("proto"); - return resource.accept(javax.ws.rs.core.MediaType.APPLICATION_JSON).get(responseType); - } - - public T findRange(Class responseType, String from, String to) throws UniformInterfaceException { - WebResource resource = webResource; - resource = resource.path(java.text.MessageFormat.format("{0}/{1}", new Object[]{from, to})); - return resource.accept(javax.ws.rs.core.MediaType.APPLICATION_JSON).get(responseType); - } - - public T find(Class responseType, String id) throws UniformInterfaceException { - WebResource resource = webResource; - resource = resource.path(java.text.MessageFormat.format("{0}", new Object[]{id})); - return resource.accept(javax.ws.rs.core.MediaType.APPLICATION_JSON).get(responseType); - } - - public void close() { - client.destroy(); - } - -} diff --git a/src/java/tmf/org/dsmapi/tt/service/AdminFacadeREST.java b/src/java/tmf/org/dsmapi/tt/service/AdminFacadeREST.java deleted file mode 100644 index 6b98b31..0000000 --- a/src/java/tmf/org/dsmapi/tt/service/AdminFacadeREST.java +++ /dev/null @@ -1,153 +0,0 @@ -package tmf.org.dsmapi.tt.service; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import javax.ejb.EJB; -import javax.ejb.Stateless; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Response; -import tmf.org.dsmapi.commons.exceptions.BadUsageException; -import tmf.org.dsmapi.commons.exceptions.UnknownResourceException; -import tmf.org.dsmapi.commons.utils.Format; -import tmf.org.dsmapi.tt.service.TroubleTicketFacade; -import tmf.org.dsmapi.tt.Note; -import tmf.org.dsmapi.tt.RelatedObject; -import tmf.org.dsmapi.tt.RelatedParty; -import tmf.org.dsmapi.tt.Severity; -import tmf.org.dsmapi.tt.Status; -import tmf.org.dsmapi.tt.TroubleTicket; - -@Stateless -@Path("admin") -public class AdminFacadeREST { - - @EJB - TroubleTicketFacade manager; - - @POST - @Path("troubleTicket") - @Consumes({"application/json"}) - @Produces({"application/json"}) - public Response post(List entities) { - - if (entities==null) return Response.status(Response.Status.BAD_REQUEST.getStatusCode()).build(); - - int previousRows = manager.count(); - int affectedRows; - - // Try to persist entities - try { - affectedRows = manager.create(entities); - } catch (BadUsageException e) { - return Response.status(Response.Status.BAD_REQUEST.getStatusCode()).build(); - } - - Report stat = new Report(manager.count()); - stat.setAffectedRows(affectedRows); - stat.setPreviousRows(previousRows); - - // 201 OK - return Response.created(null). - entity(stat). - build(); - } - - @DELETE - @Path("troubleTicket") - public Report deleteAll() { - - int affectedRows = manager.removeAll(); - - Report stat = new Report(manager.count()); - stat.setAffectedRows(affectedRows); - stat.setPreviousRows(affectedRows); - - return stat; - } - - @DELETE - @Path("troubleTicket/{id}") - public Report delete(@PathParam("id") String id) throws UnknownResourceException { - - int previousRows = manager.count(); - - manager.remove(id); - int affectedRows = 1; - - Report stat = new Report(manager.count()); - stat.setAffectedRows(affectedRows); - stat.setPreviousRows(previousRows); - - return stat; - } - - @GET - @Path("troubleTicket/count") - @Produces({"application/json"}) - public Report count() { - return new Report(manager.count()); - } - - @DELETE - @Path("troubleTicket/cache") - public void clearCache() { - manager.invalidCache(); - } - - - - @GET - @Path("troubleTicket/mock") - @Produces({"application/json"}) - public TroubleTicket proto() { - TroubleTicket tt = new TroubleTicket(); - tt.setId("id"); - Date dt = new Date(); - String dts = Format.toString(dt); - tt.setDescription("Some Description"); - - - tt.setCreationDate(dts); - tt.setStatus(Status.Acknowledged); - tt.setSeverity(Severity.Medium); - tt.setType("Bills, charges or payment"); - tt.setResolutionDate(dts); - tt.setTargetResolutionDate(dts); - - RelatedObject ro = new RelatedObject(); - ro.setInvolvement("involvment"); - ro.setReference("referenceobject"); - - List relatedObjects = new ArrayList (); - relatedObjects.add(ro); - relatedObjects.add(ro); - tt.setRelatedObjects(relatedObjects); - - RelatedParty rp = new RelatedParty(); - rp.setRole("role"); - rp.setReference("reference party"); - - List relatedParties = new ArrayList (); - relatedParties.add(rp); - relatedParties.add(rp); - tt.setRelatedParties(relatedParties); - - Note note = new Note(); - note.setAuthor("author"); - note.setDate(dts); - note.setText("text"); - List notes = new ArrayList (); - notes.add(note); - notes.add(note); - tt.setNotes(notes); - return tt; - - } -} diff --git a/src/java/tmf/org/dsmapi/tt/service/TroubleTicketFacade.java b/src/java/tmf/org/dsmapi/tt/service/TroubleTicketFacade.java deleted file mode 100644 index 0c6281f..0000000 --- a/src/java/tmf/org/dsmapi/tt/service/TroubleTicketFacade.java +++ /dev/null @@ -1,197 +0,0 @@ -package tmf.org.dsmapi.tt.service; - -import tmf.org.dsmapi.tt.TroubleTicketField; -import java.util.Date; -import java.util.List; -import tmf.org.dsmapi.tt.TroubleTicket; -import java.util.Set; -import javax.annotation.PostConstruct; -import javax.ejb.Stateless; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.Query; -import javax.persistence.criteria.CriteriaBuilder; -import javax.ws.rs.core.MultivaluedMap; -import tmf.org.dsmapi.commons.exceptions.BadUsageException; -import tmf.org.dsmapi.commons.exceptions.ExceptionType; -import tmf.org.dsmapi.commons.exceptions.UnknownResourceException; -import tmf.org.dsmapi.commons.utils.Format; -import static tmf.org.dsmapi.tt.TroubleTicketField.CORRELATION_ID; -import static tmf.org.dsmapi.tt.TroubleTicketField.CREATION_DATE; -import static tmf.org.dsmapi.tt.TroubleTicketField.DESCRIPTION; -import static tmf.org.dsmapi.tt.TroubleTicketField.NOTES; -import static tmf.org.dsmapi.tt.TroubleTicketField.RELATED_OBJECTS; -import static tmf.org.dsmapi.tt.TroubleTicketField.RELATED_PARTIES; -import static tmf.org.dsmapi.tt.TroubleTicketField.RESOLUTION_DATE; -import static tmf.org.dsmapi.tt.TroubleTicketField.SEVERITY; -import static tmf.org.dsmapi.tt.TroubleTicketField.STATUS; -import static tmf.org.dsmapi.tt.TroubleTicketField.STATUS_CHANGE_REASON; -import static tmf.org.dsmapi.tt.TroubleTicketField.SUB_STATUS; -import static tmf.org.dsmapi.tt.TroubleTicketField.TARGET_RESOLUTION_DATE; -import static tmf.org.dsmapi.tt.TroubleTicketField.TYPE; -import tmf.org.dsmapi.tt.Status; - -/** - * - * @author maig7313 - * - */ -@Stateless -public class TroubleTicketFacade extends AbstractFacade { - - @PersistenceContext(unitName = "DSTroubleTicketPU") - private EntityManager em; - private CriteriaBuilder cb; - - @PostConstruct - private void init() { - cb = em.getCriteriaBuilder(); - } - - /** - * - */ - public TroubleTicketFacade() { - super(TroubleTicket.class); - } - - public TroubleTicket edit(String id, TroubleTicket tt) throws UnknownResourceException { - tt.setId(id); - return super.edit(id, tt); - } - - /** - * - * @param partialTT - * @return - */ - public TroubleTicket partialEdit(String id, TroubleTicket partialTT) throws BadUsageException, UnknownResourceException { - - TroubleTicket currentTT = this.find(id); - - if (currentTT == null) { - throw new UnknownResourceException(ExceptionType.UNKNOWN_RESOURCE); - } - - Set tokens = partialTT.getFieldsIN(); - - if (tokens.contains(STATUS) & !(tokens.contains(STATUS_CHANGE_REASON))) { - throw new BadUsageException(ExceptionType.BAD_USAGE_MANDATORY_FIELDS, "While updating 'status', please provide a 'statusChangeReason'"); - } - - if (tokens.contains(STATUS)) { - if (WorkflowValidator.isCorrect(currentTT.getStatus(), partialTT.getStatus())) { - currentTT.setStatus(partialTT.getStatus()); - currentTT.setStatusChangeDate(Format.toString(new Date())); - currentTT.setStatusChangeReason(partialTT.getStatusChangeReason()); - } else { - throw new BadUsageException(ExceptionType.BAD_USAGE_STATUS_TRANSITION, "current=" + currentTT.getStatus() + " sent=" + partialTT.getStatus()); - } - } - - for (TroubleTicketField token : tokens) { - switch (token) { - case CORRELATION_ID: - currentTT.setCorrelationId(partialTT.getCorrelationId()); - break; - case CREATION_DATE: - currentTT.setCreationDate(partialTT.getCreationDate()); - break; - case DESCRIPTION: - if (partialTT.getDescription() != null) { - currentTT.setDescription(partialTT.getDescription()); - } - break; - case NOTES: - currentTT.setNotes(partialTT.getNotes()); - break; - case RELATED_OBJECTS: - currentTT.setRelatedObjects(partialTT.getRelatedObjects()); - break; - case RELATED_PARTIES: - currentTT.setRelatedParties(partialTT.getRelatedParties()); - break; - case RESOLUTION_DATE: - currentTT.setResolutionDate(partialTT.getResolutionDate()); - break; - case SEVERITY: - if (partialTT.getSeverity() != null) { - currentTT.setSeverity(partialTT.getSeverity()); - } - break; - case SUB_STATUS: - currentTT.setSubStatus(partialTT.getSubStatus()); - break; - case TARGET_RESOLUTION_DATE: - currentTT.setResolutionDate(partialTT.getResolutionDate()); - break; - case TYPE: - if (partialTT.getDescription() != null) { - currentTT.setType(partialTT.getType()); - } - break; - } - } - - return currentTT; - - } - - @Override - public void create(TroubleTicket tt) throws BadUsageException { - - if (tt.getId() != null) { - throw new BadUsageException(ExceptionType.BAD_USAGE_GENERIC, "While creating a TT id should be null"); - } - - if ((tt.getDescription() == null) || (tt.getSeverity() == null) || (tt.getType() == null)) { - String fieldName = null; - if (tt.getDescription() == null) { - fieldName = TroubleTicketField.DESCRIPTION.getText(); - } else { - if (tt.getSeverity() == null) { - fieldName = TroubleTicketField.SEVERITY.getText(); - } else if (tt.getType() == null) { - fieldName = TroubleTicketField.TYPE.getText(); - } - } - throw new BadUsageException(ExceptionType.BAD_USAGE_MANDATORY_FIELDS, fieldName); - } - - tt.setStatus(Status.Submitted); - tt.setStatusChangeDate(Format.toString(new Date())); - tt.setStatusChangeReason("Creation"); - - super.create(tt); - } - - /** - * - * @return - */ - @Override - protected EntityManager getEntityManager() { - return em; - } - - public List find(MultivaluedMap queryParameters) { - - List tickets; - if (queryParameters != null && !queryParameters.isEmpty()) { - tickets = findByCriteria(queryParameters, TroubleTicket.class); - } else { - tickets = this.findAll(); - } - return tickets; - - } - - public int removeAll() { - List tickets = this.findAll(); - int size = tickets.size(); - for (TroubleTicket tt : tickets) { - em.remove(tt); - } - return size; - } -} diff --git a/src/java/tmf/org/dsmapi/tt/service/TroubleTicketFacadeREST.java b/src/java/tmf/org/dsmapi/tt/service/TroubleTicketFacadeREST.java deleted file mode 100644 index 5723843..0000000 --- a/src/java/tmf/org/dsmapi/tt/service/TroubleTicketFacadeREST.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.tt.service; -//changes22222 now look agan too much bbbbb cccc vvvvv last vvv mo - -import tmf.org.dsmapi.tt.service.mapper.FacadeRestUtil; -import tmf.org.dsmapi.commons.jaxrs.PATCH; -import tmf.org.dsmapi.tt.TroubleTicket; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Set; -import javax.ejb.EJB; -import javax.ejb.Stateless; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; -import javax.ws.rs.core.UriInfo; -import org.codehaus.jackson.node.ObjectNode; -import tmf.org.dsmapi.commons.exceptions.BadUsageException; -import tmf.org.dsmapi.commons.exceptions.UnknownResourceException; -import tmf.org.dsmapi.hub.service.PublisherLocal; -import tmf.org.dsmapi.tt.service.TroubleTicketFacade; - -/** - * - * @author pierregauthier - */ -@Stateless -@Path("troubleTicket") -public class TroubleTicketFacadeREST { - - @Context - UriInfo uriInfo; - @EJB - TroubleTicketFacade manager; - @EJB - PublisherLocal publisher; - - public TroubleTicketFacadeREST() { - } - - /* - * RESOURCE - * troubleTicket - */ - @GET - @Produces({"application/json"}) - public Response list(@Context UriInfo info) { - - Response response = null; - // search queryParameters - MultivaluedMap queryParameters = info.getQueryParameters(); - // fields to filter view - Set fieldSet = FacadeRestUtil.getFieldSet(queryParameters); - - List resultList = manager.find(queryParameters); - - if (fieldSet.isEmpty() || fieldSet.contains(FacadeRestUtil.ALL_FIELDS)) { - - response = Response.ok(resultList).build(); - - } else { - - fieldSet.add(FacadeRestUtil.ID_FIELD); - List nodeList = new ArrayList(); - ObjectNode node; - for (TroubleTicket tt : resultList) { - node = FacadeRestUtil.createNodeViewWithFields(tt, fieldSet); - nodeList.add(node); - } - response = Response.ok(nodeList).build(); - - } - - return response; - } - - @POST - @Consumes({"application/json"}) - @Produces({"application/json"}) - public Response post(TroubleTicket entity) throws BadUsageException { - - manager.create(entity); - - System.out.println("Calling Publish"); - publisher.publishTicketCreateNotification(entity); - System.out.println("After Calling Publish"); - - // 201 OK + location - UriBuilder uriBuilder = UriBuilder.fromUri(uriInfo.getRequestUri()); - String id = entity.getId(); - uriBuilder.path("{id}"); - return Response.created(uriBuilder.build(id)). - entity(entity). - build(); - - } - - /* - * RESOURCE - * troubleTicket/{id} - */ - @PUT - @Path("{id}") - @Consumes({"application/json"}) - @Produces({"application/json"}) - public Response put(@PathParam("id") String id, TroubleTicket entity) throws UnknownResourceException { - - // Try to merge - entity = manager.edit(id, entity); - - publisher.publishTicketChangedNotification(entity); - publisher.publishTicketStatusChangedNotification(entity); - - // 201 OK + location - UriBuilder uriBuilder = UriBuilder.fromUri(uriInfo.getRequestUri()); - uriBuilder.path("{id}"); - return Response.created(uriBuilder.build(id)). - entity(entity). - build(); - } - - //X-HTTP-Method-Override on POST - @PATCH - @Path("{id}") - @Consumes({"application/json"}) - @Produces({"application/json"}) - public Response patch(@PathParam("id") String id, TroubleTicket partialTT) throws BadUsageException, UnknownResourceException { - - TroubleTicket fullTT = manager.partialEdit(id, partialTT); - - publisher.publishTicketChangedNotification(partialTT); - publisher.publishTicketStatusChangedNotification(partialTT); - - // 201 OK + location - UriBuilder uriBuilder = UriBuilder.fromUri(uriInfo.getRequestUri()); - id = fullTT.getId(); - uriBuilder.path("{id}"); - return Response.created(uriBuilder.build(id)). - entity(fullTT). - build(); - - - } - - @GET - @Path("{id}") - @Produces({"application/json"}) - public Response get(@PathParam("id") String id, @Context UriInfo info) throws UnknownResourceException { - - Response response = null; - TroubleTicket tt = manager.find(id); - - MultivaluedMap queryParameters = info.getQueryParameters(); - Set fieldSet = FacadeRestUtil.getFieldSet(queryParameters); - - if (fieldSet.isEmpty() || fieldSet.contains(FacadeRestUtil.ALL_FIELDS)) { - response = Response.ok(tt).build(); - } else { - fieldSet.add(FacadeRestUtil.ID_FIELD); - ObjectNode root = FacadeRestUtil.createNodeViewWithFields(tt, fieldSet); - response = Response.ok(root).build(); - } - - return response; - } - - public static Date parse(String input) throws java.text.ParseException { - - //NOTE: SimpleDateFormat uses GMT[-+]hh:mm for the TZ which breaks - //things a bit. Before we go on we have to repair this. - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"); - - //this is zero time so we need to add that TZ indicator for - if (input.endsWith("Z")) { - input = input.substring(0, input.length() - 1) + "GMT-00:00"; - } else { - int inset = 6; - String s0 = input.substring(0, input.length() - inset); - String s1 = input.substring(input.length() - inset, input.length()); - input = s0 + "GMT" + s1; - } - - return df.parse(input); - - } -} diff --git a/src/java/tmf/org/dsmapi/tt/service/WorkflowValidator.java b/src/java/tmf/org/dsmapi/tt/service/WorkflowValidator.java deleted file mode 100644 index 882a4d9..0000000 --- a/src/java/tmf/org/dsmapi/tt/service/WorkflowValidator.java +++ /dev/null @@ -1,56 +0,0 @@ -package tmf.org.dsmapi.tt.service; - -import tmf.org.dsmapi.tt.Status; -import static tmf.org.dsmapi.tt.Status.Acknowledged; -import static tmf.org.dsmapi.tt.Status.Cancelled; -import static tmf.org.dsmapi.tt.Status.Rejected; -import static tmf.org.dsmapi.tt.Status.Resolved; -import static tmf.org.dsmapi.tt.Status.Submitted; - -public class WorkflowValidator { - - public static boolean isCorrect(Status current, Status next) { - - boolean valid = false; - - switch (current) { - case Submitted: - switch (next) { - case Rejected: - case Acknowledged: - valid = true; - break; - } - break; - case Acknowledged: - switch (next) { - case InProgress_Held: - case InProgress_Pending: - case Cancelled: - valid = true; - break; - } - break; - case InProgress_Held: - case InProgress_Pending: - switch (next) { - case Resolved: - case Cancelled: - valid = true; - break; - } - break; - case Resolved: - switch (next) { - case Closed: - case InProgress_Held: - case InProgress_Pending: - valid = true; - break; - } - break; - } - return valid; - } - -} diff --git a/src/java/tmf/org/dsmapi/tt/service/mapper/FacadeRestUtil.java b/src/java/tmf/org/dsmapi/tt/service/mapper/FacadeRestUtil.java deleted file mode 100644 index f5f7cd7..0000000 --- a/src/java/tmf/org/dsmapi/tt/service/mapper/FacadeRestUtil.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.tt.service.mapper; - -import com.sun.jersey.core.util.MultivaluedMapImpl; -import java.math.BigDecimal; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.ws.rs.core.MultivaluedMap; -import org.apache.commons.beanutils.PropertyUtilsBean; -import org.codehaus.jackson.JsonNode; -import org.codehaus.jackson.map.ObjectMapper; -import org.codehaus.jackson.node.ArrayNode; -import org.codehaus.jackson.node.ObjectNode; - -/** - * - * @author jyus7291 - */ -public class FacadeRestUtil { - - private static PropertyUtilsBean pub = new PropertyUtilsBean(); - /** - * - */ - public static final String ALL_FIELDS = "all"; - /** - * - */ - public static final String ID_FIELD = "id"; - /** - * - */ - public static final String QUERY_KEY_FIELD = "fields"; - /** - * - */ - public static final String QUERY_KEY_FIELD_ESCAPE = ":"; - - /** - * - * @param bean - * @param fieldNames - * @return - */ - public static ObjectNode createNodeViewWithFields(Object bean, Set fieldNames) { - ObjectMapper mapper = new ObjectMapper(); - return createNodeViewWithFields(mapper, bean, fieldNames); - } - - private static ObjectNode createNodeViewWithFields(ObjectMapper mapper, Object bean, Set fieldNames) { - // split fieldNames in 2 categories : - // simpleFields for simple property names with no '.' - // nestedFields for nested property names with a '.' - Set simpleFields = new HashSet(); - MultivaluedMapImpl nestedFields = new MultivaluedMapImpl(); - for (String fieldName : fieldNames) { - int index = fieldName.indexOf('.'); - boolean isNestedField = index > 0 && index < fieldName.length(); - if (isNestedField) { - String rootFieldName = fieldName.substring(0, index); - String subFieldName = fieldName.substring(index + 1); - nestedFields.add(rootFieldName, subFieldName); - } else { - simpleFields.add(fieldName); - } - } - - // create a simple node with only one level containing all simples properties - ObjectNode rootNode = createNodeWithSimpleFields(mapper, bean, simpleFields); - - // create nested nodes with deeper levels - Set>> entrySet = nestedFields.entrySet(); - // for each nested property, create recursively a node - for (Map.Entry> entry : entrySet) { - String rootFieldName = entry.getKey(); - // add in current node only if full property is not already present in 1st level - if (!simpleFields.contains(rootFieldName)) { - Object nestedBean = getField(bean, rootFieldName); - // add only non null fields - if (nestedBean == null) { - break; - } - Set nestedFieldNames = new HashSet(entry.getValue()); - // current node is an array - if (nestedBean.getClass().isArray()) { - Object[] array = (Object[]) nestedBean; - if (array.length > 0) { - // create a node for each element in array - // and add created node in an arrayNode - Collection nodes = new LinkedList(); - for (Object object : array) { - ObjectNode nestedNode = createNodeViewWithFields(mapper, object, nestedFieldNames); - nodes.add(nestedNode); - } - ArrayNode arrayNode = mapper.createArrayNode(); - arrayNode.addAll(nodes); - rootNode.put(rootFieldName, arrayNode); - } - } else { - // create recursively a node and add it in current root node - ObjectNode nestedNode = createNodeViewWithFields(mapper, nestedBean, nestedFieldNames); - rootNode.put(rootFieldName, nestedNode); - } - } - } - return rootNode; - } - - // create a simple flat node with only one-level fields - private static ObjectNode createNodeWithSimpleFields(ObjectMapper mapper, Object bean, Set fieldNames) { - ObjectNode node = mapper.createObjectNode(); - for (String fieldName : fieldNames) { - Object fieldValue = getField(bean, fieldName); - if (fieldValue != null) { - nodePut(node, fieldName, fieldValue); - } - } - return node; - } - - // generic node.put for any Object - private static void nodePut(ObjectNode node, String fieldName, Object value) { - if (value instanceof Boolean) { - node.put(fieldName, (Boolean) value); - } else if (value instanceof Integer) { - node.put(fieldName, (Integer) value); - } else if (value instanceof Long) { - node.put(fieldName, (Long) value); - } else if (value instanceof Float) { - node.put(fieldName, (Float) value); - } else if (value instanceof Double) { - node.put(fieldName, (Double) value); - } else if (value instanceof BigDecimal) { - node.put(fieldName, (BigDecimal) value); - } else if (value instanceof String) { - node.put(fieldName, (String) value); - } else { - node.putPOJO(fieldName, value); - } - - } - - // get value of field named "name" in bean - /** - * - * @param bean - * @param name - * @return - */ - public static Object getField(Object bean, String name) { - try { - return pub.getNestedProperty(bean, name); - } catch (Exception e) { - return null; - } - } - - /** - * - * @param queryParameters - * @return - */ - public static Set getFieldSet(MultivaluedMap queryParameters) { - Set fieldSet = new HashSet(); - if (queryParameters != null) { - List queryParameterField = queryParameters.remove(QUERY_KEY_FIELD_ESCAPE + QUERY_KEY_FIELD); - if (queryParameterField == null) { - queryParameterField = queryParameters.remove(QUERY_KEY_FIELD); - } - if (queryParameterField != null && !queryParameterField.isEmpty()) { - String queryParameterValue = queryParameterField.get(0); - if (queryParameterValue != null) { - String[] tokenArray = queryParameterValue.split(","); - fieldSet.addAll(Arrays.asList(tokenArray)); - } - } - } - return fieldSet; - } -} diff --git a/src/java/tmf/org/dsmapi/tt/service/mapper/TroubleTicketReader.java b/src/java/tmf/org/dsmapi/tt/service/mapper/TroubleTicketReader.java deleted file mode 100644 index 6902d58..0000000 --- a/src/java/tmf/org/dsmapi/tt/service/mapper/TroubleTicketReader.java +++ /dev/null @@ -1,83 +0,0 @@ -package tmf.org.dsmapi.tt.service.mapper; - -import tmf.org.dsmapi.tt.TroubleTicketField; -import tmf.org.dsmapi.tt.TroubleTicket; -import java.io.IOException; -import java.io.InputStream; -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; -import javax.ws.rs.Consumes; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.MessageBodyReader; -import javax.ws.rs.ext.Provider; -import org.codehaus.jackson.JsonNode; -import org.codehaus.jackson.map.ObjectMapper; - -/** - * - */ -@Provider -@Consumes({"application/json"}) -public class TroubleTicketReader implements MessageBodyReader { - - /** - * - * @param type - * @param genericType - * @param annotations - * @param mediaType - * @return - */ - @Override - public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { - return TroubleTicket.class.isAssignableFrom(type); - } - - /** - * - * @param type - * @param type1 - * @param antns - * @param mediaType - * @param httpHeaders - * @param entityStream - * @return - * @throws IOException - * @throws WebApplicationException - */ - @Override - public TroubleTicket readFrom(Class type, - Type type1, - Annotation[] antns, - MediaType mediaType, - MultivaluedMap httpHeaders, - InputStream entityStream) - throws IOException, WebApplicationException { - - TroubleTicket tt; - - System.out.println("=== TroubleTicket MessageBodyReader ENABLE ==="); - - ObjectMapper mapper = new ObjectMapper(); - JsonNode root = mapper.readValue(entityStream, JsonNode.class); - - Set tokenList = new HashSet(); - - // Iterate over first level to set received tokens - Iterator it = root.getFieldNames(); - while (it.hasNext()) { - tokenList.add(TroubleTicketField.fromString(it.next())); - } - - tt = mapper.readValue(root, TroubleTicket.class); - tt.setFieldsIN(tokenList); - - return tt; - } - -} diff --git a/src/java/tmf/org/dsmapi/commons/exceptions/BadUsageException.java b/src/main/java/org/tmf/dsmapi/commons/exceptions/BadUsageException.java similarity index 86% rename from src/java/tmf/org/dsmapi/commons/exceptions/BadUsageException.java rename to src/main/java/org/tmf/dsmapi/commons/exceptions/BadUsageException.java index 2ba7186..0e41f42 100644 --- a/src/java/tmf/org/dsmapi/commons/exceptions/BadUsageException.java +++ b/src/main/java/org/tmf/dsmapi/commons/exceptions/BadUsageException.java @@ -1,4 +1,4 @@ -package tmf.org.dsmapi.commons.exceptions; +package org.tmf.dsmapi.commons.exceptions; public class BadUsageException extends FunctionalException { diff --git a/src/java/tmf/org/dsmapi/commons/exceptions/ExceptionBean.java b/src/main/java/org/tmf/dsmapi/commons/exceptions/ExceptionBean.java similarity index 93% rename from src/java/tmf/org/dsmapi/commons/exceptions/ExceptionBean.java rename to src/main/java/org/tmf/dsmapi/commons/exceptions/ExceptionBean.java index 38cb694..791740e 100644 --- a/src/java/tmf/org/dsmapi/commons/exceptions/ExceptionBean.java +++ b/src/main/java/org/tmf/dsmapi/commons/exceptions/ExceptionBean.java @@ -1,4 +1,4 @@ -package tmf.org.dsmapi.commons.exceptions; +package org.tmf.dsmapi.commons.exceptions; public class ExceptionBean { diff --git a/src/java/tmf/org/dsmapi/commons/exceptions/ExceptionType.java b/src/main/java/org/tmf/dsmapi/commons/exceptions/ExceptionType.java similarity index 78% rename from src/java/tmf/org/dsmapi/commons/exceptions/ExceptionType.java rename to src/main/java/org/tmf/dsmapi/commons/exceptions/ExceptionType.java index 99e40b7..99ff710 100644 --- a/src/java/tmf/org/dsmapi/commons/exceptions/ExceptionType.java +++ b/src/main/java/org/tmf/dsmapi/commons/exceptions/ExceptionType.java @@ -1,4 +1,4 @@ -package tmf.org.dsmapi.commons.exceptions; +package org.tmf.dsmapi.commons.exceptions; public enum ExceptionType { @@ -7,6 +7,9 @@ public enum ExceptionType { BAD_USAGE_STATUS_TRANSITION(new ExceptionBean("4002", "The status transition is not allowed")), BAD_USAGE_MANDATORY_FIELDS(new ExceptionBean("4003", "Missing mandatory field")), BAD_USAGE_UNKNOWN_VALUE(new ExceptionBean("4004", "Unknown value")), + BAD_USAGE_OPERATOR(new ExceptionBean("4011", "Wrong operator usage")), + BAD_USAGE_FORMAT(new ExceptionBean("4012", "Wrong format")), + TECHNICAL(new ExceptionBean("9000", "Technical error")), UNKNOWN_RESOURCE(new ExceptionBean("4041", "Unknown resource")); private ExceptionBean info; diff --git a/src/java/tmf/org/dsmapi/commons/exceptions/FunctionalException.java b/src/main/java/org/tmf/dsmapi/commons/exceptions/FunctionalException.java similarity index 71% rename from src/java/tmf/org/dsmapi/commons/exceptions/FunctionalException.java rename to src/main/java/org/tmf/dsmapi/commons/exceptions/FunctionalException.java index ba9450d..16689ed 100644 --- a/src/java/tmf/org/dsmapi/commons/exceptions/FunctionalException.java +++ b/src/main/java/org/tmf/dsmapi/commons/exceptions/FunctionalException.java @@ -1,6 +1,8 @@ -package tmf.org.dsmapi.commons.exceptions; +package org.tmf.dsmapi.commons.exceptions; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; /** * generic class for all functional exceptions. Functional exceptions must be @@ -12,6 +14,7 @@ public class FunctionalException extends Exception implements Serializable { private String localisationClass; private String localisationMethod; private ExceptionType type; + private List keyValue; public FunctionalException() { @@ -89,5 +92,27 @@ public ExceptionType getType() { public void setType(ExceptionType type) { this.type = type; } - + + /** + * @return the keyValue + */ + public List getKeyValue() { + return keyValue; + } + + public List addKeyValue(KeyValue keyValue) { + if (this.keyValue == null) { + this.keyValue = new ArrayList(); + } + this.keyValue.add(keyValue); + return this.keyValue; + } + + public List addKeyValue(String key, String value) { + if (this.keyValue == null) { + this.keyValue = new ArrayList(); + } + this.keyValue.add(new KeyValue(key, value)); + return this.keyValue; + } } diff --git a/src/main/java/org/tmf/dsmapi/commons/exceptions/KeyValue.java b/src/main/java/org/tmf/dsmapi/commons/exceptions/KeyValue.java new file mode 100644 index 0000000..c1d8763 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/commons/exceptions/KeyValue.java @@ -0,0 +1,39 @@ +package org.tmf.dsmapi.commons.exceptions; + +public class KeyValue { + private String key; + private String value; + + public KeyValue(String key, String value) { + this.key = key; + this.value = value; + } + + /** + * @return the key + */ + public String getKey() { + return key; + } + + /** + * @param key the key to set + */ + public void setKey(String key) { + this.key = key; + } + + /** + * @return the value + */ + public String getValue() { + return value; + } + + /** + * @param value the value to set + */ + public void setValue(String value) { + this.value = value; + } +} diff --git a/src/main/java/org/tmf/dsmapi/commons/exceptions/TechnicalException.java b/src/main/java/org/tmf/dsmapi/commons/exceptions/TechnicalException.java new file mode 100644 index 0000000..f342b3f --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/commons/exceptions/TechnicalException.java @@ -0,0 +1,113 @@ +package org.tmf.dsmapi.commons.exceptions; + +import java.io.Serializable; + +/** + * generic class for all functional exceptions. Functional exceptions must be + * checked exceptions + */ +public class TechnicalException extends RuntimeException implements Serializable { + + private static final long serialVersionUID = 7552671441723224932L; + private String localisationClass; + private String localisationMethod; + private ExceptionType type = ExceptionType.TECHNICAL; + + + /** + * + */ + public TechnicalException() { + super(); + localisationClass = ""; + localisationMethod = ""; + } + + /** + * + * @param message + */ + public TechnicalException(String message) { + super(message); + localisationClass = ""; + localisationMethod = ""; + } + + /** + * + * @param message + * @param cause + */ + public TechnicalException(String message, Throwable cause) { + super(message, cause); + localisationClass = ""; + localisationMethod = ""; + } + + /** + * + * @param cause + */ + public TechnicalException(Throwable cause) { + super(cause); + localisationClass = ""; + localisationMethod = ""; + } + + /** + * + * @param clazz + * @param method + * @param message + */ + public TechnicalException(String clazz, String method, String message) { + super(message); + localisationClass = clazz; + localisationMethod = method; + } + + /** + * + * @param clazz + * @param method + * @param message + * @param cause + */ + public TechnicalException(String clazz, String method, String message, Throwable cause) { + super(message, cause); + localisationClass = clazz; + localisationMethod = method; + } + + /** + * + * @return + */ + public String getLocalisationClasse() { + return localisationClass; + } + + /** + * + * @return + */ + public String getLocalisationMethod() { + return localisationMethod; + } + + /** + * + * @return + */ + public String getLocalisation() { + return localisationClass + "." + localisationMethod; + } + + /** + * @return the type + */ + public ExceptionType getType() { + return type; + } + +} diff --git a/src/java/tmf/org/dsmapi/commons/exceptions/UnknownResourceException.java b/src/main/java/org/tmf/dsmapi/commons/exceptions/UnknownResourceException.java similarity index 86% rename from src/java/tmf/org/dsmapi/commons/exceptions/UnknownResourceException.java rename to src/main/java/org/tmf/dsmapi/commons/exceptions/UnknownResourceException.java index b09253d..1318aaf 100644 --- a/src/java/tmf/org/dsmapi/commons/exceptions/UnknownResourceException.java +++ b/src/main/java/org/tmf/dsmapi/commons/exceptions/UnknownResourceException.java @@ -1,4 +1,4 @@ -package tmf.org.dsmapi.commons.exceptions; +package org.tmf.dsmapi.commons.exceptions; public class UnknownResourceException extends FunctionalException { diff --git a/src/java/tmf/org/dsmapi/tt/service/AbstractFacade.java b/src/main/java/org/tmf/dsmapi/commons/facade/AbstractFacade.java similarity index 51% rename from src/java/tmf/org/dsmapi/tt/service/AbstractFacade.java rename to src/main/java/org/tmf/dsmapi/commons/facade/AbstractFacade.java index a3e2063..273f33d 100644 --- a/src/java/tmf/org/dsmapi/tt/service/AbstractFacade.java +++ b/src/main/java/org/tmf/dsmapi/commons/facade/AbstractFacade.java @@ -1,15 +1,16 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.tt.service; +package org.tmf.dsmapi.commons.facade; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; +import java.util.Date; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; @@ -19,29 +20,42 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.ws.rs.core.MultivaluedMap; -import tmf.org.dsmapi.commons.exceptions.BadUsageException; -import tmf.org.dsmapi.commons.exceptions.ExceptionType; -import tmf.org.dsmapi.commons.exceptions.UnknownResourceException; -import static tmf.org.dsmapi.tt.service.mapper.FacadeRestUtil.QUERY_KEY_FIELD; -import static tmf.org.dsmapi.tt.service.mapper.FacadeRestUtil.QUERY_KEY_FIELD_ESCAPE; -import tmf.org.dsmapi.tt.Status; -import tmf.org.dsmapi.tt.TroubleTicket; +import org.tmf.dsmapi.commons.exceptions.BadUsageException; +import org.tmf.dsmapi.commons.exceptions.ExceptionType; +import org.tmf.dsmapi.commons.exceptions.UnknownResourceException; +import org.tmf.dsmapi.commons.utils.TMFDate; /** - * xxxxx * * @author pierregauthier */ public abstract class AbstractFacade { + private Class entityClass; + private static final String pattern = "yyyy-MM-dd'T'HH:mm:ssZ"; + private static SimpleDateFormat formatter = new SimpleDateFormat(pattern); + /** + * + * @param entityClass + */ public AbstractFacade(Class entityClass) { this.entityClass = entityClass; } + /** + * + * @return + */ protected abstract EntityManager getEntityManager(); + /** + * + * @param entities + * @return + * @throws BadUsageException + */ public int create(List entities) throws BadUsageException { for (T entity : entities) { this.create(entity); @@ -49,24 +63,50 @@ public int create(List entities) throws BadUsageException { return entities.size(); } + /** + * + * @param entity + * @throws BadUsageException + */ public void create(T entity) throws BadUsageException { getEntityManager().persist(entity); } - public T edit(String id, T entity) throws UnknownResourceException { - T targetEntity = this.find(id); - if (targetEntity == null) { - throw new UnknownResourceException(ExceptionType.UNKNOWN_RESOURCE); - } + /** + * + * @param entity + * @return + * @throws UnknownResourceException + */ + public T edit(T entity) throws UnknownResourceException { getEntityManager().merge(entity); return entity; } + /** + * + * @param id + * @throws UnknownResourceException + */ public void remove(Object id) throws UnknownResourceException { T entity = getEntityManager().find(entityClass, id); getEntityManager().remove(getEntityManager().merge(entity)); } + public void removeAll() { + List all = findAll(); + EntityManager em = getEntityManager(); + for (T entity : all) { + em.remove(entity); + } + } + + /** + * + * @param id + * @return + * @throws UnknownResourceException + */ public T find(Object id) throws UnknownResourceException { T entity = getEntityManager().find(entityClass, id); if (entity == null) { @@ -75,20 +115,36 @@ public T find(Object id) throws UnknownResourceException { return entity; } + /** + * + * @param entity + */ public void detach(T entity) { getEntityManager().detach(entity); } - public void invalidCache() { + /** + * + */ + public void clearCache() { getEntityManager().clear(); } + /** + * + * @return + */ public List findAll() { javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery(); cq.select(cq.from(entityClass)); return getEntityManager().createQuery(cq).getResultList(); } + /** + * + * @param range + * @return + */ public List findRange(int[] range) { javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery(); cq.select(cq.from(entityClass)); @@ -98,6 +154,10 @@ public List findRange(int[] range) { return q.getResultList(); } + /** + * + * @return + */ public int count() { javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery(); javax.persistence.criteria.Root rt = cq.from(entityClass); @@ -106,41 +166,52 @@ public int count() { return ((Long) q.getSingleResult()).intValue(); } - public List findByCriteria(MultivaluedMap queryParameters, Class clazz) { + /** + * + * @param map + * @param clazz + * @return + */ + public List findByCriteria(MultivaluedMap map, Class clazz) { List resultsList = null; - CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder(); - CriteriaQuery cq = criteriaBuilder.createQuery(clazz); - List andPredicates = new ArrayList(); - Root tt = cq.from(clazz); - for (Map.Entry> entry : queryParameters.entrySet()) { - List valueList = entry.getValue(); - Predicate predicate = null; - if (valueList.size() > 1) { - // name=value1&name=value&...&name=valueN - // value of name is list [value1, value, ..., valueN] - // => name=value1 OR name=value OR ... OR name=valueN - List orPredicates = new ArrayList(); - for (String currentValue : valueList) { - Predicate orPredicate = buildPredicate(tt, entry.getKey(), currentValue); - orPredicates.add(orPredicate); + try { + CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder(); + CriteriaQuery cq = criteriaBuilder.createQuery(clazz); + List andPredicates = new ArrayList(); + Root tt = cq.from(clazz); + for (Map.Entry> entry : map.entrySet()) { + List valueList = entry.getValue(); + Predicate predicate = null; + if (valueList.size() > 1) { + // name=value1&name=value2&...&name=valueN + // value of name is list [value1, value2, ..., valueN] + // => name=value1 OR name=value2 OR ... OR name=valueN + List orPredicates = new ArrayList(); + for (String currentValue : valueList) { + Predicate orPredicate = buildPredicate(tt, entry.getKey(), currentValue); + orPredicates.add(orPredicate); + } + predicate = criteriaBuilder.or(orPredicates.toArray(new Predicate[orPredicates.size()])); + } else { + // name=value + // value of name is one element list [value] + // => name=value + predicate = buildPredicate(tt, entry.getKey(), valueList.get(0)); } - predicate = criteriaBuilder.or(orPredicates.toArray(new Predicate[orPredicates.size()])); - } else { - // name=value - // value of name is one element list [value] - // => name=value - predicate = buildPredicate(tt, entry.getKey(), valueList.get(0)); + andPredicates.add(predicate); } - andPredicates.add(predicate); + cq.where(andPredicates.toArray(new Predicate[andPredicates.size()])); + cq.select(tt); + TypedQuery q = getEntityManager().createQuery(cq); + resultsList = q.getResultList(); + return resultsList; + } catch (Exception ex) { + Logger.getLogger(AbstractFacade.class.getName()).log(Level.INFO, "findByCriteria error, null result returned", ex); + return null; } - cq.where(andPredicates.toArray(new Predicate[andPredicates.size()])); - cq.select(tt); - TypedQuery q = getEntityManager().createQuery(cq); - resultsList = q.getResultList(); - return resultsList; } - private Predicate buildPredicate(Path tt, String name, String value) { + private Predicate buildPredicate(Path tt, String name, String value) throws BadUsageException { Predicate predicate = null; int index = name.indexOf('.'); if (index > 0 && index < name.length()) { @@ -156,25 +227,15 @@ private Predicate buildPredicate(Path tt, String name, String value) { return predicate; } - private Predicate buildSimplePredicate(Path path, String name, String value) { + private Predicate buildSimplePredicate(Path tt, String name, String value) throws BadUsageException { Predicate predicate; CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder(); - if (isMultipleOrValue(value)) { - // name=value1,value,...,valueN - // => name=value1 OR name=value OR ... OR name=valueN - List valueList = convertMultipleOrValueToList(value); - List orPredicates = new ArrayList(); - for (String currentValue : valueList) { - Predicate orPredicate = buildPredicateWithOperator(path, name, currentValue); - orPredicates.add(orPredicate); - } - predicate = criteriaBuilder.or(orPredicates.toArray(new Predicate[orPredicates.size()])); - } else if (isMultipleAndValue(value)) { - // name=(subname1=value1&subname2=value&...&subnameN=valueN) - // => name.subname1=value1 AND name.subname2=value AND ... AND name.subnameN=valueN + if (isMultipleAndValue(value)) { + // name=(subname1=value1&subname2=value2&...&subnameN=valueN) + // => name.subname1=value1 AND name.subname2=value2 AND ... AND name.subnameN=valueN List> subFieldNameValue = convertMultipleAndValue(value); List andPredicates = new ArrayList(); - Path root = path.get(name); + Path root = tt.get(name); for (Map.Entry entry : subFieldNameValue) { String currentsubFieldName = entry.getKey(); String currentValue = entry.getValue(); @@ -182,25 +243,35 @@ private Predicate buildSimplePredicate(Path path, String name, String value) andPredicates.add(andPredicate); } predicate = criteriaBuilder.and(andPredicates.toArray(new Predicate[andPredicates.size()])); + } else if (isMultipleOrValue(value)) { + // name=value1,value2,...,valueN + // => name=value1 OR name=value2 OR ... OR name=valueN + List valueList = convertMultipleOrValueToList(value); + List orPredicates = new ArrayList(); + for (String currentValue : valueList) { + Predicate orPredicate = buildPredicateWithOperator(tt, name, currentValue); + orPredicates.add(orPredicate); + } + predicate = criteriaBuilder.or(orPredicates.toArray(new Predicate[orPredicates.size()])); } else { // name=value - predicate = buildPredicateWithOperator(path, name, value); + predicate = buildPredicateWithOperator(tt, name, value); } return predicate; } - // value has format value1,value,...,valueN + // value has format value1,value2,...,valueN private static boolean isMultipleOrValue(String value) { return (value.indexOf(",") > -1); } - // value has format (value1&value&...&valueN) + // value has format (value1&value2&...&valueN) private static boolean isMultipleAndValue(String value) { return (value.startsWith("(") && value.endsWith(")")); } - // convert String "value1,value,...,valueN" - // to List [value1, value, ..., valueN] + // convert String "value1,value2,...,valueN" + // to List [value1, value2, ..., valueN] private static List convertMultipleOrValueToList(String value) { List valueList = new ArrayList(); String[] tokenArray = value.split(","); @@ -208,8 +279,8 @@ private static List convertMultipleOrValueToList(String value) { return valueList; } - // convert String "(name1=value1&name2=value&...&nameN=valueN)" - // to List of Entry [name1=value1, name2=value, ..., nameN=valueN] + // convert String "(name1=value1&name2=value2&...&nameN=valueN)" + // to List of Entry [name1=value1, name2=value2, ..., nameN=valueN] // Conversion is not to a Map since there can be a same name with differents values private static List> convertMultipleAndValue(String multipleValue) { List> nameValueList = new ArrayList>(); @@ -228,68 +299,116 @@ private static List> convertMultipleAndValue(String mu return nameValueList; } - protected Predicate buildPredicateWithOperator(Path path, String name, String value) { + // safe Enum.valueOf without exception + private Enum safeEnumValueOf(Class enumType, String name) { + Enum enumValue = null; + if (name != null) { + try { + enumValue = Enum.valueOf(enumType, name); + } catch (Exception e) { + enumValue = null; + } + } + return enumValue; + } + private Object convertStringValueToObject(String value, Class clazz) throws BadUsageException { + Object convertedValue = null; + if (clazz.isEnum()) { + convertedValue = safeEnumValueOf(clazz, value); + } else if (Date.class.isAssignableFrom(clazz)) { + convertedValue = TMFDate.parse(value); + } else if ((clazz.isPrimitive() && !clazz.equals(boolean.class)) + || (Number.class.isAssignableFrom(clazz))) { + try { + convertedValue = NumberFormat.getInstance().parse(value); + } catch (ParseException ex) { + convertedValue = null; + } + } else { + convertedValue = value; + } + if (convertedValue != null) { + return convertedValue; + } else { + throw new BadUsageException(ExceptionType.BAD_USAGE_FORMAT, "Wrong format for value " + value); + } + + } + + // operators = and <> are compatibles with all types + // operators < > <= >= are compatibles with numbers and dates + // operator "LIKE" is compatible with String + private boolean classCompatibleWithOperator(Class clazz, Operator operator) { + if (operator == null) { + return true; + } else { + switch (operator) { + case NE: + case EQ: + return true; + case GT: + case GTE: + case LT: + case LTE: + return (Date.class.isAssignableFrom(clazz) + || (clazz.isPrimitive() && !clazz.equals(boolean.class)) + || Number.class.isAssignableFrom(clazz)); + case EX: + return String.class.equals(clazz); + default: + return false; + } + } + } + + /** + * + * @param tt + * @param name + * @param value + * @return + * @throws BadUsageException + */ + protected Predicate buildPredicateWithOperator(Path tt, String name, String value) throws BadUsageException { CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder(); Operator operator = Operator.fromString(name); // perform operation, default operation is equal if (operator == null) { - Path attribute = path.get(name); - Object valueObject = convertStringValueToObject(attribute, value); + Path attribute = tt.get(name); + Object valueObject = convertStringValueToObject(value, attribute.getJavaType()); + System.out.println("### bp RETURN " + name + "=" + value); return criteriaBuilder.equal(attribute, valueObject); } else { + Class javaType = tt.getJavaType(); + if (!classCompatibleWithOperator(javaType, operator)) { + throw new BadUsageException(ExceptionType.BAD_USAGE_OPERATOR, operator.getValue() + " operator incompatible with field"); + } + Object valueObject = convertStringValueToObject(value, javaType); switch (operator) { case GT: - return criteriaBuilder.greaterThan((Expression) path, value); + return criteriaBuilder.greaterThan((Expression) tt, (Comparable) valueObject); case GTE: - return criteriaBuilder.greaterThanOrEqualTo((Expression) path, value); + return criteriaBuilder.greaterThanOrEqualTo((Expression) tt, (Comparable) valueObject); case LT: - return criteriaBuilder.lessThan((Expression) path, value); + return criteriaBuilder.lessThan((Expression) tt, (Comparable) valueObject); case LTE: - return criteriaBuilder.lessThanOrEqualTo((Expression) path, value); - case NE: { - Object valueObject = convertStringValueToObject(path, value); - return criteriaBuilder.notEqual(path, valueObject); - } - case EQ: { - Object valueObject = convertStringValueToObject(path, value); - return criteriaBuilder.equal(path, valueObject); - } + return criteriaBuilder.lessThanOrEqualTo((Expression) tt, (Comparable) valueObject); + case NE: + return criteriaBuilder.notEqual(tt, valueObject); + case EQ: + return criteriaBuilder.equal(tt, valueObject); case EX: - return criteriaBuilder.like((Expression) path, value.replace('*', '%')); + return criteriaBuilder.like((Expression) tt, value.replace('*', '%')); default: { - Path attribute = path.get(name); - Object valueObject = convertStringValueToObject(attribute, value); + Path attribute = tt.get(name); + valueObject = convertStringValueToObject(value, attribute.getJavaType()); return criteriaBuilder.equal(attribute, valueObject); } } } - - } - - private Enum safeEnumValueOf(Class enumType, String name) { - Enum enumValue = null; - if (name != null) { - try { - enumValue = Enum.valueOf(enumType, name); - } catch (Exception e) { - enumValue = null; - } - } - return enumValue; - } - - private Object convertStringValueToObject(Path tt, String value) { - Class javaType = tt.getJavaType(); - if (javaType.isEnum()) { - Enum enumValue = safeEnumValueOf(javaType, value); - return enumValue; - } else { - return value; - } } - //protected abstract Predicate buildPredicateForEnum(Path path, String name, String value); enum Operator { EQ("eq"), @@ -320,4 +439,4 @@ public static Operator fromString(String value) { return null; } } -} +} \ No newline at end of file diff --git a/src/main/java/org/tmf/dsmapi/commons/jaxrs/CorsFilter.java b/src/main/java/org/tmf/dsmapi/commons/jaxrs/CorsFilter.java new file mode 100644 index 0000000..6a8b4f3 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/commons/jaxrs/CorsFilter.java @@ -0,0 +1,32 @@ +package org.tmf.dsmapi.commons.jaxrs; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletResponse; + +@WebFilter(filterName = "CorsFilter", urlPatterns = {"/*"}) +public class CorsFilter implements javax.servlet.Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletResponse res = (HttpServletResponse) response; + res.addHeader("Access-Control-Allow-Origin", "*"); + res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT"); + res.addHeader("Access-Control-Allow-Headers", "Content-Type"); + chain.doFilter(request, response); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void destroy() { + } + +} diff --git a/src/java/tmf/org/dsmapi/tt/service/JsonFault.java b/src/main/java/org/tmf/dsmapi/commons/jaxrs/model/JsonFault.java similarity index 90% rename from src/java/tmf/org/dsmapi/tt/service/JsonFault.java rename to src/main/java/org/tmf/dsmapi/commons/jaxrs/model/JsonFault.java index b632c03..45b4157 100644 --- a/src/java/tmf/org/dsmapi/tt/service/JsonFault.java +++ b/src/main/java/org/tmf/dsmapi/commons/jaxrs/model/JsonFault.java @@ -1,8 +1,8 @@ -package tmf.org.dsmapi.tt.service; +package org.tmf.dsmapi.commons.jaxrs.model; import javax.xml.bind.annotation.XmlRootElement; import org.codehaus.jackson.map.annotate.JsonSerialize; -import tmf.org.dsmapi.commons.exceptions.ExceptionBean; +import org.tmf.dsmapi.commons.exceptions.ExceptionBean; @XmlRootElement @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) diff --git a/src/java/tmf/org/dsmapi/tt/service/Report.java b/src/main/java/org/tmf/dsmapi/commons/jaxrs/model/Report.java similarity index 97% rename from src/java/tmf/org/dsmapi/tt/service/Report.java rename to src/main/java/org/tmf/dsmapi/commons/jaxrs/model/Report.java index ff5c7be..6d3341a 100644 --- a/src/java/tmf/org/dsmapi/tt/service/Report.java +++ b/src/main/java/org/tmf/dsmapi/commons/jaxrs/model/Report.java @@ -1,4 +1,4 @@ -package tmf.org.dsmapi.tt.service; +package org.tmf.dsmapi.commons.jaxrs.model; import javax.xml.bind.annotation.XmlRootElement; import org.codehaus.jackson.map.annotate.JsonSerialize; diff --git a/src/java/tmf/org/dsmapi/tt/service/mapper/BadUsageExceptionMapper.java b/src/main/java/org/tmf/dsmapi/commons/jaxrs/provider/BadUsageExceptionMapper.java similarity index 77% rename from src/java/tmf/org/dsmapi/tt/service/mapper/BadUsageExceptionMapper.java rename to src/main/java/org/tmf/dsmapi/commons/jaxrs/provider/BadUsageExceptionMapper.java index 6ad564d..aa1288c 100644 --- a/src/java/tmf/org/dsmapi/tt/service/mapper/BadUsageExceptionMapper.java +++ b/src/main/java/org/tmf/dsmapi/commons/jaxrs/provider/BadUsageExceptionMapper.java @@ -2,13 +2,13 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.tt.service.mapper; +package org.tmf.dsmapi.commons.jaxrs.provider; -import tmf.org.dsmapi.tt.service.JsonFault; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; -import tmf.org.dsmapi.commons.exceptions.BadUsageException; +import org.tmf.dsmapi.commons.exceptions.BadUsageException; +import org.tmf.dsmapi.commons.jaxrs.model.JsonFault; @Provider public class BadUsageExceptionMapper implements ExceptionMapper { diff --git a/src/java/tmf/org/dsmapi/tt/service/mapper/JacksonConfigurator.java b/src/main/java/org/tmf/dsmapi/commons/jaxrs/provider/JacksonConfigurator.java similarity index 90% rename from src/java/tmf/org/dsmapi/tt/service/mapper/JacksonConfigurator.java rename to src/main/java/org/tmf/dsmapi/commons/jaxrs/provider/JacksonConfigurator.java index fdca7bd..45b43c2 100644 --- a/src/java/tmf/org/dsmapi/tt/service/mapper/JacksonConfigurator.java +++ b/src/main/java/org/tmf/dsmapi/commons/jaxrs/provider/JacksonConfigurator.java @@ -2,7 +2,7 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.tt.service.mapper; +package org.tmf.dsmapi.commons.jaxrs.provider; import javax.ws.rs.Produces; import javax.ws.rs.ext.ContextResolver; diff --git a/src/java/tmf/org/dsmapi/tt/service/mapper/JsonMappingExceptionMapper.java b/src/main/java/org/tmf/dsmapi/commons/jaxrs/provider/JsonMappingExceptionMapper.java similarity index 80% rename from src/java/tmf/org/dsmapi/tt/service/mapper/JsonMappingExceptionMapper.java rename to src/main/java/org/tmf/dsmapi/commons/jaxrs/provider/JsonMappingExceptionMapper.java index 1d148b0..b79ba9e 100644 --- a/src/java/tmf/org/dsmapi/tt/service/mapper/JsonMappingExceptionMapper.java +++ b/src/main/java/org/tmf/dsmapi/commons/jaxrs/provider/JsonMappingExceptionMapper.java @@ -2,14 +2,14 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.tt.service.mapper; +package org.tmf.dsmapi.commons.jaxrs.provider; -import tmf.org.dsmapi.tt.service.JsonFault; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import org.codehaus.jackson.map.JsonMappingException; -import tmf.org.dsmapi.commons.exceptions.ExceptionType; +import org.tmf.dsmapi.commons.exceptions.ExceptionType; +import org.tmf.dsmapi.commons.jaxrs.model.JsonFault; @Provider public class JsonMappingExceptionMapper implements ExceptionMapper { diff --git a/src/java/tmf/org/dsmapi/tt/service/mapper/UnknowResourceExceptionMapper.java b/src/main/java/org/tmf/dsmapi/commons/jaxrs/provider/UnknowResourceExceptionMapper.java similarity index 76% rename from src/java/tmf/org/dsmapi/tt/service/mapper/UnknowResourceExceptionMapper.java rename to src/main/java/org/tmf/dsmapi/commons/jaxrs/provider/UnknowResourceExceptionMapper.java index 583adf8..8f37327 100644 --- a/src/java/tmf/org/dsmapi/tt/service/mapper/UnknowResourceExceptionMapper.java +++ b/src/main/java/org/tmf/dsmapi/commons/jaxrs/provider/UnknowResourceExceptionMapper.java @@ -2,13 +2,13 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.tt.service.mapper; +package org.tmf.dsmapi.commons.jaxrs.provider; -import tmf.org.dsmapi.tt.service.JsonFault; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; -import tmf.org.dsmapi.commons.exceptions.UnknownResourceException; +import org.tmf.dsmapi.commons.exceptions.UnknownResourceException; +import org.tmf.dsmapi.commons.jaxrs.model.JsonFault; @Provider public class UnknowResourceExceptionMapper implements ExceptionMapper { diff --git a/src/main/java/org/tmf/dsmapi/commons/utils/BeanUtils.java b/src/main/java/org/tmf/dsmapi/commons/utils/BeanUtils.java new file mode 100644 index 0000000..54dd724 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/commons/utils/BeanUtils.java @@ -0,0 +1,75 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.tmf.dsmapi.commons.utils; + +import java.util.Iterator; +import org.apache.commons.beanutils.PropertyUtilsBean; +import org.codehaus.jackson.JsonNode; + +/** + * + * @author maig7313 + */ +public class BeanUtils { + + private static final PropertyUtilsBean PUB = new PropertyUtilsBean(); + + /** + * + * @param bean + * @param name + * @return + */ + public static Object getNestedProperty(Object bean, String name) { + try { + return PUB.getNestedProperty(bean, name); + } catch (Exception e) { + return null; + } + } + + /** + * + * @param bean + * @param name + * @param value + */ + public static void setNestedProperty(Object bean, String name, Object value) { + try { + PUB.setNestedProperty(bean, name, value); + } catch (Exception ex) { + } + } + + /** + * + * @param bean + * @param patchBean + * @param node + */ + public static void patch(Object bean, Object patch, JsonNode node) { + String name; + JsonNode child; + Object value; + Object patchValue; + Iterator it = node.getFieldNames(); + while (it.hasNext()) { + name = it.next(); + patchValue = BeanUtils.getNestedProperty(patch, name); + child = node.get(name); + if (child.isObject()) { + value = BeanUtils.getNestedProperty(bean, name); + if (value != null) { + patch(value, patchValue, child); + BeanUtils.setNestedProperty(bean, name, value); + } else { + BeanUtils.setNestedProperty(bean, name, patchValue); + } + } else { + BeanUtils.setNestedProperty(bean, name, patchValue); + } + } + } +} diff --git a/src/main/java/org/tmf/dsmapi/commons/utils/CustomJsonDateSerializer.java.toto b/src/main/java/org/tmf/dsmapi/commons/utils/CustomJsonDateSerializer.java.toto new file mode 100644 index 0000000..6c511e2 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/commons/utils/CustomJsonDateSerializer.java.toto @@ -0,0 +1,33 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.tmf.dsmapi.commons.utils; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.map.JsonSerializer; +import org.codehaus.jackson.map.SerializerProvider; + +/** + * + * @author ecus6396 + */ +public class CustomJsonDateSerializer extends JsonSerializer +{ + @Override + public void serialize(Date aDate, JsonGenerator aJsonGenerator, SerializerProvider aSerializerProvider) + throws IOException, JsonProcessingException { +// +// SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"); +// "2013-07-21 08:16:39ZGMT+1" +// SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss'Z'zzz"); +// dateFormat.setTimeZone(TimeZone.getTimeZone("PST")); + String dateString = dateFormat.format(aDate); + aJsonGenerator.writeString(dateString); + } +} \ No newline at end of file diff --git a/src/main/java/org/tmf/dsmapi/commons/utils/Jackson.java b/src/main/java/org/tmf/dsmapi/commons/utils/Jackson.java new file mode 100644 index 0000000..11af3b9 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/commons/utils/Jackson.java @@ -0,0 +1,158 @@ +package org.tmf.dsmapi.commons.utils; + +import com.sun.jersey.core.util.MultivaluedMapImpl; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.node.ArrayNode; +import org.codehaus.jackson.node.ObjectNode; + +public class Jackson { + + private Jackson() { + } + + public static ObjectNode createNode(Object bean, Set fieldNames) { + ObjectMapper mapper = new ObjectMapper(); + return createNode(mapper, bean, fieldNames); + } + + public static List createNodes(Collection list, Set fieldNames) { + List nodeList = new ArrayList(); + for (Object element : list) { + ObjectNode node = createNode(element, fieldNames); + nodeList.add(node); + } + return nodeList; + } + + public static Object refineBean(Object bean, Set fieldNames) { + ObjectNode rootNode = Jackson.createNode(bean, fieldNames); + Object result = null; + try { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); + mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); + result = mapper.treeToValue(rootNode, bean.getClass()); + } catch (IOException ex) { + Logger.getLogger(Jackson.class.getName()).log(Level.SEVERE, null, ex); + } + return result; + } + + private static ObjectNode createNode(ObjectMapper mapper, Object bean, Set names) { + // split fieldNames in 2 categories : + // simpleFields for simple property names with no '.' + // nestedFields for nested property names with a '.' + Set simpleFields = new HashSet(); + MultivaluedMapImpl nestedFields = new MultivaluedMapImpl(); + for (String name : names) { + int index = name.indexOf('.'); + boolean isNestedField = index > 0 && index < name.length(); + if (isNestedField) { + String rootFieldName = name.substring(0, index); + String subFieldName = name.substring(index + 1); + nestedFields.add(rootFieldName, subFieldName); + } else { + simpleFields.add(name); + } + } + + // create a simple node with only one level containing all simples properties + ObjectNode rootNode = createNodeWithSimpleFields(mapper, bean, simpleFields); + + // create nested nodes with deeper levels + Set>> entrySet = nestedFields.entrySet(); + // for each nested value, create recursively a node + for (Map.Entry> entry : entrySet) { + String rootFieldName = entry.getKey(); + // add in current node only if full value is not already present in 1st level + if (!simpleFields.contains(rootFieldName)) { + Object nestedBean = BeanUtils.getNestedProperty(bean, rootFieldName); + // add only non null fields + if (nestedBean == null) { + break; + } + Set nestedFieldNames = new HashSet(entry.getValue()); + // current node is an array or a list + if ((nestedBean.getClass().isArray()) || (Collection.class.isAssignableFrom(nestedBean.getClass()))) { + Object[] array = null; + if ((nestedBean.getClass().isArray())) { + array = (Object[]) nestedBean; + } else { + Collection collection = (Collection) nestedBean; + array = collection.toArray(); + } + if (array.length > 0) { + // create a node for each element in array + // and add created node in an arrayNode + Collection nodes = new LinkedList(); + for (Object object : array) { + ObjectNode nestedNode = createNode(mapper, object, nestedFieldNames); + if (nestedNode != null && nestedNode.size() > 0) { + nodes.add(nestedNode); + } + } + ArrayNode arrayNode = mapper.createArrayNode(); + arrayNode.addAll(nodes); + if (arrayNode.size() > 0) { + rootNode.put(rootFieldName, arrayNode); + } + } + } else { + // create recursively a node and add it in current root node + ObjectNode nestedNode = createNode(mapper, nestedBean, nestedFieldNames); + if (nestedNode != null && nestedNode.size() > 0) { + rootNode.put(rootFieldName, nestedNode); + } + } + } + } + return rootNode; + } + + // create a simple flat node with only one-level fields + private static ObjectNode createNodeWithSimpleFields(ObjectMapper mapper, Object bean, Set names) { + ObjectNode node = mapper.createObjectNode(); + Object value; + for (String name : names) { + value = BeanUtils.getNestedProperty(bean, name); + if (value != null) { + nodePut(node, name, value); + } + } + return node; + } + + // generic node.put for any Object + private static void nodePut(ObjectNode node, String name, Object value) { + if (value instanceof Boolean) { + node.put(name, (Boolean) value); + } else if (value instanceof Integer) { + node.put(name, (Integer) value); + } else if (value instanceof Long) { + node.put(name, (Long) value); + } else if (value instanceof Float) { + node.put(name, (Float) value); + } else if (value instanceof Double) { + node.put(name, (Double) value); + } else if (value instanceof BigDecimal) { + node.put(name, (BigDecimal) value); + } else if (value instanceof String) { + node.put(name, (String) value); + } else { + node.putPOJO(name, value); + } + } +} diff --git a/src/java/tmf/org/dsmapi/commons/jaxrs/PATCH.java b/src/main/java/org/tmf/dsmapi/commons/utils/PATCH.java similarity index 82% rename from src/java/tmf/org/dsmapi/commons/jaxrs/PATCH.java rename to src/main/java/org/tmf/dsmapi/commons/utils/PATCH.java index bf81b81..abdeaa9 100644 --- a/src/java/tmf/org/dsmapi/commons/jaxrs/PATCH.java +++ b/src/main/java/org/tmf/dsmapi/commons/utils/PATCH.java @@ -2,7 +2,7 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.commons.jaxrs; +package org.tmf.dsmapi.commons.utils; @@ -13,6 +13,10 @@ import javax.ws.rs.HttpMethod; import java.lang.annotation.*; +/** + * + * @author maig7313 + */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @HttpMethod("PATCH") diff --git a/src/main/java/org/tmf/dsmapi/commons/utils/TMFDate.java b/src/main/java/org/tmf/dsmapi/commons/utils/TMFDate.java new file mode 100644 index 0000000..fc29d36 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/commons/utils/TMFDate.java @@ -0,0 +1,64 @@ +package org.tmf.dsmapi.commons.utils; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.map.JsonSerializer; +import org.codehaus.jackson.map.SerializerProvider; + +/** + * + * @author maig7313 + */ +public class TMFDate extends JsonSerializer{ + + private static SimpleDateFormat FORMATER; + private static String label="yyyy-MM-dd'T'HH:mm:ssz"; + + /** + * + * @param date + * @return + */ + public static String toString(Date date) { + return getFormater().format(date); + } + + /** + * + * @param date + * @return + */ + public static Date parse(String value) { + try { + return getFormater().parse(value); + } catch (ParseException ex) { + return null; + } + } + + private static SimpleDateFormat getFormater() { + if (FORMATER == null) { +// FORMATER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"); + FORMATER = new SimpleDateFormat(label); + TimeZone tz = TimeZone.getTimeZone("UTC"); + FORMATER.setTimeZone(tz); + } + return FORMATER; + } + + @Override + public void serialize(Date t, JsonGenerator jg, SerializerProvider sp) + throws IOException, JsonProcessingException { + SimpleDateFormat dateFormat = new SimpleDateFormat(label); +// "2013-07-21 08:16:39ZGMT+1" +// SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss'Z'zzz"); +// dateFormat.setTimeZone(TimeZone.getTimeZone("PST")); + String dateString = dateFormat.format(t); + jg.writeString(dateString); + } +} diff --git a/src/main/java/org/tmf/dsmapi/commons/utils/URIParser.java b/src/main/java/org/tmf/dsmapi/commons/utils/URIParser.java new file mode 100644 index 0000000..7f9749f --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/commons/utils/URIParser.java @@ -0,0 +1,152 @@ +package org.tmf.dsmapi.commons.utils; + +import com.sun.jersey.core.util.MultivaluedMapImpl; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLDecoder; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriInfo; + +/** + * + * @author maig7313 + */ +public class URIParser { + + public static final String ALL_FIELDS = "all"; + public static final String ID_FIELD = "id"; + private static final String QUERY_KEY_FIELD = "fields"; + private static final String QUERY_KEY_FIELD_ESCAPE = ":"; + + private URIParser() { + } + + /** + * + * @param queryParameters + * @return + */ + public static Set getFieldsSelection(MultivaluedMap queryParameters) { + Set fieldSet = new HashSet(); + if (queryParameters != null) { + List queryParameterField = queryParameters.remove(QUERY_KEY_FIELD_ESCAPE + QUERY_KEY_FIELD); + if (queryParameterField == null) { + queryParameterField = queryParameters.remove(QUERY_KEY_FIELD); + } + if (queryParameterField != null && !queryParameterField.isEmpty()) { + String queryParameterValue = queryParameterField.get(0); + if (queryParameterValue != null) { + String[] tokenArray = queryParameterValue.split(","); + fieldSet.addAll(Arrays.asList(tokenArray)); + } + } + } + return fieldSet; + } + + public static MultivaluedMap getParameters(UriInfo info) { + if (info != null) { + URI requestUri = info.getRequestUri(); + if (requestUri != null) { + String rawQuery = requestUri.getRawQuery(); + return getParameters(rawQuery); + } + } + return new MultivaluedMapImpl(); + } + + public static MultivaluedMap getParameters(String rawQuery) { + MultivaluedMap map = new MultivaluedMapImpl(); + //return info.getQueryParameters(); + try { + if (rawQuery == null || rawQuery.length() == 0) { + return map; + } + String query = URLDecoder.decode(rawQuery, "UTF-8"); + if (query == null || query.length() == 0) { + return map; + } + + StringBuffer attribute = new StringBuffer(); + StringBuffer value = new StringBuffer(); + boolean inAttr = true; + boolean inValue = false; + int brackets = 0; + for (int i = 0; i < query.length(); i++) { + char c = query.charAt(i); + switch (c) { + case '&': + if (brackets <= 0) { + brackets = 0; + if (attribute.length() > 0) { + map.putSingle(attribute.toString(), value.toString()); + } + attribute = new StringBuffer(); + value = new StringBuffer(); + inAttr = true; + inValue = false; + } else { + if (inValue) { + value.append(c); + } else if (inAttr) { + attribute.append(c); + } + } + break; + case '=': + if (brackets <= 0) { + brackets = 0; + inValue = true; + inAttr = false; + value = new StringBuffer(); + } else { + if (inValue) { + value.append(c); + } else if (inAttr) { + attribute.append(c); + } + } + break; + case '(': + if (inValue) { + brackets++; + value.append(c); + } else if (inAttr) { + attribute.append(c); + } + break; + case ')': + if (inValue) { + brackets--; + value.append(c); + } else if (inAttr) { + attribute.append(c); + } + break; + + default: + if (inValue) { + value.append(c); + } else if (inAttr) { + attribute.append(c); + } + break; + } + } + if (attribute.length() > 0) { + map.putSingle(attribute.toString(), value.toString()); + } + + } catch (UnsupportedEncodingException ex) { + Logger.getLogger(URIParser.class.getName()).log(Level.SEVERE, null, ex); + } + return map; + + } +} diff --git a/src/main/java/org/tmf/dsmapi/hub/model/Event.java b/src/main/java/org/tmf/dsmapi/hub/model/Event.java new file mode 100644 index 0000000..62f40e9 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/hub/model/Event.java @@ -0,0 +1,90 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.tmf.dsmapi.hub.model; + +import java.io.Serializable; +import java.util.Date; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.xml.bind.annotation.XmlRootElement; +import org.codehaus.jackson.annotate.JsonPropertyOrder; +import org.codehaus.jackson.map.annotate.JsonSerialize; +import org.tmf.dsmapi.commons.utils.TMFDate; +import org.tmf.dsmapi.troubleTicket.model.TroubleTicket; + +/** + * + * @author pierregauthier + */ +@XmlRootElement +@Entity +@JsonPropertyOrder(value = {"event", "reason", "dateEvent", "eventType"}) +@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +@Table(name="EventTroubleTicket") +public class Event implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private String id; + private String reason; + @Temporal(TemporalType.TIMESTAMP) + @JsonSerialize(using = TMFDate.class) + private Date dateEvent; + private TroubleTicket resource; //checl for object + @Enumerated(value = EnumType.STRING) + private EventTypeEnum eventType; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public EventTypeEnum getEventType() { + return eventType; + } + + public void setEventType(EventTypeEnum eventType) { + this.eventType = eventType; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public Date getDate() { + return dateEvent; + } + + public void setDate(Date date) { + this.dateEvent = date; + } + + public TroubleTicket getResource() { + return resource; + } + + public void setResource(TroubleTicket resource) { + this.resource = resource; + } + + @Override + public String toString() { + return "Event{" + "id=" + id + ", eventType=" + eventType + ", reason=" + reason + ", dateEvent=" + dateEvent + ", resource=" + resource + "}"; + } +} diff --git a/src/java/tmf/org/dsmapi/hub/TroubleTicketEventTypeEnum.java b/src/main/java/org/tmf/dsmapi/hub/model/EventTypeEnum.java similarity index 58% rename from src/java/tmf/org/dsmapi/hub/TroubleTicketEventTypeEnum.java rename to src/main/java/org/tmf/dsmapi/hub/model/EventTypeEnum.java index c261dd5..96dcad8 100644 --- a/src/java/tmf/org/dsmapi/hub/TroubleTicketEventTypeEnum.java +++ b/src/main/java/org/tmf/dsmapi/hub/model/EventTypeEnum.java @@ -2,34 +2,58 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.hub; +package org.tmf.dsmapi.hub.model; /** * * @author pierregauthier */ -public enum TroubleTicketEventTypeEnum { +public enum EventTypeEnum { + /** + * + */ TicketCreateNotification("TicketCreateNotification"), + /** + * + */ TicketStatusChangedNotification("TicketStatusChangedNotification"), - TicketChangedNotification("TicketStatusChangedNotification"), + /** + * + */ + TicketChangedNotification("TicketChangedNotification"), + /** + * + */ TicketClearanceRequestNotification("TicketClearanceRequestNotification"), + /** + * + */ InformationRequiredNotification("InformationRequiredNotification"); private String text; - TroubleTicketEventTypeEnum(String text) { + EventTypeEnum(String text) { this.text = text; } + /** + * + * @return + */ public String getText() { return this.text; } - public static tmf.org.dsmapi.hub.TroubleTicketEventTypeEnum fromString(String text) { + /** + * + * @param text + * @return + */ + public static org.tmf.dsmapi.hub.model.EventTypeEnum fromString(String text) { if (text != null) { - for (TroubleTicketEventTypeEnum b : TroubleTicketEventTypeEnum.values()) { + for (EventTypeEnum b : EventTypeEnum.values()) { if (text.equalsIgnoreCase(b.text)) { return b; } diff --git a/src/java/tmf/org/dsmapi/hub/Hub.java b/src/main/java/org/tmf/dsmapi/hub/model/Hub.java similarity index 75% rename from src/java/tmf/org/dsmapi/hub/Hub.java rename to src/main/java/org/tmf/dsmapi/hub/model/Hub.java index ece9845..ef2638b 100644 --- a/src/java/tmf/org/dsmapi/hub/Hub.java +++ b/src/main/java/org/tmf/dsmapi/hub/model/Hub.java @@ -2,14 +2,19 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.hub; +package org.tmf.dsmapi.hub.model; import java.io.Serializable; +import java.util.Date; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; import javax.xml.bind.annotation.XmlRootElement; +import org.codehaus.jackson.map.annotate.JsonSerialize; +import org.tmf.dsmapi.commons.utils.TMFDate; /** * @@ -17,6 +22,8 @@ */ @Entity @XmlRootElement +@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +@Table(name="HubTroubleTicket") public class Hub implements Serializable { private static final long serialVersionUID = 1L; @@ -24,6 +31,12 @@ public class Hub implements Serializable { @GeneratedValue(strategy = GenerationType.AUTO) private String id; + private Integer leaseSeconds; + + @Temporal(javax.persistence.TemporalType.DATE) + @JsonSerialize(using = TMFDate.class) + private Date dateTime; + public String getId() { return id; } @@ -72,6 +85,6 @@ public boolean equals(Object object) { @Override public String toString() { - return "tmf.org.dsmapi.hub.Hub[ id=" + id + " ]"; + return "tmf.org.dsmapi.hub.model.Hub[ id=" + id + " ]"; } } diff --git a/src/main/java/org/tmf/dsmapi/hub/service/EventFacade.java b/src/main/java/org/tmf/dsmapi/hub/service/EventFacade.java new file mode 100644 index 0000000..eb9cdb0 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/hub/service/EventFacade.java @@ -0,0 +1,34 @@ +package org.tmf.dsmapi.hub.service; + +import javax.ejb.Stateless; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import org.tmf.dsmapi.commons.facade.AbstractFacade; +import org.tmf.dsmapi.hub.model.Event; + +/** + * + * @author pierregauthier + */ +@Stateless +public class EventFacade extends AbstractFacade { + + @PersistenceContext(unitName = "DSTroubleTicketPU") + private EntityManager em; + + + + /** + * + */ + public EventFacade() { + super(Event.class); + } + + + @Override + protected EntityManager getEntityManager() { + return em; + } + +} diff --git a/src/main/java/org/tmf/dsmapi/hub/service/EventPublisher.java b/src/main/java/org/tmf/dsmapi/hub/service/EventPublisher.java new file mode 100644 index 0000000..0d786f3 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/hub/service/EventPublisher.java @@ -0,0 +1,105 @@ +package org.tmf.dsmapi.hub.service; + +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.ejb.Asynchronous; +import javax.ejb.EJB; +import javax.ejb.Stateless; +import org.tmf.dsmapi.commons.exceptions.BadUsageException; +import org.tmf.dsmapi.hub.model.Event; +import org.tmf.dsmapi.hub.model.EventTypeEnum; +import org.tmf.dsmapi.hub.model.Hub; +import org.tmf.dsmapi.troubleTicket.model.TroubleTicket; + +/** + * + * @author pierregauthier should be async or called with MDB + */ +@Stateless +@Asynchronous +public class EventPublisher implements EventPublisherLocal { + + @EJB + HubFacade hubFacade; + @EJB + EventFacade eventFacade; + @EJB + RESTEventPublisherLocal restEventPublisherLocal; + + // Add business logic below. (Right-click in editor and choose + // "Insert Code > Add Business Method") + //Access Hubs using callbacks and send to http publisher + //(pool should be configured around the RESTEventPublisher bean) + //Loop into array of Hubs + //Call RestEventPublisher - Need to implement resend policy plus eviction + //Filtering is done in RestEventPublisher based on query expression + @Override + public void publish(Event event) { + try { + eventFacade.create(event); + } catch (BadUsageException ex) { + Logger.getLogger(EventPublisher.class.getName()).log(Level.SEVERE, null, ex); + } + + List hubList = hubFacade.findAll(); + Iterator it = hubList.iterator(); + while (it.hasNext()) { + Hub hub = it.next(); + restEventPublisherLocal.publish(hub, event); + } + } + + @Override + public void createNotification(TroubleTicket bean, String reason, Date date) { + Event event = new Event(); + event.setResource(bean); + event.setDate(date); + event.setReason(reason); + event.setEventType(EventTypeEnum.TicketCreateNotification); + publish(event); + + } + + @Override + public void changedNotification(TroubleTicket bean, String reason, Date date) { + Event event = new Event(); + event.setResource(bean); + event.setDate(date); + event.setReason(reason); + event.setEventType(EventTypeEnum.TicketChangedNotification); + publish(event); + } + + @Override + public void statusChangedNotification(TroubleTicket bean, String reason, Date date) { + Event event = new Event(); + event.setResource(bean); + event.setDate(date); + event.setReason(reason); + event.setEventType(EventTypeEnum.TicketStatusChangedNotification); + publish(event); + } + + @Override + public void clearanceNotification(TroubleTicket bean, String reason, Date date) { + Event event = new Event(); + event.setResource(bean); + event.setDate(date); + event.setReason(reason); + event.setEventType(EventTypeEnum.TicketClearanceRequestNotification); + publish(event); + } + + @Override + public void informationRequiredNotification(TroubleTicket bean, String reason, Date date) { + Event event = new Event(); + event.setResource(bean); + event.setDate(date); + event.setReason(reason); + event.setEventType(EventTypeEnum.InformationRequiredNotification); + publish(event); + } +} diff --git a/src/main/java/org/tmf/dsmapi/hub/service/EventPublisherLocal.java b/src/main/java/org/tmf/dsmapi/hub/service/EventPublisherLocal.java new file mode 100644 index 0000000..f1568b5 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/hub/service/EventPublisherLocal.java @@ -0,0 +1,49 @@ +package org.tmf.dsmapi.hub.service; + +import java.util.Date; +import javax.ejb.Local; +import org.tmf.dsmapi.hub.model.Event; +import org.tmf.dsmapi.troubleTicket.model.TroubleTicket; + + + +/** + * + * @author pierregauthier + */ +@Local +public interface EventPublisherLocal { + + void publish(Event event); + + /** + * + * @param tt + */ + public void createNotification(TroubleTicket bean, String reason, Date date); + + /** + * + * @param tt + */ + public void statusChangedNotification(TroubleTicket bean, String reason, Date date); + + /** + * + * @param tt + */ + public void changedNotification(TroubleTicket bean, String reason, Date date); + + /** + * + * @param tt + */ + public void clearanceNotification(TroubleTicket bean, String reason, Date date); + + /** + * + * @param tt + */ + public void informationRequiredNotification(TroubleTicket bean, String reason, Date date); + +} diff --git a/src/java/tmf/org/dsmapi/hub/service/HubFacade.java b/src/main/java/org/tmf/dsmapi/hub/service/HubFacade.java similarity index 73% rename from src/java/tmf/org/dsmapi/hub/service/HubFacade.java rename to src/main/java/org/tmf/dsmapi/hub/service/HubFacade.java index ed28e80..6c0d931 100644 --- a/src/java/tmf/org/dsmapi/hub/service/HubFacade.java +++ b/src/main/java/org/tmf/dsmapi/hub/service/HubFacade.java @@ -1,13 +1,10 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package tmf.org.dsmapi.hub.service; +package org.tmf.dsmapi.hub.service; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; -import tmf.org.dsmapi.hub.Hub; +import org.tmf.dsmapi.commons.facade.AbstractFacade; +import org.tmf.dsmapi.hub.model.Hub; /** * @@ -18,9 +15,7 @@ public class HubFacade extends AbstractFacade{ @PersistenceContext(unitName = "DSTroubleTicketPU") private EntityManager em; - - /** * */ @@ -28,10 +23,9 @@ public HubFacade() { super(Hub.class); } - @Override protected EntityManager getEntityManager() { return em; } - + } diff --git a/src/main/java/org/tmf/dsmapi/hub/service/RESTClient.java b/src/main/java/org/tmf/dsmapi/hub/service/RESTClient.java new file mode 100644 index 0000000..db7fea8 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/hub/service/RESTClient.java @@ -0,0 +1,58 @@ +package org.tmf.dsmapi.hub.service; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.UniformInterfaceException; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.config.ClientConfig; +import com.sun.jersey.api.client.config.DefaultClientConfig; +import java.util.HashMap; +import javax.ejb.Stateless; +import org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider; +import org.codehaus.jackson.map.DeserializationConfig; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; +import org.codehaus.jackson.node.ObjectNode; + +@Stateless +public class RESTClient { + + private static Client jaxrsClient; + private HashMap webResources = new HashMap(); + + public void publishEvent(String callbackURL, Object requestEntity) throws UniformInterfaceException { + System.out.println("publishEvent " + requestEntity); + WebResource webResource = getWebResource(callbackURL); + webResource.type(javax.ws.rs.core.MediaType.APPLICATION_JSON).post(requestEntity); + //webResource.path("").type(javax.ws.rs.core.MediaType.APPLICATION_JSON).post(requestEntity); + } + + public void publishEvent(String callbackURL, ObjectNode node) throws UniformInterfaceException { + System.out.println("publishEvent " + node); + WebResource webResource = getWebResource(callbackURL); + webResource.type(javax.ws.rs.core.MediaType.APPLICATION_JSON).post(node); + //webResource.path("").type(javax.ws.rs.core.MediaType.APPLICATION_JSON).post(requestEntity); + } + + private Client getJaxrsClient() { + if (jaxrsClient == null) { + ObjectMapper mapper = new ObjectMapper().configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false).configure(SerializationConfig.Feature.INDENT_OUTPUT, true).configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, true); + JacksonJaxbJsonProvider jacksonProvider = new JacksonJaxbJsonProvider(); + jacksonProvider.setMapper(mapper); + ClientConfig config = new DefaultClientConfig(); + config.getSingletons().add(jacksonProvider); + jaxrsClient = Client.create(config); + jaxrsClient.setConnectTimeout(new Integer("3000")); + jaxrsClient.setReadTimeout(new Integer("3000")); + } + return jaxrsClient; + } + + // In memory caching, webResources and client are thread safe see jersey doc + private WebResource getWebResource(String endpointURL) { + if (!webResources.containsKey(endpointURL)) { + WebResource webResource = getJaxrsClient().resource(endpointURL).path(""); + webResources.put(endpointURL, webResource); + } + return webResources.get(endpointURL); + } +} diff --git a/src/main/java/org/tmf/dsmapi/hub/service/RESTEventPublisher.java b/src/main/java/org/tmf/dsmapi/hub/service/RESTEventPublisher.java new file mode 100644 index 0000000..567a012 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/hub/service/RESTEventPublisher.java @@ -0,0 +1,56 @@ +package org.tmf.dsmapi.hub.service; + +import java.util.List; +import java.util.Set; +import javax.ejb.Asynchronous; +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.ws.rs.core.MultivaluedMap; +import org.codehaus.jackson.node.ObjectNode; +import org.tmf.dsmapi.commons.utils.Jackson; +import org.tmf.dsmapi.commons.utils.URIParser; +import org.tmf.dsmapi.hub.model.Event; +import org.tmf.dsmapi.hub.model.Hub; + +/** + * + * @author pierregauthier + */ +@Stateless +@Asynchronous +public class RESTEventPublisher implements RESTEventPublisherLocal { + + @EJB + EventFacade eventFacade; + + @EJB + RESTClient client; + + @Override + public void publish(Hub hub, Event event) { + + MultivaluedMap query = URIParser.getParameters(hub.getQuery()); + query.putSingle("id", event.getId()); + + // fields to filter view + Set fieldSet = URIParser.getFieldsSelection(query); + + List resultList = null; + resultList = eventFacade.findByCriteria(query, Event.class); + + if (resultList != null && !resultList.isEmpty()) { + if (!fieldSet.isEmpty() && !fieldSet.contains(URIParser.ALL_FIELDS)) { + fieldSet.add("id"); + fieldSet.add("date"); + fieldSet.add("eventType"); + fieldSet.add("reason"); + ObjectNode rootNode = Jackson.createNode(event, fieldSet); + client.publishEvent(hub.getCallback(), rootNode); + } else { + client.publishEvent(hub.getCallback(), event); + } + + } + } + +} diff --git a/src/main/java/org/tmf/dsmapi/hub/service/RESTEventPublisherLocal.java b/src/main/java/org/tmf/dsmapi/hub/service/RESTEventPublisherLocal.java new file mode 100644 index 0000000..90033b0 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/hub/service/RESTEventPublisherLocal.java @@ -0,0 +1,16 @@ +package org.tmf.dsmapi.hub.service; + +import javax.ejb.Local; +import org.tmf.dsmapi.hub.model.Event; +import org.tmf.dsmapi.hub.model.Hub; + +/** + * + * @author pierregauthier + */ +@Local +public interface RESTEventPublisherLocal { + + public void publish(Hub hub, Event event); + +} diff --git a/src/main/java/org/tmf/dsmapi/jaxrs/ApplicationConfig.java b/src/main/java/org/tmf/dsmapi/jaxrs/ApplicationConfig.java new file mode 100644 index 0000000..0de8056 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/jaxrs/ApplicationConfig.java @@ -0,0 +1,33 @@ +package org.tmf.dsmapi.jaxrs; + +import java.util.Set; +import javax.ws.rs.core.Application; + +@javax.ws.rs.ApplicationPath("troubleTicketApi") +public class ApplicationConfig extends Application { + + @Override + public Set> getClasses() { + return getRestResourceClasses(); + } + + private Set> getRestResourceClasses() { + Set> resources = new java.util.HashSet>(); + resources.add(org.tmf.dsmapi.commons.jaxrs.provider.BadUsageExceptionMapper.class); + resources.add(org.tmf.dsmapi.commons.jaxrs.provider.JacksonConfigurator.class); + resources.add(org.tmf.dsmapi.commons.jaxrs.provider.JsonMappingExceptionMapper.class); + resources.add(org.tmf.dsmapi.commons.jaxrs.provider.UnknowResourceExceptionMapper.class); + resources.add(org.tmf.dsmapi.jaxrs.resource.AdminResource.class); + resources.add(org.tmf.dsmapi.jaxrs.resource.HubResource.class); + resources.add(org.tmf.dsmapi.jaxrs.resource.TroubleTicketResource.class); + // following code can be used to customize Jersey 1.x JSON provider: + try { + Class jacksonProvider = Class.forName("org.codehaus.jackson.jaxrs.JacksonJsonProvider"); + resources.add(jacksonProvider); + } catch (ClassNotFoundException ex) { + java.util.logging.Logger.getLogger(getClass().getName()).log(java.util.logging.Level.SEVERE, null, ex); + } + return resources; + } + +} diff --git a/src/main/java/org/tmf/dsmapi/jaxrs/resource/AdminResource.java b/src/main/java/org/tmf/dsmapi/jaxrs/resource/AdminResource.java new file mode 100644 index 0000000..e8dfd5a --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/jaxrs/resource/AdminResource.java @@ -0,0 +1,237 @@ +package org.tmf.dsmapi.jaxrs.resource; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import org.tmf.dsmapi.troubleTicket.model.Report; +import org.tmf.dsmapi.commons.exceptions.BadUsageException; +import org.tmf.dsmapi.commons.exceptions.UnknownResourceException; +import org.tmf.dsmapi.hub.model.Event; +import org.tmf.dsmapi.hub.service.EventFacade; +import org.tmf.dsmapi.hub.service.EventPublisherLocal; +import org.tmf.dsmapi.hub.service.HubFacade; +import org.tmf.dsmapi.troubleTicket.model.TroubleTicket; +import org.tmf.dsmapi.troubleTicket.service.TroubleTicketFacade; + +/** + * + * @author maig7313 + */ +@Stateless +@Path("admin") +public class AdminResource { + + @EJB + TroubleTicketFacade troubleTicketFacade; + @EJB + EventFacade eventFacade; + @EJB + HubFacade hubFacade; + @EJB + EventPublisherLocal publisher; + + /** + * + * @param entities + * @return + */ + @POST + @Path("") + @Consumes({"application/json"}) + @Produces({"application/json"}) + public Response post(List entities) { + + if (entities == null) { + return Response.status(Response.Status.BAD_REQUEST.getStatusCode()).build(); + } + + int previousRows = troubleTicketFacade.count(); + int affectedRows; + + // Try to persist entities + try { + affectedRows = troubleTicketFacade.create(entities); + } catch (BadUsageException e) { + return Response.status(Response.Status.BAD_REQUEST.getStatusCode()).build(); + } + + Report stat = new Report(troubleTicketFacade.count()); + stat.setAffectedRows(affectedRows); + stat.setPreviousRows(previousRows); + + // 201 OK + return Response.created(null). + entity(stat). + build(); + } + + /** + * + * @return + * @throws tmf.org.dsmapi.commons.exceptions.UnknownResourceException + */ + @DELETE + @Path("") + public Report deleteAll() throws UnknownResourceException { + + eventFacade.removeAll(); + int previousRows = troubleTicketFacade.count(); + troubleTicketFacade.removeAll(); + List tts = troubleTicketFacade.findAll(); + for (TroubleTicket tt : tts) { + delete(tt.getId()); + } + int currentRows = troubleTicketFacade.count(); + int affectedRows = previousRows - currentRows; + + Report stat = new Report(currentRows); + stat.setAffectedRows(affectedRows); + stat.setPreviousRows(previousRows); + + return stat; + } + + /** + * + * @param id + * @return + * @throws UnknownResourceException + */ + @DELETE + @Path("{id}") +// public Report delete(@PathParam("id") String id) throws UnknownResourceException { + public Response delete(@PathParam("id") String id) { + try { + // int previousRows = troubleTicketFacade.count(); + TroubleTicket entity = troubleTicketFacade.find(id); + + try { + //Pause for 4 seconds to finish notification + Thread.sleep(4000); + } catch (InterruptedException ex) { + Logger.getLogger(AdminResource.class.getName()).log(Level.SEVERE, null, ex); + } + // remove event(s) binding to the resource + List events = eventFacade.findAll(); + for (Event event : events) { + if (event.getResource().getId().equals(id)) { + eventFacade.remove(event.getId()); + } + } + //remove resource + troubleTicketFacade.remove(id); + + // int affectedRows = 1; + // Report stat = new Report(troubleTicketFacade.count()); + // stat.setAffectedRows(affectedRows); + // stat.setPreviousRows(previousRows); + // return stat; + // 200 + Response response = Response.ok(entity).build(); + return response; + } catch (UnknownResourceException ex) { + Logger.getLogger(AdminResource.class.getName()).log(Level.SEVERE, null, ex); + Response response = Response.status(Response.Status.NOT_FOUND).build(); + return response; + } + } + + /** + * + * @return + */ + @DELETE + @Path("hub") + public Report deleteAllHub() { + + int previousRows = hubFacade.count(); + hubFacade.removeAll(); + int currentRows = hubFacade.count(); + int affectedRows = previousRows - currentRows; + + Report stat = new Report(currentRows); + stat.setAffectedRows(affectedRows); + stat.setPreviousRows(previousRows); + + return stat; + } + + @DELETE + @Path("event") + public Report deleteAllEvent() { + + int previousRows = eventFacade.count(); + eventFacade.removeAll(); + int currentRows = eventFacade.count(); + int affectedRows = previousRows - currentRows; + + Report stat = new Report(currentRows); + stat.setAffectedRows(affectedRows); + stat.setPreviousRows(previousRows); + + return stat; + } + + @DELETE + @Path("event/{id}") +// public Report deleteEvent(@PathParam("id") String id) throws UnknownResourceException { + public Response deleteEvent(@PathParam("id") String id) throws UnknownResourceException { + +// int previousRows = eventFacade.count(); + List events = eventFacade.findAll(); + Event oneEvent = null; + for (Event event : events) { + if (event.getResource().getId().equals(id)) { + oneEvent = event; + eventFacade.remove(event.getId()); + + } + } +// int currentRows = eventFacade.count(); +// int affectedRows = previousRows - currentRows; +// +// Report stat = new Report(currentRows); +// stat.setAffectedRows(affectedRows); +// stat.setPreviousRows(previousRows); +// +// return stat; + // 200 + Response response = Response.ok(oneEvent).build(); + return response; + } + + /** + * + * @return + */ + @GET + @Path("count") + @Produces({"application/json"}) + public Report count() { + return new Report(troubleTicketFacade.count()); + } +// /** +// * +// */ +// @DELETE +// @Path("troubleTicket/cache") +// public void clearCache() { +// troubleTicketFacade.clearCache(); +// } +// +// @PUT +// @Path("troubleTicket/wf/delay/{value}") +// public void patchDelay(@PathParam("value") long value) { +// troubleTicketFacade.setDelay(value); +// } +} diff --git a/src/main/java/org/tmf/dsmapi/jaxrs/resource/HubResource.java b/src/main/java/org/tmf/dsmapi/jaxrs/resource/HubResource.java new file mode 100644 index 0000000..13ce996 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/jaxrs/resource/HubResource.java @@ -0,0 +1,177 @@ +package org.tmf.dsmapi.jaxrs.resource; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import org.codehaus.jackson.node.ObjectNode; +import org.tmf.dsmapi.commons.exceptions.BadUsageException; +import org.tmf.dsmapi.commons.exceptions.UnknownResourceException; +import org.tmf.dsmapi.commons.utils.Jackson; +import org.tmf.dsmapi.commons.utils.URIParser; +import org.tmf.dsmapi.hub.model.Event; +import org.tmf.dsmapi.hub.model.EventTypeEnum; +import org.tmf.dsmapi.hub.model.Hub; +import org.tmf.dsmapi.hub.service.EventFacade; +import org.tmf.dsmapi.hub.service.HubFacade; +import org.tmf.dsmapi.troubleTicket.model.TroubleTicket; + +/** + * + * @author pierregauthier + */ +@Stateless +@Path("hub") +public class HubResource { + + @EJB + HubFacade hubFacade; + @EJB + EventFacade eventFacade; + + public HubResource() { + } + + @POST + @Path("") + @Consumes({"application/json"}) + @Produces({"application/json"}) + public Response create(Hub entity) throws BadUsageException { + entity.setId(null); + hubFacade.create(entity); + Response response = Response.ok(entity).build(); + return response; + } + + @DELETE + @Path("{id}") + public void remove(@PathParam("id") String id) throws UnknownResourceException { + hubFacade.remove(id); + } + + @GET + @Path("{id}") + @Produces({"application/json"}) + public Response findById(@PathParam("id") String id, @Context UriInfo info) throws UnknownResourceException { + // fields to filter view + Set fieldSet = URIParser.getFieldsSelection(info.getQueryParameters()); + + Hub entity = hubFacade.find(id); + Response response; + if (entity != null) { + // 200 + if (fieldSet.isEmpty() || fieldSet.contains(URIParser.ALL_FIELDS)) { + response = Response.ok(entity).build(); + } else { + fieldSet.add(URIParser.ID_FIELD); + ObjectNode node = Jackson.createNode(entity, fieldSet); + response = Response.ok(node).build(); + } + } else { + // 404 not found + response = Response.status(Response.Status.NOT_FOUND).build(); + } + return response; + } + + @GET + @Produces({"application/json"}) + public List findAll() { + return hubFacade.findAll(); + } + + @POST + @Path("listener") + @Consumes({"application/json"}) + @Produces({"application/json"}) + public void publishEvent(Event event) { + + System.out.println("HubEvent =" + event); + System.out.println("Event = " + event.getResource()); + System.out.println("Event type = " + event.getEventType()); + + } + + + @GET + @Path("listener") + @Produces({"application/json"}) + public Response findEvents(@Context UriInfo info) { + + // search criteria + MultivaluedMap parameters = URIParser.getParameters(info); + // fields to filter view + Set fieldsSelection = URIParser.getFieldsSelection(parameters); + + Set resultList = findByCriteria(parameters); + + Response response; + if (fieldsSelection.isEmpty() || fieldsSelection.contains(URIParser.ALL_FIELDS)) { + response = Response.ok(resultList).build(); + } else { + fieldsSelection.add(URIParser.ID_FIELD); + List nodeList = Jackson.createNodes(resultList, fieldsSelection); + response = Response.ok(nodeList).build(); + } + return response; + } + + // return Set of unique elements to avoid List with same elements in case of join + private Set findByCriteria(MultivaluedMap criteria) { + List resultList = null; + if (criteria != null && !criteria.isEmpty()) { + resultList = eventFacade.findByCriteria(criteria, Event.class); + } else { + resultList = eventFacade.findAll(); + } + if (resultList == null) { + return new LinkedHashSet(); + } else { + return new LinkedHashSet(resultList); + } + } + + @GET + @Path("proto") + @Produces({"application/json"}) + public Hub hubProto() { + Hub hub = new Hub(); + hub.setCallback("callback"); + hub.setQuery("queryString"); + hub.setId("id"); + return hub; + } + + @GET + @Path("eventProto") + @Produces({"application/json"}) + public Event eventProto() { + Event event = new Event(); + event.setResource(proto()); + event.setEventType(EventTypeEnum.TicketCreateNotification); + System.out.println("Event = " + event.getResource().toString()); + System.out.println("Event type = " + event.getEventType().getText()); + return event; + } + + /** + * + * @return + */ + public TroubleTicket proto() { + TroubleTicket tt = new TroubleTicket(); + return tt; + } +} diff --git a/src/main/java/org/tmf/dsmapi/jaxrs/resource/TroubleTicketResource.java b/src/main/java/org/tmf/dsmapi/jaxrs/resource/TroubleTicketResource.java new file mode 100644 index 0000000..d9a66fd --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/jaxrs/resource/TroubleTicketResource.java @@ -0,0 +1,284 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.tmf.dsmapi.jaxrs.resource; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.ejb.TransactionAttribute; +import javax.ejb.TransactionAttributeType; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.node.ObjectNode; +import org.tmf.dsmapi.commons.exceptions.BadUsageException; +import org.tmf.dsmapi.commons.exceptions.TechnicalException; +import org.tmf.dsmapi.commons.exceptions.UnknownResourceException; +import org.tmf.dsmapi.commons.utils.Jackson; +import org.tmf.dsmapi.commons.utils.PATCH; +import org.tmf.dsmapi.commons.utils.TMFDate; +import org.tmf.dsmapi.commons.utils.URIParser; +import org.tmf.dsmapi.hub.service.EventPublisherLocal; +import org.tmf.dsmapi.troubleTicket.model.Note; +import org.tmf.dsmapi.troubleTicket.model.RelatedObject; +import org.tmf.dsmapi.troubleTicket.model.RelatedParty; +import org.tmf.dsmapi.troubleTicket.model.Severity; +import org.tmf.dsmapi.troubleTicket.model.Status; +import org.tmf.dsmapi.troubleTicket.model.TroubleTicket; +import org.tmf.dsmapi.troubleTicket.model.TroubleTicketField; +import org.tmf.dsmapi.troubleTicket.service.TroubleTicketFacade; + +/** + * + * @author pierregauthier + */ +@Stateless +@Path("troubleTicket") +@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) +public class TroubleTicketResource { + + @EJB + TroubleTicketFacade troubleTicketFacade; + @EJB + EventPublisherLocal publisher; + + public TroubleTicketResource() { + } + + @GET + @Produces({"application/json"}) + public Response findByCriteriaWithFields(@Context UriInfo info) throws BadUsageException { + + // search queryParameters + MultivaluedMap queryParameters = info.getQueryParameters(); + // fields to filter view + Set fieldSet = URIParser.getFieldsSelection(queryParameters); + + Set resultList = findByCriteria(queryParameters); + + Response response; + if (fieldSet.isEmpty() || fieldSet.contains(URIParser.ALL_FIELDS)) { + response = Response.ok(resultList).build(); + } else { + fieldSet.add(URIParser.ID_FIELD); + List nodeList = Jackson.createNodes(resultList, fieldSet); + response = Response.ok(nodeList).build(); + } + return response; + } + + // return Set of unique elements to avoid List with same elements in case of join + private Set findByCriteria(MultivaluedMap criteria) throws BadUsageException { + + List resultList = null; + if (criteria != null && !criteria.isEmpty()) { + resultList = troubleTicketFacade.findByCriteria(criteria, TroubleTicket.class); + } else { + resultList = troubleTicketFacade.findAll(); + } + if (resultList == null) { + return new LinkedHashSet(); + } else { + return new LinkedHashSet(resultList); + } + } + + /** + * + * @param id + * @param info + * @return + * @throws UnknownResourceException + */ + @GET + @Path("{id}") + @Produces({"application/json"}) + public Response findById(@PathParam("id") String id, @Context UriInfo info) throws UnknownResourceException { + + // fields to filter view + Set fieldSet = URIParser.getFieldsSelection(info.getQueryParameters()); + + TroubleTicket troubleTicket = troubleTicketFacade.find(id); + Response response; + if (troubleTicket != null) { + // 200 + if (fieldSet.isEmpty() || fieldSet.contains(URIParser.ALL_FIELDS)) { + response = Response.ok(troubleTicket).build(); + } else { + fieldSet.add(URIParser.ID_FIELD); + ObjectNode node = Jackson.createNode(troubleTicket, fieldSet); + response = Response.ok(node).build(); + } + } else { + // 404 not found + response = Response.status(Response.Status.NOT_FOUND).build(); + } + return response; + } + + /** + * + * @param entity + * @return + * @throws BadUsageException + * @throws TechnicalException + */ + @POST + @Consumes({"application/json"}) + @Produces({"application/json"}) + public Response create(TroubleTicket entity) throws BadUsageException, TechnicalException { + + entity.setStatus(Status.Submitted); + entity.setStatusChangeDate(TMFDate.toString(new Date())); + troubleTicketFacade.create(entity); + publisher.createNotification(entity, "New TroubleTicket", new Date()); + // 201 + Response response = Response.status(Response.Status.CREATED).entity(entity).build(); + return response; + + } + + /** + * + * @param id + * @param entity + * @return + * @throws UnknownResourceException + */ + @PUT + @Path("{id}") + @Consumes({"application/json"}) + @Produces({"application/json"}) + public Response put(@PathParam("id") String id, TroubleTicket entity) throws UnknownResourceException { + + Response response = null; + TroubleTicket troubleTicket = troubleTicketFacade.find(id); + if (troubleTicket != null) { + entity.setId(id); + troubleTicketFacade.edit(entity); + publisher.changedNotification(entity, "TroubleTicket Modified", new Date()); + // 201 OK + location + response = Response.status(Response.Status.CREATED).entity(entity).build(); + + } else { + // 404 not found + response = Response.status(Response.Status.NOT_FOUND).build(); + } + return response; + } + + /** + * + * @param id + * @param jsonNode + * @return + * @throws BadUsageException + * @throws UnknownResourceException + */ + @PATCH + @Path("{id}") + @Consumes({"application/json"}) + @Produces({"application/json"}) + public Response patch(@PathParam("id") String id, JsonNode jsonNode) throws BadUsageException, UnknownResourceException { + + Set fields = new HashSet(); + + // Iterate over first level to set received tokens + Iterator it = jsonNode.getFieldNames(); + while (it.hasNext()) { + fields.add(TroubleTicketField.fromString(it.next())); + } + + ObjectMapper mapper = new ObjectMapper(); + TroubleTicket partialTT; + try { + partialTT = mapper.readValue(jsonNode, TroubleTicket.class); + } catch (Exception ex) { + Logger.getLogger(TroubleTicketResource.class.getName()).log(Level.SEVERE, null, ex); + throw new TechnicalException(); + } + + // id is in URL + partialTT.setId(id); + + // Status is updated when correlationId==nul ( admin or demo purpose only) + TroubleTicket fullTT = troubleTicketFacade.updateAttributes(partialTT, fields); + + // 201 + Response response = Response.status(Response.Status.CREATED).entity(fullTT).build(); + return response; + + } + + /** + * + * @return + */ + @GET + @Path("mock") + @Produces({"application/json"}) + public TroubleTicket mock() { + TroubleTicket tt = new TroubleTicket(); + tt.setId("id"); + Date dt = new Date(); + String dts = TMFDate.toString(dt); + tt.setDescription("Some Description"); + + + tt.setCreationDate(dts); + tt.setStatus(Status.Acknowledged); + tt.setSeverity(Severity.Medium); + tt.setType("Bills, charges or payment"); + tt.setResolutionDate(dts); + tt.setTargetResolutionDate(dts); + + RelatedObject ro = new RelatedObject(); + ro.setInvolvement("involvment"); + ro.setReference("referenceobject"); + + List relatedObjects = new ArrayList(); + relatedObjects.add(ro); + relatedObjects.add(ro); + tt.setRelatedObjects(relatedObjects); + + RelatedParty rp = new RelatedParty(); + rp.setRole("role"); + rp.setReference("reference party"); + + List relatedParties = new ArrayList(); + relatedParties.add(rp); + relatedParties.add(rp); + tt.setRelatedParties(relatedParties); + + Note note = new Note(); + note.setAuthor("author"); + note.setDate(dts); + note.setText("text"); + List notes = new ArrayList(); + notes.add(note); + notes.add(note); + tt.setNotes(notes); + return tt; + + } +} diff --git a/src/main/java/org/tmf/dsmapi/subscriber/model/EventBag.java b/src/main/java/org/tmf/dsmapi/subscriber/model/EventBag.java new file mode 100644 index 0000000..37f86b3 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/subscriber/model/EventBag.java @@ -0,0 +1,51 @@ +package org.tmf.dsmapi.subscriber.model; + +import java.io.Serializable; +import java.util.Date; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.Temporal; +import org.codehaus.jackson.map.annotate.JsonSerialize; +import org.tmf.dsmapi.commons.utils.TMFDate; + +@Entity +public class EventBag implements Serializable { + + private static final long serialVersionUID = 1L; + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private String id; + @Lob + private byte[] event; + @Temporal(javax.persistence.TemporalType.TIMESTAMP) + @JsonSerialize(using = TMFDate.class) + private Date date; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public byte[] getEvent() { + return event; + } + + public void setEvent(byte[] event) { + this.event = event; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + +} diff --git a/src/main/java/org/tmf/dsmapi/subscriber/resource/Current.java b/src/main/java/org/tmf/dsmapi/subscriber/resource/Current.java new file mode 100644 index 0000000..fce535c --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/subscriber/resource/Current.java @@ -0,0 +1,54 @@ +package org.tmf.dsmapi.subscriber.resource; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.List; +import javax.ejb.EJB; +import javax.servlet.ServletOutputStream; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.tmf.dsmapi.subscriber.model.EventBag; +import org.tmf.dsmapi.subscriber.service.EventApiFacade; + +@WebServlet("/subscriber/eventApi/current") +public class Current extends HttpServlet { + + @EJB + EventApiFacade eventApiFacade; + + public Current() { + } + + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException { + + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(byteOut); + + List events = eventApiFacade.findAll(); + ServletOutputStream servletOut = response.getOutputStream(); + EventBag mostRecentNotif = null; + for (int i = 0; events.size() > i; i++) { + mostRecentNotif = events.get(i); + if (events.get(i).getDate().before(mostRecentNotif.getDate())) { + mostRecentNotif = events.get(i); + } + } + if (null != mostRecentNotif) { + servletOut.write(mostRecentNotif.getEvent()); + response.setContentType("application/json"); + response.setStatus(200); + } else { + servletOut.write("no event found".getBytes()); + response.setContentType("application/text"); + response.setStatus(404); + } + out.flush(); + servletOut.close(); + byteOut.close(); + byteOut = null; + } +} diff --git a/src/main/java/org/tmf/dsmapi/subscriber/resource/Event.java b/src/main/java/org/tmf/dsmapi/subscriber/resource/Event.java new file mode 100644 index 0000000..25762e3 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/subscriber/resource/Event.java @@ -0,0 +1,92 @@ +package org.tmf.dsmapi.subscriber.resource; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Date; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.ejb.EJB; +import javax.servlet.ServletOutputStream; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.tmf.dsmapi.commons.exceptions.BadUsageException; +import org.tmf.dsmapi.commons.utils.TMFDate; +import org.tmf.dsmapi.subscriber.model.EventBag; +import org.tmf.dsmapi.subscriber.service.EventApiFacade; + +@WebServlet("/subscriber/eventApi/event") +public class Event extends HttpServlet { + + @EJB + EventApiFacade eventApiFacade; + + private static long index = 0; + + public synchronized String getIndex() { + return Long.toString(index++); + } + + + public Event() { + } + + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws IOException { + EventBag notification = new EventBag(); + response.setContentType("application/json"); + notification.setDate(TMFDate.parse(TMFDate.toString(new Date()))); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + DataInputStream in = new DataInputStream(request.getInputStream()); + byte[] buffer = new byte[256]; + int bytesRead = 0; + while ((bytesRead = in.read(buffer)) != -1) { + byteOut.write(buffer, 0, bytesRead); + } + notification.setEvent(byteOut.toByteArray()); + try { + eventApiFacade.create(notification); + response.setStatus(201); + } catch (BadUsageException ex) { + Logger.getLogger(Event.class.getName()).log(Level.SEVERE, null, ex); + response.setStatus(500); + } + byteOut.close(); + byteOut = null; + System.gc(); + } + + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException { + + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + response.setContentType("application/json"); + List notifications = eventApiFacade.findAll(); + ServletOutputStream servletOut = response.getOutputStream(); + servletOut.write("[".getBytes()); + for (int i = 0; notifications.size() > i; i++) { + servletOut.write(notifications.get(i).getEvent()); + if (i + 1 < notifications.size()) { + servletOut.write(",".getBytes()); + } + } + servletOut.write("]".getBytes()); + response.setStatus(200); + servletOut.close(); + byteOut.close(); + byteOut = null; + System.gc(); + } + + public void doDelete(HttpServletRequest request, HttpServletResponse response) + throws IOException { + List notifications = eventApiFacade.findAll(); + eventApiFacade.removeAll(); + response.setStatus(200); + response.getWriter().println(notifications.size()+" event(s) deleted"); + } + +} diff --git a/src/main/java/org/tmf/dsmapi/subscriber/resource/History.java b/src/main/java/org/tmf/dsmapi/subscriber/resource/History.java new file mode 100644 index 0000000..fdaa048 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/subscriber/resource/History.java @@ -0,0 +1,54 @@ +package org.tmf.dsmapi.subscriber.resource; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; +import javax.ejb.EJB; +import javax.servlet.ServletOutputStream; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.tmf.dsmapi.subscriber.model.EventBag; +import org.tmf.dsmapi.subscriber.service.EventApiFacade; + +@WebServlet("/subscriber/eventApi/history") +public class History extends HttpServlet { + + @EJB + EventApiFacade eventApiFacade; + + public History() { + } + + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException { + + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + response.setContentType("application/json"); + List notifications = eventApiFacade.findAll(); + ServletOutputStream servletOut = response.getOutputStream(); + servletOut.write("[".getBytes()); + for (int i = 0; notifications.size() > i; i++) { + servletOut.write(notifications.get(i).getEvent()); + if (i + 1 < notifications.size()) { + servletOut.write(",".getBytes()); + } + } + servletOut.write("]".getBytes()); + response.setStatus(200); + servletOut.close(); + byteOut.close(); + byteOut = null; + System.gc(); + } + + public void doDelete(HttpServletRequest request, HttpServletResponse response) + throws IOException { + List notifications = eventApiFacade.findAll(); + eventApiFacade.removeAll(); + response.setStatus(200); + response.getWriter().println(notifications.size()+" event(s) deleted"); + } + +} diff --git a/src/main/java/org/tmf/dsmapi/subscriber/service/EventApiFacade.java b/src/main/java/org/tmf/dsmapi/subscriber/service/EventApiFacade.java new file mode 100644 index 0000000..a584433 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/subscriber/service/EventApiFacade.java @@ -0,0 +1,28 @@ +package org.tmf.dsmapi.subscriber.service; + +import javax.ejb.Stateless; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import org.tmf.dsmapi.commons.facade.AbstractFacade; +import org.tmf.dsmapi.subscriber.model.EventBag; + +/** + * + * @author ecus6396 + */ +@Stateless +public class EventApiFacade extends AbstractFacade { + + @PersistenceContext(unitName = "DSTroubleTicketPU") + private EntityManager em; + + public EventApiFacade() { + super(EventBag.class); + } + + @Override + protected EntityManager getEntityManager() { + return em; + } + +} diff --git a/src/main/java/org/tmf/dsmapi/troubleTicket/model/IncidentType.java b/src/main/java/org/tmf/dsmapi/troubleTicket/model/IncidentType.java new file mode 100644 index 0000000..a341abd --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/troubleTicket/model/IncidentType.java @@ -0,0 +1,36 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.tmf.dsmapi.troubleTicket.model; + +/** + * none that i know + * @author pierregauthier ATMFORUM + */ +public enum IncidentType { + /** + * + */ + Submitted, + /** + * + */ + Acknowledged, + /** + * + */ + InProgress, + /** + * + */ + Resolved, + /** + * + */ + Closed, + /** + * + */ + Rejected; //; is optional +} diff --git a/src/main/java/org/tmf/dsmapi/troubleTicket/model/JsonError.java b/src/main/java/org/tmf/dsmapi/troubleTicket/model/JsonError.java new file mode 100644 index 0000000..d33922a --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/troubleTicket/model/JsonError.java @@ -0,0 +1,74 @@ +package org.tmf.dsmapi.troubleTicket.model; + +import org.tmf.dsmapi.commons.exceptions.KeyValue; +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlRootElement; +import org.codehaus.jackson.map.annotate.JsonSerialize; +import org.tmf.dsmapi.commons.exceptions.ExceptionBean; + +@XmlRootElement +@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +public class JsonError { + + private ExceptionBean error; + private String detail; + private List keyValue; + + public JsonError(ExceptionBean error) { + this.error = error; + } + + public JsonError(ExceptionBean error, String detail) { + this.error = error; + this.detail = detail; + } + + public JsonError(ExceptionBean error, String detail, List keyValue) { + this.error = error; + this.detail = detail; + this.keyValue = keyValue; + } + + /** + * @return the type + */ + public ExceptionBean getError() { + return error; + } + + /** + * @param type the type to set + */ + public void setError(ExceptionBean error) { + this.error = error; + } + + /** + * @return the detail + */ + public String getDetail() { + return detail; + } + + /** + * @param detail the detail to set + */ + public void setDetail(String detail) { + this.detail = detail; + } + + /** + * @return the keyValue + */ + public List getKeyValue() { + return keyValue; + } + + public void addKeyValue(KeyValue keyValue) { + if (this.keyValue == null) { + this.keyValue = new ArrayList(); + } + this.keyValue.add(keyValue); + } +} diff --git a/src/java/tmf/org/dsmapi/tt/Note.java b/src/main/java/org/tmf/dsmapi/troubleTicket/model/Note.java similarity index 78% rename from src/java/tmf/org/dsmapi/tt/Note.java rename to src/main/java/org/tmf/dsmapi/troubleTicket/model/Note.java index 0320a08..b42bf8f 100644 --- a/src/java/tmf/org/dsmapi/tt/Note.java +++ b/src/main/java/org/tmf/dsmapi/troubleTicket/model/Note.java @@ -2,7 +2,7 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.tt; +package org.tmf.dsmapi.troubleTicket.model; import java.io.Serializable; import javax.persistence.Embeddable; @@ -22,26 +22,50 @@ public class Note implements Serializable { private String author; private String text; + /** + * + * @return + */ public String getDate() { return date; } + /** + * + * @param date + */ public void setDate(String date) { this.date = date; } + /** + * + * @return + */ public String getAuthor() { return author; } + /** + * + * @param author + */ public void setAuthor(String author) { this.author = author; } + /** + * + * @return + */ public String getText() { return text; } + /** + * + * @param text + */ public void setText(String text) { this.text = text; } diff --git a/src/java/tmf/org/dsmapi/tt/RelatedObject.java b/src/main/java/org/tmf/dsmapi/troubleTicket/model/RelatedObject.java similarity index 82% rename from src/java/tmf/org/dsmapi/tt/RelatedObject.java rename to src/main/java/org/tmf/dsmapi/troubleTicket/model/RelatedObject.java index 0064553..120110c 100644 --- a/src/java/tmf/org/dsmapi/tt/RelatedObject.java +++ b/src/main/java/org/tmf/dsmapi/troubleTicket/model/RelatedObject.java @@ -2,7 +2,7 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.tt; +package org.tmf.dsmapi.troubleTicket.model; import java.io.Serializable; import javax.persistence.Embeddable; @@ -29,18 +29,34 @@ public class RelatedObject implements Serializable { private String reference; private String involvement; + /** + * + * @return + */ public String getInvolvement() { return involvement; } + /** + * + * @param involvement + */ public void setInvolvement(String involvement) { this.involvement = involvement; } + /** + * + * @return + */ public String getReference() { return reference; } + /** + * + * @param reference + */ public void setReference(String reference) { this.reference = reference; } diff --git a/src/java/tmf/org/dsmapi/tt/RelatedParty.java b/src/main/java/org/tmf/dsmapi/troubleTicket/model/RelatedParty.java similarity index 83% rename from src/java/tmf/org/dsmapi/tt/RelatedParty.java rename to src/main/java/org/tmf/dsmapi/troubleTicket/model/RelatedParty.java index 5583582..616f074 100644 --- a/src/java/tmf/org/dsmapi/tt/RelatedParty.java +++ b/src/main/java/org/tmf/dsmapi/troubleTicket/model/RelatedParty.java @@ -2,7 +2,7 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.tt; +package org.tmf.dsmapi.troubleTicket.model; import java.io.Serializable; import javax.persistence.Embeddable; @@ -32,18 +32,34 @@ public class RelatedParty implements Serializable { private String role; private String reference; + /** + * + * @return + */ public String getRole() { return role; } + /** + * + * @param role + */ public void setRole(String role) { this.role = role; } + /** + * + * @return + */ public String getReference() { return reference; } + /** + * + * @param reference + */ public void setReference(String reference) { this.reference = reference; } diff --git a/src/main/java/org/tmf/dsmapi/troubleTicket/model/Report.java b/src/main/java/org/tmf/dsmapi/troubleTicket/model/Report.java new file mode 100644 index 0000000..c0d96f1 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/troubleTicket/model/Report.java @@ -0,0 +1,70 @@ +package org.tmf.dsmapi.troubleTicket.model; + +import javax.xml.bind.annotation.XmlRootElement; +import org.codehaus.jackson.map.annotate.JsonSerialize; + +@XmlRootElement +@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) +public class Report { + + private String previousRows; + private String currentRows; + private String affectedRows; + + /** + * + */ + public Report () { + } + + /** + * + * @param currentRows + */ + public Report (int currentRows) { + this.currentRows=String.valueOf(currentRows); + } + + /** + * @return the currentRows + */ + public String getCurrentRows() { + return currentRows; + } + + /** + * @param currentRows the currentRows to set + */ + public void setCurrentRows(int currentRows) { + this.currentRows = String.valueOf(currentRows); + } + + /** + * @return the previousRows + */ + public String getPreviousRows() { + return previousRows; + } + + /** + * @param previousRows the previousRows to set + */ + public void setPreviousRows(int previousRows) { + this.previousRows = String.valueOf(previousRows); + } + + /** + * @return the affectedRows + */ + public String getAffectedRows() { + return affectedRows; + } + + /** + * @param affectedRows the affectedRows to set + */ + public void setAffectedRows(int affectedRows) { + this.affectedRows = String.valueOf(affectedRows); + } + +} diff --git a/src/java/tmf/org/dsmapi/tt/Severity.java b/src/main/java/org/tmf/dsmapi/troubleTicket/model/Severity.java similarity index 72% rename from src/java/tmf/org/dsmapi/tt/Severity.java rename to src/main/java/org/tmf/dsmapi/troubleTicket/model/Severity.java index a1c41e5..bfa0271 100644 --- a/src/java/tmf/org/dsmapi/tt/Severity.java +++ b/src/main/java/org/tmf/dsmapi/troubleTicket/model/Severity.java @@ -2,7 +2,7 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.tt; +package org.tmf.dsmapi.troubleTicket.model; /** * @@ -10,11 +10,28 @@ */ public enum Severity { + /** + * + */ Low, + /** + * + */ Critical, + /** + * + */ Medium, + /** + * + */ High; //; is optional + /** + * + * @param text + * @return + */ public static Severity fromString(String text) { if (text != null) { for (Severity b : Severity.values()) { diff --git a/src/java/tmf/org/dsmapi/tt/Status.java b/src/main/java/org/tmf/dsmapi/troubleTicket/model/Status.java similarity index 65% rename from src/java/tmf/org/dsmapi/tt/Status.java rename to src/main/java/org/tmf/dsmapi/troubleTicket/model/Status.java index 7b34e79..5974055 100644 --- a/src/java/tmf/org/dsmapi/tt/Status.java +++ b/src/main/java/org/tmf/dsmapi/troubleTicket/model/Status.java @@ -2,7 +2,7 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.tt; +package org.tmf.dsmapi.troubleTicket.model; /** * @@ -10,15 +10,48 @@ */ public enum Status { + /** + * + */ Submitted, + /** + * + */ Acknowledged, + /** + * + */ + InProgress, + /** + * + */ InProgress_Held, + /** + * + */ InProgress_Pending, + /** + * + */ Resolved, + /** + * + */ Closed, + /** + * + */ Rejected, + /** + * + */ Cancelled; //; is optional + /** + * + * @param text + * @return + */ public static Status fromString(String text) { if (text != null) { for (Status b : Status.values()) { diff --git a/src/java/tmf/org/dsmapi/tt/SubStatus.java b/src/main/java/org/tmf/dsmapi/troubleTicket/model/SubStatus.java similarity index 55% rename from src/java/tmf/org/dsmapi/tt/SubStatus.java rename to src/main/java/org/tmf/dsmapi/troubleTicket/model/SubStatus.java index 22feef1..b6b46e0 100644 --- a/src/java/tmf/org/dsmapi/tt/SubStatus.java +++ b/src/main/java/org/tmf/dsmapi/troubleTicket/model/SubStatus.java @@ -2,12 +2,19 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.tt; +package org.tmf.dsmapi.troubleTicket.model; /** * * @author pierregauthier */ public enum SubStatus { - Pending, Held; //; is optional + /** + * + */ + Pending, + /** + * + */ + Held; //; is optional } diff --git a/src/java/tmf/org/dsmapi/tt/TroubleTicket.java b/src/main/java/org/tmf/dsmapi/troubleTicket/model/TroubleTicket.java similarity index 77% rename from src/java/tmf/org/dsmapi/tt/TroubleTicket.java rename to src/main/java/org/tmf/dsmapi/troubleTicket/model/TroubleTicket.java index b63acd0..09d429a 100644 --- a/src/java/tmf/org/dsmapi/tt/TroubleTicket.java +++ b/src/main/java/org/tmf/dsmapi/troubleTicket/model/TroubleTicket.java @@ -2,11 +2,11 @@ * To change this template, choose Tools | Templates * and open the template in the editor. */ -package tmf.org.dsmapi.tt; +package org.tmf.dsmapi.troubleTicket.model; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; -import java.util.Set; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; @@ -15,9 +15,7 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; -import javax.persistence.Transient; import javax.xml.bind.annotation.XmlRootElement; -import org.codehaus.jackson.annotate.JsonIgnore; import org.codehaus.jackson.map.annotate.JsonSerialize; /** @@ -45,24 +43,28 @@ public class TroubleTicket implements Serializable { //Add other static strings as required.... private static final long serialVersionUID = 1L; - // Used for incremental update - @Transient - @JsonIgnore - private Set fieldsIN; @Id - @Column(name="TT_ID") + @Column(name = "TT_ID") @GeneratedValue(strategy = GenerationType.AUTO) private String id; + /** + * + * @return + */ public String getId() { return id; } + /** + * + * @param id + */ public void setId(String id) { this.id = id; } private String correlationId; - private String description; + private String description; private Severity severity; private String type; private String creationDate; @@ -72,21 +74,18 @@ public void setId(String id) { private String statusChangeReason; private String statusChangeDate; private String resolutionDate; - @ElementCollection @CollectionTable( name = "RELATED_OBJECT", joinColumns = @JoinColumn(name = "OWNER_ID")) private List relatedObjects; - @ElementCollection @CollectionTable( name = "NOTES", joinColumns = @JoinColumn(name = "OWNER_ID")) private List notes; - @ElementCollection @CollectionTable( name = "RELATED_PARTY", @@ -94,117 +93,237 @@ public void setId(String id) { @JoinColumn(name = "OWNER_ID")) private List relatedParties; + /** + * + * @return + */ public String getCorrelationId() { return correlationId; } + /** + * + * @param correlationId + */ public void setCorrelationId(String correlationId) { this.correlationId = correlationId; } + /** + * + * @return + */ public String getDescription() { return description; } + /** + * + * @param description + */ public void setDescription(String description) { this.description = description; } + /** + * + * @return + */ public Severity getSeverity() { return severity; } + /** + * + * @param severity + */ public void setSeverity(Severity severity) { this.severity = severity; } + /** + * + * @return + */ public String getType() { return type; } + /** + * + * @param type + */ public void setType(String type) { this.type = type; } + /** + * + * @return + */ public String getCreationDate() { return creationDate; } + /** + * + * @param creationDate + */ public void setCreationDate(String creationDate) { this.creationDate = creationDate; } + /** + * + * @return + */ public String getTargetResolutionDate() { return targetResolutionDate; } + /** + * + * @param targetResolutionDate + */ public void setTargetResolutionDate(String targetResolutionDate) { this.targetResolutionDate = targetResolutionDate; } + /** + * + * @return + */ public Status getStatus() { return status; } + /** + * + * @param status + */ public void setStatus(Status status) { this.status = status; } + /** + * + * @return + */ public SubStatus getSubStatus() { return subStatus; } + /** + * + * @param subStatus + */ public void setSubStatus(SubStatus subStatus) { this.subStatus = subStatus; } + /** + * + * @return + */ public String getStatusChangeReason() { return statusChangeReason; } + /** + * + * @param statusChangeReason + */ public void setStatusChangeReason(String statusChangeReason) { this.statusChangeReason = statusChangeReason; } + /** + * + * @return + */ public String getStatusChangeDate() { return statusChangeDate; } + /** + * + * @param statusChangeDate + */ public void setStatusChangeDate(String statusChangeDate) { this.statusChangeDate = statusChangeDate; } + /** + * + * @return + */ public String getResolutionDate() { return resolutionDate; } + /** + * + * @param resolutionDate + */ public void setResolutionDate(String resolutionDate) { this.resolutionDate = resolutionDate; } + /** + * + * @return + */ public List getRelatedObjects() { - if (relatedObjects.isEmpty()) return null; - else return relatedObjects; + if (relatedObjects == null || relatedObjects.isEmpty()) { + return null; + } else { + return relatedObjects; + } } + /** + * + * @param relatedObjects + */ public void setRelatedObjects(List relatedObjects) { this.relatedObjects = relatedObjects; } + /** + * + * @return + */ public List getNotes() { - if (notes.isEmpty()) return null; - else return notes; + if (notes == null) { + notes = new ArrayList(); + } + return notes; } + /** + * + * @param notes + */ public void setNotes(List notes) { this.notes = notes; } + /** + * + * @return + */ public List getRelatedParties() { - if (relatedParties.isEmpty()) return null; - else return relatedParties; + if (relatedParties == null || relatedParties.isEmpty()) { + return null; + } else { + return relatedParties; + } } + /** + * + * @param relatedParties + */ public void setRelatedParties(List relatedParties) { this.relatedParties = relatedParties; } @@ -235,17 +354,4 @@ public String toString() { return "tmf.org.dsmtt.TroubleTicket[ id=" + id + " ]"; } - /** - * @return the fieldsIN - */ - public Set getFieldsIN() { - return fieldsIN; - } - - /** - * @param fieldsIN the fieldsIN to set - */ - public void setFieldsIN(Set fields) { - this.fieldsIN = fields; - } } diff --git a/src/java/tmf/org/dsmapi/tt/TroubleTicketField.java b/src/main/java/org/tmf/dsmapi/troubleTicket/model/TroubleTicketField.java similarity index 75% rename from src/java/tmf/org/dsmapi/tt/TroubleTicketField.java rename to src/main/java/org/tmf/dsmapi/troubleTicket/model/TroubleTicketField.java index a7592e8..f9ae95b 100644 --- a/src/java/tmf/org/dsmapi/tt/TroubleTicketField.java +++ b/src/main/java/org/tmf/dsmapi/troubleTicket/model/TroubleTicketField.java @@ -1,25 +1,77 @@ -package tmf.org.dsmapi.tt; +package org.tmf.dsmapi.troubleTicket.model; import java.util.HashSet; import java.util.Set; +/** + * + * @author maig7313 + */ public enum TroubleTicketField { + /** + * + */ ALL("all"), + /** + * + */ ID("id"), + /** + * + */ CORRELATION_ID("correlationId"), + /** + * + */ CREATION_DATE("creationDate"), + /** + * + */ DESCRIPTION("description"), + /** + * + */ NOTES("notes"), + /** + * + */ RELATED_OBJECTS("relatedObjects"), + /** + * + */ RELATED_PARTIES("relatedParties"), + /** + * + */ RESOLUTION_DATE("resolutionDate"), + /** + * + */ SEVERITY("severity"), + /** + * + */ STATUS("status"), + /** + * + */ STATUS_CHANGE_DATE("statusChangeDate"), + /** + * + */ STATUS_CHANGE_REASON("statusChangeReason"), + /** + * + */ SUB_STATUS("subStatus"), + /** + * + */ TARGET_RESOLUTION_DATE("targetResolutionDate"), + /** + * + */ TYPE("type"); private String text; @@ -27,10 +79,19 @@ public enum TroubleTicketField { this.text = text; } + /** + * + * @return + */ public String getText() { return this.text; } + /** + * + * @param text + * @return + */ public static TroubleTicketField fromString(String text) { if (text != null) { for (TroubleTicketField b : TroubleTicketField.values()) { @@ -42,6 +103,11 @@ public static TroubleTicketField fromString(String text) { return null; } + /** + * + * @param fields + * @return + */ public static Set fromStringToSet(String fields) { // Convert fields parameter to a set of TroubleTicketField Set fieldsSet = new HashSet(); diff --git a/src/main/java/org/tmf/dsmapi/troubleTicket/service/TroubleTicketFacade.java b/src/main/java/org/tmf/dsmapi/troubleTicket/service/TroubleTicketFacade.java new file mode 100644 index 0000000..c83cf82 --- /dev/null +++ b/src/main/java/org/tmf/dsmapi/troubleTicket/service/TroubleTicketFacade.java @@ -0,0 +1,219 @@ +package org.tmf.dsmapi.troubleTicket.service; + +import java.util.Date; +import java.util.List; +import java.util.Set; +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.ws.rs.core.MultivaluedMap; +import org.tmf.dsmapi.commons.exceptions.BadUsageException; +import org.tmf.dsmapi.commons.exceptions.ExceptionType; +import org.tmf.dsmapi.commons.exceptions.UnknownResourceException; +import org.tmf.dsmapi.commons.facade.AbstractFacade; +import org.tmf.dsmapi.commons.utils.TMFDate; +import org.tmf.dsmapi.hub.service.EventPublisherLocal; +import org.tmf.dsmapi.troubleTicket.model.Status; +import org.tmf.dsmapi.troubleTicket.model.TroubleTicket; +import org.tmf.dsmapi.troubleTicket.model.TroubleTicketField; +import static org.tmf.dsmapi.troubleTicket.model.TroubleTicketField.STATUS; +import static org.tmf.dsmapi.troubleTicket.model.TroubleTicketField.STATUS_CHANGE_REASON; + +/** + * + * @author maig7313 + * + */ +@Stateless +public class TroubleTicketFacade extends AbstractFacade { + + @PersistenceContext(unitName = "DSTroubleTicketPU") + private EntityManager em; + private static long delay = 3000; + + @EJB + EventPublisherLocal publisher; + + /** + * + */ + public TroubleTicketFacade() { + super(TroubleTicket.class); + } + + /** + * + * @param tt + * @return + * @throws UnknownResourceException + */ + @Override + public TroubleTicket edit(TroubleTicket tt) throws UnknownResourceException { + TroubleTicket targetEntity = this.find(tt.getId()); + if (targetEntity == null) { + throw new UnknownResourceException(ExceptionType.UNKNOWN_RESOURCE); + } + tt = super.edit(tt); + return tt; + } + + /** + * + * @param patchTT + * @param fields + * @return + * @throws BadUsageException + * @throws UnknownResourceException + */ + public TroubleTicket updateAttributes(TroubleTicket patchTT, Set fields) throws BadUsageException, UnknownResourceException { + + TroubleTicket currentTT = this.find(patchTT.getId()); + + if (currentTT == null) { + throw new UnknownResourceException(ExceptionType.UNKNOWN_RESOURCE); + } + + if (fields.contains(STATUS) & !(fields.contains(STATUS_CHANGE_REASON))) { + throw new BadUsageException(ExceptionType.BAD_USAGE_MANDATORY_FIELDS, "While updating 'status', please provide a 'statusChangeReason'"); + } + + // Allow status update when there is no correlationId, for demo or admin purpose + if (fields.contains(STATUS) && (patchTT.getCorrelationId() == null)) { + // isValidTransition if this transition is allowed + currentTT.setStatus(patchTT.getStatus()); + currentTT.setStatusChangeDate(TMFDate.toString(new Date())); + currentTT.setStatusChangeReason(patchTT.getStatusChangeReason()); + // notify Status change + publisher.changedNotification(currentTT, null, new Date()); + } + + for (TroubleTicketField token : fields) { + switch (token) { + case CREATION_DATE: + currentTT.setCreationDate(patchTT.getCreationDate()); + break; + case DESCRIPTION: + if (patchTT.getDescription() != null) { + currentTT.setDescription(patchTT.getDescription()); + } + break; + case NOTES: + currentTT.setNotes(patchTT.getNotes()); // Replace Notes + break; + case RELATED_OBJECTS: + currentTT.setRelatedObjects(patchTT.getRelatedObjects()); + break; + case RELATED_PARTIES: + currentTT.setRelatedParties(patchTT.getRelatedParties()); + break; + case RESOLUTION_DATE: + currentTT.setResolutionDate(patchTT.getResolutionDate()); + break; + case SEVERITY: + if (patchTT.getSeverity() != null) { + currentTT.setSeverity(patchTT.getSeverity()); + } + break; + case TARGET_RESOLUTION_DATE: + currentTT.setResolutionDate(patchTT.getResolutionDate()); + break; + case TYPE: + if (patchTT.getType() != null) { + currentTT.setType(patchTT.getType()); + } + break; + } + } + em.merge(currentTT); + + // Event tt changed + publisher.changedNotification(currentTT, null, new Date()); + return currentTT; + } + + /** + * + * @param troubleTicket + * @param status + * @param reason + * @return + */ + public TroubleTicket updateStatus(TroubleTicket troubleTicket, Status status, String reason) { + troubleTicket.setStatus(status); + troubleTicket.setStatusChangeDate(TMFDate.toString(new Date())); + troubleTicket.setStatusChangeReason(reason); + em.merge(troubleTicket); + return troubleTicket; + } + + /** + * + * @param tt + * @throws BadUsageException + */ + @Override + public void create(TroubleTicket tt) throws BadUsageException { + + if (tt.getId() != null) { + throw new BadUsageException(ExceptionType.BAD_USAGE_GENERIC, "While creating a TT id should be null"); + } + + if ((tt.getDescription() == null) || (tt.getSeverity() == null) || (tt.getType() == null)) { + String fieldName = null; + if (tt.getDescription() == null) { + fieldName = TroubleTicketField.DESCRIPTION.getText(); + } else { + if (tt.getSeverity() == null) { + fieldName = TroubleTicketField.SEVERITY.getText(); + } else if (tt.getType() == null) { + fieldName = TroubleTicketField.TYPE.getText(); + } + } + throw new BadUsageException(ExceptionType.BAD_USAGE_MANDATORY_FIELDS, fieldName); + } + + super.create(tt); + + } + + /** + * + * @return + */ + @Override + protected EntityManager getEntityManager() { + return em; + } + + /** + * + * @param queryParameters + * @return + */ + public List find(MultivaluedMap queryParameters) { + + List tickets; + if (queryParameters != null && !queryParameters.isEmpty()) { + tickets = findByCriteria(queryParameters, TroubleTicket.class); + } else { + tickets = this.findAll(); + } + return tickets; + + } + + /** + * @return the delay + */ + public long getDelay() { + return delay; + } + + /** + * @param delay the delay to set + */ + public void setDelay(long delay) { + this.delay = delay; + } +} diff --git a/src/conf/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF similarity index 100% rename from src/conf/MANIFEST.MF rename to src/main/resources/META-INF/MANIFEST.MF diff --git a/src/conf/persistence.xml b/src/main/resources/META-INF/persistence.xml similarity index 57% rename from src/conf/persistence.xml rename to src/main/resources/META-INF/persistence.xml index c20fde9..ef057fb 100644 --- a/src/conf/persistence.xml +++ b/src/main/resources/META-INF/persistence.xml @@ -1,13 +1,12 @@ - - + + org.eclipse.persistence.jpa.PersistenceProvider jdbc/sample false - - + + - diff --git a/src/main/webapp/WEB-INF/glassfish-web.xml b/src/main/webapp/WEB-INF/glassfish-web.xml new file mode 100644 index 0000000..c915f06 --- /dev/null +++ b/src/main/webapp/WEB-INF/glassfish-web.xml @@ -0,0 +1,21 @@ + + + + /DSTroubleTicket + + USERS + USERS + USERS + + + ADMIN + ADMIN + ADMIN + + + + + Keep a copy of the generated servlet class' java code. + + + diff --git a/web/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml similarity index 52% rename from web/WEB-INF/web.xml rename to src/main/webapp/WEB-INF/web.xml index c57c558..f90a888 100644 --- a/web/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -3,19 +3,19 @@ xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> - @@ -23,24 +23,7 @@ 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

FP12{yowCyvZ&4d6CAfX>rv9=Uk*z1n7<$w` z_p*5zyObn{Ow{a$D`brqM)JIkfjr^KX@_Eo;76$o5DV-_DVc~)| z{z87<=k!{-4JO>uHe2C}luRd2VPd9HRLY*kzdsM z2Y}~-&1|cqqsUMz6(b^XTa#l@2k68VVrog#Qp|`w^@`**wr=ls!ue7UgajjB`_3sR%v5j)ZpjE?!az&$EfoA@Y)_t0$v=AX>SOeID&rdIp#>9;4zEF zf&utslSFui!;nfCOa)k&Fjr?ioQ@7)>v356AnoJ71PrdOoBDGu=)-APTO4kiua~>i zlL@hOdmj-(GsOQ8%7T=k$4TXRLw&RJHV2;EeICy*BVGxiK4@E)QAq4IM2BD04SjU) zY!bM*V#d7YobomCFsOLwu01$cZgB~7V}Z*}K_UHGz+iil7)Bi7 z;pYJk8u2Q6sq}bl#wXf>E1H;z?+4Nj?QK0;%P+@AeT6~_;vC(>xQ|rk$>d*+R+bfN z;6;cQJ5lp5X0x=Y8&|HFF~7z&;RZ*EgX$$@-Z@8FLy^op=j7+}@L{C}qtAKBuZE%r zR2)g9)`6u_d;M=jaa%861B(CH^o*xH$@CvkyxhXsYxhnINuKGgu_>z+N-=Zn_OEJc zE!x(!LR-E$8+83ayx((@R>$CEFHJrNK;?CGXQO$Af%Llwi$Wd5R{8(4$y!W>97WGjR%Y&$Lx%tA#r{`OY z5=Ss<(1F=yKBPEO%-G7%sL!rX`OfYM8q;^lu`td*Ti+H)_!6|iFx7`fo*o(s@IRWT zGLm$@7qJnTzVAFYk5PP*a?z@1FA+sJr__I~S8tZ=;w@USc@vuXIU80HZ4v0Xz;*4c zM)yA;mfP_)tMcBLLvOqh#pOGd&J1SrPCV@Z3B_eimVMecJveNyE{Bb&b1J<^jqdB< z?)3|n_Kuep4|@FWGr6<`*EreZU^$$o?*9Qt9GoG}30T?d_2xYFr1DJf*k~ri$QG*| zb0b;^cs>vlCJ+Z(1sf#QZGt+FDL#5N{Lw47qB8m^Furn>2MJh&p|X8Nr9JLhDlHZE z7rtKFPrd+~zY4F5UUuv^&)sK;`TNQ<&VoxOM|_S<*tEGi5VqtU3Cl;e{PNRg6=J_> z1_qw$$KNWBEgn;-8PkoejI6A4OKX9&wPS>d<_)W-l$HkS8b7@$nZT|=H#iR{Ie1GR zjj37pLKfI1_*|TH0g^(E7o*lozbe|xt3@stIN6uUImXKJTT7y^$G9H?hCtE0LPMgm z4w+U~C`;CGkj~(b3)+NargVpVCS*bW7^OqcgtvfS5;! zwY)rr2d|r+vt|*)9$v>U%e0KXYxOYP=54Lg&Dlm){EGkjCh$Eh}tif8v*E6An^_7!DUaiTPRgv1JSkHgdSOTNJ}tKd*BD~UJ_ zD>a#*96=5qIr+JT6JF~JhHdjH)YyU2oohd&2)n)^*?44j$rt{83!MXiMn_ z8}vA2?yHU>*2T1TM!~h(rE{=#a+IVhi-{#1tA28%PmM1)sN2q=2~Z=vClNIhYVmh+ zN_tOl0&BoP9oz4(tY18z=|jIp6N>l|tajntvzhxt zt)q^A$Jg9nngw#;8MLp@Lob|pwYc?y$yauCMNp9d7&oSKKH}1>A0DhoV zNeAY0lRhX2j3=q|Ux0d7i{{m05P3xf!xiR_+x%Xqt#=%wRLe+{#GGDrf0Oh~3?c=X zZk!oodh-0*y_@%<=dFbtE9*|eO@hVknQucNK3^f_SI9S=lO4`H@RaCKTk)J4!7Ews zD}VFAZzntS{>zvnxxkt$ZLhew9>zt*AtbFuz4UIJqXa=$xNHW&F%F7Q`%rEb4=hkNW`R5-dFP8z7rbH) zE+cKqKvs9|GF~VbX{MfEC|HHRQpl^afQvi69-L6D0dgWzStT(ZLT0cAv;gMp@*wR) z_a|Ah2cK+|@UUninU{#5A#y1<+_+W#{@PBGs;QRNAZZBAtf@B|dNElq1s{61#0Lq@ zGBsgOPc2{Uw-I_FJvTh707RZcoSd%;ya-h0Qp>}AD$g`*^2%n?wIbwA@3?n=@3`!W zg4tv>>KhzgkiTic9`&YKd&O>f4M;<(+3RK>_NF3N3RtT{v-(+F~Ptr{TN0fVtM zv|8H0HjlpQaUq@Q=cLRJvO?XRc|((mNCvwu=R=E_Ku|EjUeU5e?Gfk&__PDE7WKRm8>5tpt+jg zUsGX3kT{gCpm5qE5dQ6hMbayh&0Iw%s-m7Z;#Jr1yH{^z`N{&i4XseU1cRC}rprKQ zps?gUWG0Z_0cH`_IucNAD4*i8&8Jd?xbov@XnR9mA1wdki_liTUKwoc#SBpe zn`4h$=IMhro#Cc*@v*xxv3@FNA6?pi2#=Y&ztu{W%X&oMb)0DwFANLA+M35?E%@Ji zJm*RbH`?bL+WSVB6 zev7HQq@>C*=Kf00m80h_T~qM(CO)+iA5&R6zOSg#;0qM41}6z++avbvV~<=o{Mk$V zV!7hbX`n;!q=ki2zsF>NQiaSX_0qwTl5hyy(-BAWl7tHVfu(Ib~sLMUhTx z-V|+=LDIum)|NDd=kbGUR&$8~m2j)}>2X{pUy-e+0nIb<&AU$oz`Pn(Y&b6Hl$jOP z7$EX@E!p?4^LLiLqoX>Ol`gPPAw*@-I(BpyCo6t;tzFQucGi^WAUqY@yo|_W9vxdiug|ytdJ5u>tUW+ zA57EHiY!CKmA-m)@rDcjIgDp*t)Rv&e&Kde<0k*hy>vx_CTm=s%TB>2#NGOOQr)53ZJT&3Hpm@;cO-@N(yQA2sAQusp z56?!BIdW5*M$Er?mA`ibVoKf`tI5d8P^g%%O5S{xZr_gc&f1OEz$Lq(`M2k^wJycj zCMt1C=FwCG%p=E!LV_7^#E{_o7-)~NO5KkXKkXMt-mw0*`l`ERg$eUVWD8(!7xuaU zsHIH(_Wf#jlk`tKwt$Ot*7-SlPp=}=k*+0s!skHJJ%P`P&`Q%S98@je=IZ=2NtK6h zoCP2Y1ElhsJVfz~Pr+|BG0W{F|CGcE0CTmRZyaNQ@J$|kPI}zxD1g~mv6orep}5sM zNO$K)-gVqG8CmUSA@Ve`NrsU9T`2oddd$eJWM0aOpscaA6rW+Bidu<4p0J%SPr2<9 z0p2np+6W2ZZZ1u<_j|;b`o@GjJQ29`gzvuL6p-8XyFruJ^#KEtRFt7H@7?CkTgP1T zI#dSmeKG4|Fj`yeeENd}i}OtT6XFK#tX_%rUiw(8(jGHk+yN60_R@wMKPI}4tSCUlndu58jS%yJIEf9uvhOdNTi&K@eM{b1R2&yGwlV6n zvM}ni1?*r8W7K4u`f<%hIRUI=VX<+-<@BQ7?pgn#uBa$oe*l#X9&g?wm&*)?n(JJw zk^*Ex(oCcct&bgF_&2_uh4$y7kzF8?RiJC~gcsS*3?k&&~4b*gzV0?M`wY?E=G z@2Dt2g=FuzinILq<%?!4d&gJej_AUVl)jsL||I+O4aN$8bW2HC2e z1Xd|Z?)L}_ahtR2tBr=^gGuNSGU#16>B=+TDZ$D1=wtmho*F{gz(~f^x`ac4>2*mP zV#-9QofsyuSWgkJU)<`!(iU)o_gL~(B5Pj1JJ)^NpfSZyg5uimpD>UfDskbk z-%}?8g2mLdSs5yj)VA1X^Sr92Ho9j_f7m6s>{T9+nBF?>z@bK}-IdBA<6{{nhz(@$ z{jG}9K3tMmL#uq*)L*F8T6XCMj29K8`BAdz(kg~0;OG-JSFVv>Z|ajU zU)s7sX8rdr6WvOYM@@In?p%ORC|Sz>-E!m~kat^qTcLaGg4mp0(ZF(U_;uC9yVty| z`^CisQsOP@<1~-;PQmY72S~6g?!-~Geel_sPt&(FlT>$N5YAx-V{V>!9^Gr&lBDM4 z??__5{nv#_mn*|yRPC2(zdUr5{fff{Wq*yq4l7)1L9#X8DFqqiB}zE@_4ssOK_Yap zvOEXhfPVq`!IH0g1G}>|87s8XXdsh^cOXG>KlNB$(0y`aH1VA$BGl2$Y1RLsm@ei< z%34jvaBxNY`i8nxkRnM5(+tau`V!mV3CTfDt&W|jrQLz+(*4irYXL1$7G78iar$wnp*J7HIP7(3 z4nYMl@E=Zu&TD*W>uC=uKBDKa&w|x8dxOI3E2`_GU9zKqOiG(?oT^LjPx-3xae1IT z;}B(uWDIwCNq3Bz>euJdu6}sW+Bfe?$kiO4b7IE+9Uq=tUe9~c;<^p9&a5dmp6s)= z?o-AD#EJYo*?4)Q6XPKCgtY*KS6be1R%EV{xVutLcR@tm2$);&qZPcw+KM=v&9%lESwl6nFjs|O4 zwVsl59UIIS_xlQl-;!tyS8i9WTYmO{0=51yr^yLS&|ZA-4=85yHV{Wi-dei7s6zoJ zXI#jbKis5_w+&2TYltZW+b&BJmVVSuKELWN_rzZYC&qORpM)c8^UWf@S&pW9-5-(q z>iQ38>*nJWK@^kr`0UeDUnhgv_XyRwQpZfr?by))Cy>DSC2(FL zuUf8hVB8!!>?%%Ov)=>tL6G_GLYs@V`nLRMNy29;Jb?G;s#5j!OR`~?1mex7AsBYU zzZ^DG&#{#kGxo1KlFo6>gZE1E{Ju%48ImIR!FDRKVJsiDy(*^MQIa{^^5ve%I|J6E zIVJ7S&BrgXw9v5RjFxSl@;UyF{1~GrLM5?+fNpUdaEuf}R18{aDCaw5YGs~dI`+Zi zWZ0HIu2JwviVqN-j|0OMkX$3Qi=6H26=D1mBYAUr_wN!D3rY%eIyWII$gG8#fD{Wc zo1OlneWnz27_fwr9Aotn#qJ~I>=hHF*x0(q;J67~Mn<%+Ay7%_H-=bbOYH>cF%uc}umQN$j7o6e(=gzxZjXm2duaI@cG{mzK@@>omSHa=&-qid zTQ*JhCB?uEk^mwQC1A#?qt)(CG^95QI`@qbM}pGNaSK=51tdx<YFDcM>*&0hHyETw@fAu*|$`hDHXcM zM^Ot5?HZRnL(PF{m%9rK1Qj)XYmU=*`_hLS*+5D)fLzrDzrS*5Af($1DC9kai}PxD z)ZY^yCz)zW`_%+LN+r3V6t{W)0oB~JkHsKJR{?QBa;UOSO^}+EAFlV?alCe;W%MA@ zt)VlDw1Pd7HzalxSn7qe06&^h5%1FWDX+o?LJBHWvpf`7;Gtwcf7H=LJUJ=0rpa%9 zOCE4qD&M;Mdsks4&y+${75wI9v@#5msuK&?d~25)Dh&X84lyLr3f9~9X|*A7)^HTW z_N@~!D@eW9K;(n+t7$o(kw!MW(>O71R4BWljoVi^ecGo3o!nJu^yHL+VapnzxHGDp zZ(yu|fnEf0jV-?**3~~N4gm$0Uk75QWM(Dr8jE0_8I&X`A}F@t(Xrg4SGkXV<)db6 z=Q%^b=J7?}*c0#X1Y;=p5q`3X$ERG(uz3w%Lf2G2b^s>oU2e?l0NS6i)y6)<$C`X~ zy;hBc>Sc80iw1cEpFv($DINsNRt?{jf#9QNdXQi4+C8ePj`8Y+Q=k_)A(&H$(Fxs65 z9y0du4u4INXqgQH_cIbYx+gegk> zUc^Cw!if||ntQ#`nh9UUIjyMJu_&C&mR`xM00c0LV>C?O9)#BJ7xfM>FV8M~W*j)# zqTjCI<-O4(D+A)#r&Y`j1JTF6Bv46u! zrse_<836pB?8LVDc+CyjUrJd)p5f+hDO5VGfcW0&9szxz?HkB57svP1Xuz&eO&jlM zM5bl3tBf`F`kox4Pv~*^|5(&44p%fs)$&vX4X%7KNok3Dwqr4F?p|8Nq+(jXx zh5DJ;0)!Aoq#Dl^Vx66g9dHdP2=sk5L0&gcs{op#MjifXmmG2!$s}?O*D^6krWoN~ z{z4pZ);fy!x&AaM8fLf=w|#79GTP$|N#9QvDid2_*?cY1CBrBUEksz6;(c0k?oH?j zK`gE3cADR}uBs;G^sB00MvzC=;O!Fz=Bu;IKeHn+JX$&>OFU|&%?n19gKZFK+QrrM zTkw;qO}376W**dj-oAY%cPQUHIfKQri|Y!@qjL)ftXH)&LJ7{_UpMkSNo|Sl#l5&> zA<3om^d-&PuMpk8^Inl4W()&F`A?g};jHhQMYWcd% zU$tjn$4>f-@MX0Pi_JTSlKJX!c1Alf-{0FS$B9GOcz(SrK6-f5TC%%5n;prXDlRpQ z?w=HS9=;1<+u684XT=v1$`&%XQjb1|%K0TFZUsFV6|JbKo2x0P({C)g-j~C7aOojY zA7S7he|?+5-Zq?6`mk3&HW>Fw4r7^lS7>O%fh#dFIA9t{5H4Ll$V@d_;I`CMxgT<) z3vI+xMb{|CU^$5^@&ABwfwkJhKJxUr4T817o_1_m7ejm6>n~yOT;9AK(0rPdTUlh zy`kWhJZYOl=kEnQgszI4$xbX8uN*9-m3JZ({$3P52?pc5Y{zok`K`;KfoL74a((UNt3)=6X26E&Lv6W?Hum{aznog zNOuZ!N@a_Ki`H_Em$(W*qyfgw0OStP(^mw@{9h;2X5N25Y4+RP+2lK;FF_51i-BO!7@{^#7>)Wp@IFr1xePkJTT58l?>+F_=$73UqP96 zN+P1;znOtoa=25w*6QB46SS|xV99=IN^v8iBj&NSuEF&=z;jO+4nMdYzvJ5-oY}j1 zFxWWpvODE^h5Y7N<2B?4jk#Wz`-xk;#S}GWej4o49S5e=jsu?j5`J82$o zXfTzZfs3VvTke|bfTe}~Dm%)jrxMf>cFBoihMoP?2j&EN=iZf)cn)^)Ag`Ik-uz_o zFw>?qSfS2NWRIMHaI@vpWw3tIC=6cL5Wku{p*3O>?-)2G?i7(1lJVdXKtj75!y+f) zel;fbhUkLPyVy0$mQjJ)_5td&zJ+wU%CG9{an< z!K>1|Xm)c?$QFaX$lv`*=J&7o$e1wuNxniTJa<8v?C_b{q4>Y4$w>OmZ5qk{t{70k zed_7=z_bQ6qo@V>>5YfcP)-?OM2S|ItfH+lPL~W*+_uLxwO-qm$K44EM~-?POdzgnS_f2Aa-4^#icVa;R$ejztNE=W+Go5ahW z!#JF5(Ri(Q(JV$buB)`<h1+1$;km<8oh2auRkCOgoeBK6tNniKApZjmqas`2= ze}T8pJp#zKZS0iT@{QkLfVgr({ljbHyk2KB$C}sV0m@YQj``h4x(w7{o-jj{jL@QCeJ(#h zZsP2eLLjhDx8t+kGblj%BQcl@&6rxZp!rC?N~4rw>wuCY>q(Q5Y0Y&PhbS27X|s}> ztBLxywzhB?Bv|iZS;bXCm3cBr`zUwcmNC#=2b9u$<%An$QQ0rL(NC1s1+TTV*kZV? zRZ#s^_m2M(W4o>pzn0MIkoOBS>~h+@Bh`tQvy+Nj9UI0aOk-lAN=f_(X=WWdbpe}8 z4Hh%j{RQ2y9G_GljWZ+@prJIAg1pqk0>JHLEIKC_{IpO_ou9Vl%#fUVNzOu!RS+_iE8ScMyG}g2>s`Su-{_?*^AuIwPOS4z!o`-AJApr_;9z zIz}cUNImse(v5@{QfMK`JIm}*dvNa_+0EVE2ik__p=zO1U*s}OcJz+(4Npj8lS0(K zM?Ny*@(?fv)FGFL_GWZ|`OHv2^s@$4toF$zb=Sm7H8hmxo*1*hl)u%Rk$6aP3h-fJ z^t4YUB;FQ6@^E@4+=p6#&etZx2jgwcTV}MjbTA~(#Eu4B4EDlMU_xLQJ9oZ5E{_Iw zif?`cpem*^LK&0Q2miy0*;jQbb1=?X#RPfy*0F%G+H*F5d=hE+RfFnJ1%+fWi+CVN zm(NVR|Ma#^MfM?ND2|SVT`OvL3?L^*xXTNAcX}M|F@-DDK5QqWu=uF_m0xjEij;LG zUGAu`fKu;94Dc`(7QMb75;iR#Nj#K%rhz?&sX4*IIQCuf^QDR6l7@eP3JS1sTUxSS zmon4U5zqB~3elxbYw^gdMjHu^I)}2o2O!&jPkzf@?3{gaSEfaX$7`}}*|iJSjkmX7 z0yf&oErXtxJ2a9l;4~sz4-x z2nck^$F<*Yr*O4?noame@~t)E7oYm#_;BNY?XkjrhIvg@DZEraFvTd~w zPbEWgKp^;v_xI=NgSbYwe?awB9`YfPWnXH6dN#5l*!Dey{y^CY5LLM1rgioZSb(TUQbq#N$$|w4%;8+ zUA2}utJZ!g;J0ApuXm%0SE__v-##v>d%X{}RCzO407glao}Ll;8D`2=8$g1U@*2nZ z6GReG84EWL`u!jQKua?Qb}Rd~ZbzPd1) z2p`w1;MDHK!uK?tl{kBwIIWu2x*0z_MmB-6r%DEtuEHin^3z5&&pl`=zWd;XWc9SB zK=eZC#~dz8OYGf9F3WbgD~rhC;}CI2D%rNevON(IRa$E#N~gA;8zW21&3b8Ucj54O zk8`XyL`J((vJIkuV(|G_Ei-0V z3C`g^p}AN-l@rEb>JoB9OB>)|}_!rVxdxMFEYSSuSA2+Yp zwqV>jO~qSEThax#bkEvH$D(QOY0={5MII^JYR5BeFVwZi!=3i32CqbWb82Kn&zFA8 z{qkbeZ@$Ii=1LE%l$3?8&dFSKRS^FAP$d=_iH&?KM0-j2Chr`4UHBzsK~?}gEXWQP$Oe=aPP8giXEg@?{UmRO$eGdoG>50NGT-#2%X!xj~E0C2S~@lnK_ zR<&&9VR4j0w;*{xSQ^7kboCHy403F#OE;5M8U%-GS+AqLT=N}vFz>S4_m=^$v80Jt z&y7b#&)Y8dtU1lqC8BMpQkJ`5>P7DXyGIt0ACLY6GBGcfM&t&z-R>vPme)rJH@`{H zw}%Z8q(5s*rir|moE@C9h+qe^u%+-YX0v{PKw4>j=&9=hDSispcf#aq^;bVSwaOdb zlXw!j2fz zXh;GequEtHji&S0yqB_Eb(GwU*6g(1d!AJatw=2B_UZJg4=J&&xgB}NiJW}^$&QEJMa+#$}vE1Hnr};{4#|p8LgDhpIoLU%j08Ijx z8B{=E+4u!l@d6Dm7$$$dV+8PGB!X+NMSsH5Ktc>SRh4pOv0rp`&cH71297hF?C$3t zAD}qj$jZH0Rdd_>t;U^ee@Fxf@7HwPbarNFyG`zwS7#Q4<_n6S06qpkS7bk;#i?+k zb1F&AMkxH|p2pl$wGW8Hl&jd(@2@f<7Re_4r9o1{O>v!e;tP5e=C_-iB^&q0{L8Qa zZC?2yu^|i*#s_|{GRNJq1yIAHKA6yO3-P@*FLLDb;_)%~409Ea1i%J8K+h>{1W?Wj z%*GcPKYN|`!f(@>Xwkf_5WbEzS*OG~6&7c)hwV8_GNWIyD&N{o^sXl* zRowDJzjusMFZqOr`4#d+F?O#5VN8xQRpPl{8{!`s7Pq}?m#z}sjX$nTyEArCn2lfE9w#Yz z#rt;?fz1lxTr$XCoJ<$U@~SVwyFXNXeg6_eo!Korq&#h2dawj#A-??86+Lj6 zWe=x&Quz71XQq&al*SLj0{lj!d8##~pe)e-Bs&jOvBB!eT&IB5$}qA~cnSIw7sRQ=D>GZjajbh`QXHEQGbjuqB|rjP1_>pkUFAC{vi1tCF`N zhz190P5+kZ^tV_#mGcF%qfB+BN}u_1&|%h6BYX#VTWdfYmg%NX(~I15vl#)N;#qw% zqz?$P;i4gh8^V=weSQ0bdf8gp3(92Qm9RAaD+~eQcIwiX^X(5Z9@J5!*+Rk=0r!|g z)L5oZWp4O0Q`_3WbTB+2SAdOyjg2!Wha-1}85@hE6N|#hZ$oG`^@n+X zGNQfOLgw^F^mRLuMT7Y?(}*P*dBv#}yva5VOi*SWe`#N`XQlHM3T01SmtyJS5M`0Q z?-^}Vs9Ky*(^HrF?P%`2kM-AiM$GOPxm|`zYV#mH|t%S?0pJvJ78zTA)-u~C8%?opVwQR&s2H$REk=b)pAf;8MdN7HeKfD~KUN@R- z=JbWZn<7E!@CL!O&b|Ek!tBKe3@Y>Kdc(*OB2DfK2|<1dbjJX0(H=wQTrGuXA0RoO z@YSk*xb|e~!T>;sNBeytks--0fIyX!GPoPAcQ@j?6tgQ_TdUQ0LzJhZ5KeAq(b%Pk zoL%DJZvB!g$C_R-vrEYPv9qHB6Eau?@4RZqSfl_08?Vs7;*kawYX~=az$MTY=+6+- z)zMCnbMeXSvXvTD$SEBtybR!p!>^YYm6UVys-tx++T+v7+|$1>eSvZvd$-)ipN$q9B%@#j5IIfMSb!2e{4@(N2fRu*9xXVBrbb>$UT~xu|HWA0}%P{xebhBJTXF9Veq`GQw7B`IR@D<%ic=6{Qu922Sj zf_371z?~0&h>Vd|K$%CG+B{esu?2+?GfBsWDlCl6uhQbsz#`XsXyzq28p@*9b0HD% zI)``~)7Sw~iVP52#_+jr4W3<`vof~z#{8w$DjmBgF7zU>l^Z-#D7oa`;G4WJL7~N@G5FPZbGlEkp z1@L*8lO*%3WM5FMqd1s#=({LchY=J--{rf{5J-dC{pff90RanjvzG{cl<)_5lh@UR*#>tnuOoce0* z0`o$YUl#t!k$XaEwt5X#pR3pK0@v`~PHRIJCK`BRM(Y_eQK#@f)Tv6%EB4TfM2B@V zJ_Q|+0cie_q7!)4W6?z%Y0zA?4uX;gEm6b@|Tcy+)3B^KQzikcWrqolWNAP{%^@u5Bouwm5JY=x?Q) zL3BC9`~JsyY1_j6xQ1yZo~)>2>J{&kra0gn=6(pO;v#! zZ*_o>q&Me=HcrfH_3elP-gJZxN3PQZ#c4U(=`a-i5(ffV)&{lsI8kJ=mr^SguD%4^ zsyG%G4I&>)m>}$8KjYo#cLQx*c}7z(i1pl$7aIKIQ;jDS^PT zhI!cJPLu!%A;ry8Y!(HT?!v|d-vMNn4o6Nd2Xo2^J$JLbU;Yv62S+7I?Uf zr*Mgbxg4$td-}LgV@oO<@r!5o#H@t?6qr7vcQbi`128i{AXZ_rRHTw@Jp)MpKRcsT zrX?o=WyE5gMJ-M#u}PyW(~Exz^eCCq`=I{3KxNX3eLrin)`Fr>;tdo$)XjaMnlD%G zUS`gD%pgt2S10&l7<7TLbo$niLI_PO2f`Z81^iujQ%%~4z=k?!DFXr(ip~{VN%M~2 zat<(x{M>mjk`O!{`HoeLx#ofS(u1~ID{_wJ5sY#Fw(U>Lb&nPg+n&Eav0Z1OeOUke z{cu~Uw@aagE-B+6tDM}^bXfqB)o;8T#F;!`eKA;u`#iV&H6vT7!&UI;a6pi*bbKkD(OOtbt7p~aU}k$d zZ=G%}M`N)BaX4mLh|WQsp7o3N#)Q25A3XiU9Frg{F9`eW&2i#`KSHYZ$bn5svXE*7 z#i;ST3>O96u@iq+Vk6 z40`VPM2(owShI3VK%f3=>R9uvc1YPqs^ack1c4H_^{3*g%sIOP1>nx#JCiR5GxT+Z zWke=C{9c3Y0$^%#f_~E;omuEasuvkB7RgoV%_y#3bHcJ%L5JrK2Q$M@bZFTRu*KF| zs5??|DLOz+=#_yhuW}T?d~n$>_3zb4bHU~rAMgJ0^KYJaB{>XMcF{InWV;6U;PPCm zWez~>zk&bbG8cXvYExCHR{$u%R`uW$mPuyqEP!bN(6F+7XZ#xMdBK}_J>0yd)Gj44 zdf8gn>XeO_gzJNt%ONC)vMh00>dtN@B9;hz*>1V2NXF+Pv9_fgEG*PQm#AGPlQKJA zrV|_H2FNoue^k|+6z1xT-s7L5c()aR>wTzMD#}7L%xyvYCqw^xG8Xlc#yHYSP4}po zt%L-L94jbux_epb?kR$WTO-2tn``zicp>hVEb1C;k?LwWT#U**j37Wh z-BG?(lMu<8Pi20)1_2wZiE>#U`2F7{HJ%bX5afdk%!hH;sopJz1GNQ6h+rS_e+dMj z6y_yVOF`FK0$WRxx1Qhcw!8z&DKek1WxKpfPAmqff`~2};0`gy6!le@jSkTH1)z~G zr@MYha(sn4?6nXu%-i}WR(W-q$Mo5(Q7sW~*HXg&7t>oERG3uFp!xFCKkHAw=5TCP zDm4qf1GGO#A7mD5?WcFOT<0f3IILaReiAv0OCpq6mC>{Y96T8gV=^Ie^J= zhYJMx^V5ulRyJj#R-78)oVRt%CSMvI2p9Qthe{{k;mX{=upX~x1u6yXtQl3-paZpt zf`VOr&8)+0RsubVKuXfF3PADe>nx05s?HxAM@Ojr7ZKKawie=;sqSca(Ox;G-6?7!7?S#e;yAP=HMWvC0)L8T3IfaEva`LCmwY2NJwLh*&w& z{-*>W5%%~j)lOPs^*>!V^p`;5zKSen;lC@$w)ZN7Q4E9BE|1>PRO3oMQ^4xjV8rQ2 zqq8%x-}TWe6$?%d2L>+-jHGrQxZRJ@4-7owj#KRr^2@zPe;@B?26H7#yceZYa%}y9 z-_HXVv8)|%Q_$T+5$0ca56FKwdF(l>&adCV87e{!XVm$rt3Z1^&nUuh7Fd30OrOD& zTYeb?h-_e&t&I59RubOt$te===U&?U;@aP&#Y~OV%ka_5`J;rNmt2f8cf)n)4k9z) zColkF2n2Wy^c!4vPXf_rK@62@-z@8ODGA0O-xE#n=+)Tl`Pc(I1Fr z5!KZR|NSU~BC{w66P(OpDgM7$*JI!r`~4hn0V!z7`SZ8APBhDZ$`sYV15}^6pam4p z!vkVr0Ws+6fGjORJUomp?9W+jbGX9)ct6HRLR|0i?;9Ua@4w3Q@a3_`f1LpZT|V2z zFdR<5#g&Bsea-PLVXC~-mh)HVet~dJDWU&gw;uocV(MSYprDIqdl-f0OmRBLQvOl_1zDaQ_<4)-!tW2;{J#2csRhM? zAkC9PUceHizE7Ddb=zM4)v^CSxB74V_l^G>rhoDC{{-0N`JMm!>;EmVYE1tZivIm5 J9qIoJ{~un$Edl@l literal 0 HcmV?d00001 diff --git a/src/main/webapp/subscriber/ui/img/people_small.jpg b/src/main/webapp/subscriber/ui/img/people_small.jpg new file mode 100644 index 0000000000000000000000000000000000000000..253f156f39c38d827cf747b2236b2402b3a48b08 GIT binary patch literal 25132 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 H6d(WD7^)lS literal 0 HcmV?d00001 diff --git a/src/main/webapp/subscriber/ui/index.html b/src/main/webapp/subscriber/ui/index.html new file mode 100644 index 0000000..7eebf80 --- /dev/null +++ b/src/main/webapp/subscriber/ui/index.html @@ -0,0 +1,48 @@ + + + + TMF Subscriber console + + + + + + +

+
+
+ + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/subscriber/ui/js/angular-bootstrap/bower.json b/src/main/webapp/subscriber/ui/js/angular-bootstrap/bower.json new file mode 100644 index 0000000..6793432 --- /dev/null +++ b/src/main/webapp/subscriber/ui/js/angular-bootstrap/bower.json @@ -0,0 +1,11 @@ +{ + "author": { + "name": "https://github.com/angular-ui/bootstrap/graphs/contributors" + }, + "name": "angular-bootstrap", + "version": "0.11.0", + "main": ["./ui-bootstrap-tpls.js"], + "dependencies": { + "angular": ">=1" + } +} diff --git a/src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap-tpls.js b/src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap-tpls.js new file mode 100644 index 0000000..bcca1cd --- /dev/null +++ b/src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap-tpls.js @@ -0,0 +1,4116 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.11.0 - 2014-05-01 + * License: MIT + */ +angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); +angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]); +angular.module('ui.bootstrap.transition', []) + +/** + * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. + * @param {DOMElement} element The DOMElement that will be animated. + * @param {string|object|function} trigger The thing that will cause the transition to start: + * - As a string, it represents the css class to be added to the element. + * - As an object, it represents a hash of style attributes to be applied to the element. + * - As a function, it represents a function to be called that will cause the transition to occur. + * @return {Promise} A promise that is resolved when the transition finishes. + */ +.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { + + var $transition = function(element, trigger, options) { + options = options || {}; + var deferred = $q.defer(); + var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName']; + + var transitionEndHandler = function(event) { + $rootScope.$apply(function() { + element.unbind(endEventName, transitionEndHandler); + deferred.resolve(element); + }); + }; + + if (endEventName) { + element.bind(endEventName, transitionEndHandler); + } + + // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur + $timeout(function() { + if ( angular.isString(trigger) ) { + element.addClass(trigger); + } else if ( angular.isFunction(trigger) ) { + trigger(element); + } else if ( angular.isObject(trigger) ) { + element.css(trigger); + } + //If browser does not support transitions, instantly resolve + if ( !endEventName ) { + deferred.resolve(element); + } + }); + + // Add our custom cancel function to the promise that is returned + // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, + // i.e. it will therefore never raise a transitionEnd event for that transition + deferred.promise.cancel = function() { + if ( endEventName ) { + element.unbind(endEventName, transitionEndHandler); + } + deferred.reject('Transition cancelled'); + }; + + return deferred.promise; + }; + + // Work out the name of the transitionEnd event + var transElement = document.createElement('trans'); + var transitionEndEventNames = { + 'WebkitTransition': 'webkitTransitionEnd', + 'MozTransition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'transition': 'transitionend' + }; + var animationEndEventNames = { + 'WebkitTransition': 'webkitAnimationEnd', + 'MozTransition': 'animationend', + 'OTransition': 'oAnimationEnd', + 'transition': 'animationend' + }; + function findEndEventName(endEventNames) { + for (var name in endEventNames){ + if (transElement.style[name] !== undefined) { + return endEventNames[name]; + } + } + } + $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); + $transition.animationEndEventName = findEndEventName(animationEndEventNames); + return $transition; +}]); + +angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) + + .directive('collapse', ['$transition', function ($transition) { + + return { + link: function (scope, element, attrs) { + + var initialAnimSkip = true; + var currentTransition; + + function doTransition(change) { + var newTransition = $transition(element, change); + if (currentTransition) { + currentTransition.cancel(); + } + currentTransition = newTransition; + newTransition.then(newTransitionDone, newTransitionDone); + return newTransition; + + function newTransitionDone() { + // Make sure it's this transition, otherwise, leave it alone. + if (currentTransition === newTransition) { + currentTransition = undefined; + } + } + } + + function expand() { + if (initialAnimSkip) { + initialAnimSkip = false; + expandDone(); + } else { + element.removeClass('collapse').addClass('collapsing'); + doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone); + } + } + + function expandDone() { + element.removeClass('collapsing'); + element.addClass('collapse in'); + element.css({height: 'auto'}); + } + + function collapse() { + if (initialAnimSkip) { + initialAnimSkip = false; + collapseDone(); + element.css({height: 0}); + } else { + // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value + element.css({ height: element[0].scrollHeight + 'px' }); + //trigger reflow so a browser realizes that height was updated from auto to a specific value + var x = element[0].offsetWidth; + + element.removeClass('collapse in').addClass('collapsing'); + + doTransition({ height: 0 }).then(collapseDone); + } + } + + function collapseDone() { + element.removeClass('collapsing'); + element.addClass('collapse'); + } + + scope.$watch(attrs.collapse, function (shouldCollapse) { + if (shouldCollapse) { + collapse(); + } else { + expand(); + } + }); + } + }; + }]); + +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(index, 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', function() { + return { + require:'^accordion', // We need this directive to be inside an accordion + restrict:'EA', + transclude:true, // It transcludes the contents of the directive into the template + replace: true, // The element containing the directive will be replaced with the template + templateUrl:'template/accordion/accordion-group.html', + scope: { + heading: '@', // Interpolate the heading attribute onto this scope + isOpen: '=?', + isDisabled: '=?' + }, + controller: function() { + this.setHeading = function(element) { + this.heading = element; + }; + }, + link: function(scope, element, attrs, accordionCtrl) { + accordionCtrl.addGroup(scope); + + scope.$watch('isOpen', function(value) { + if ( value ) { + accordionCtrl.closeOthers(scope); + } + }); + + scope.toggleOpen = function() { + if ( !scope.isDisabled ) { + scope.isOpen = !scope.isOpen; + } + }; + } + }; +}) + +// Use accordion-heading below an accordion-group to provide a heading containing HTML +// +// Heading containing HTML - +// +.directive('accordionHeading', function() { + return { + restrict: 'EA', + transclude: true, // Grab the contents to be used as the heading + template: '', // In effect remove this element! + replace: true, + require: '^accordionGroup', + link: function(scope, element, attr, accordionGroupCtrl, transclude) { + // 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 +//
+// +// ... +//
+.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.alert', []) + +.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { + $scope.closeable = 'close' in $attrs; +}]) + +.directive('alert', function () { + return { + restrict:'EA', + controller:'AlertController', + templateUrl:'template/alert/alert.html', + transclude:true, + replace:true, + scope: { + type: '@', + close: '&' + } + }; +}); + +angular.module('ui.bootstrap.bindHtml', []) + + .directive('bindHtmlUnsafe', function () { + return function (scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); + scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { + element.html(value || ''); + }); + }; + }); +angular.module('ui.bootstrap.buttons', []) + +.constant('buttonConfig', { + activeClass: 'active', + toggleEvent: 'click' +}) + +.controller('ButtonsController', ['buttonConfig', function(buttonConfig) { + this.activeClass = buttonConfig.activeClass || 'active'; + this.toggleEvent = buttonConfig.toggleEvent || 'click'; +}]) + +.directive('btnRadio', function () { + return { + require: ['btnRadio', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + var isActive = element.hasClass(buttonsCtrl.activeClass); + + if (!isActive || angular.isDefined(attrs.uncheckable)) { + scope.$apply(function () { + ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio)); + ngModelCtrl.$render(); + }); + } + }); + } + }; +}) + +.directive('btnCheckbox', function () { + return { + require: ['btnCheckbox', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + function getTrueValue() { + return getCheckboxValue(attrs.btnCheckboxTrue, true); + } + + function getFalseValue() { + return getCheckboxValue(attrs.btnCheckboxFalse, false); + } + + function getCheckboxValue(attributeValue, defaultValue) { + var val = scope.$eval(attributeValue); + return angular.isDefined(val) ? val : defaultValue; + } + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + scope.$apply(function () { + ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); + ngModelCtrl.$render(); + }); + }); + } + }; +}); + +/** +* @ngdoc overview +* @name ui.bootstrap.carousel +* +* @description +* AngularJS version of an image carousel. +* +*/ +angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) +.controller('CarouselController', ['$scope', '$timeout', '$transition', function ($scope, $timeout, $transition) { + var self = this, + slides = self.slides = $scope.slides = [], + currentIndex = -1, + currentTimeout, isPlaying; + self.currentSlide = null; + + var destroyed = false; + /* direction: "prev" or "next" */ + self.select = $scope.select = function(nextSlide, direction) { + var nextIndex = slides.indexOf(nextSlide); + //Decide direction if it's not given + if (direction === undefined) { + direction = nextIndex > currentIndex ? 'next' : 'prev'; + } + if (nextSlide && nextSlide !== self.currentSlide) { + if ($scope.$currentTransition) { + $scope.$currentTransition.cancel(); + //Timeout so ng-class in template has time to fix classes for finished slide + $timeout(goNext); + } else { + goNext(); + } + } + function goNext() { + // Scope has been destroyed, stop here. + if (destroyed) { return; } + //If we have a slide to transition from and we have a transition type and we're allowed, go + if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { + //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime + nextSlide.$element.addClass(direction); + var reflow = nextSlide.$element[0].offsetWidth; //force reflow + + //Set all other slides to stop doing their stuff for the new transition + angular.forEach(slides, function(slide) { + angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); + }); + angular.extend(nextSlide, {direction: direction, active: true, entering: true}); + angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); + + $scope.$currentTransition = $transition(nextSlide.$element, {}); + //We have to create new pointers inside a closure since next & current will change + (function(next,current) { + $scope.$currentTransition.then( + function(){ transitionDone(next, current); }, + function(){ transitionDone(next, current); } + ); + }(nextSlide, self.currentSlide)); + } else { + transitionDone(nextSlide, self.currentSlide); + } + self.currentSlide = nextSlide; + currentIndex = nextIndex; + //every time you change slides, reset the timer + restartTimer(); + } + function transitionDone(next, current) { + angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); + angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); + $scope.$currentTransition = null; + } + }; + $scope.$on('$destroy', function () { + destroyed = true; + }); + + /* Allow outside people to call indexOf on slides array */ + self.indexOfSlide = function(slide) { + return slides.indexOf(slide); + }; + + $scope.next = function() { + var newIndex = (currentIndex + 1) % slides.length; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'next'); + } + }; + + $scope.prev = function() { + var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'prev'); + } + }; + + $scope.isActive = function(slide) { + return self.currentSlide === slide; + }; + + $scope.$watch('interval', restartTimer); + $scope.$on('$destroy', resetTimer); + + function restartTimer() { + resetTimer(); + var interval = +$scope.interval; + if (!isNaN(interval) && interval>=0) { + currentTimeout = $timeout(timerFn, interval); + } + } + + function resetTimer() { + if (currentTimeout) { + $timeout.cancel(currentTimeout); + currentTimeout = null; + } + } + + function timerFn() { + if (isPlaying) { + $scope.next(); + restartTimer(); + } else { + $scope.pause(); + } + } + + $scope.play = function() { + if (!isPlaying) { + isPlaying = true; + restartTimer(); + } + }; + $scope.pause = function() { + if (!$scope.noPause) { + isPlaying = false; + resetTimer(); + } + }; + + self.addSlide = function(slide, element) { + slide.$element = element; + slides.push(slide); + //if this is the first slide or the slide is set to active, select it + if(slides.length === 1 || slide.active) { + self.select(slides[slides.length-1]); + if (slides.length == 1) { + $scope.play(); + } + } else { + slide.active = false; + } + }; + + self.removeSlide = function(slide) { + //get the index of the slide inside the carousel + var index = slides.indexOf(slide); + slides.splice(index, 1); + if (slides.length > 0 && slide.active) { + if (index >= slides.length) { + self.select(slides[index-1]); + } else { + self.select(slides[index]); + } + } else if (currentIndex > index) { + currentIndex--; + } + }; + +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:carousel + * @restrict EA + * + * @description + * Carousel is the outer container for a set of image 'slides' to showcase. + * + * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. + * @param {boolean=} noTransition Whether to disable transitions on the carousel. + * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). + * + * @example + + + + + + + + + + + + + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + + + */ +.directive('carousel', [function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + controller: 'CarouselController', + require: 'carousel', + templateUrl: 'template/carousel/carousel.html', + scope: { + interval: '=', + noTransition: '=', + noPause: '=' + } + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:slide + * @restrict EA + * + * @description + * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. + * + * @param {boolean=} active Model binding, whether or not this slide is currently active. + * + * @example + + +
+ + + + + + + Interval, in milliseconds: +
Enter a negative number to stop the interval. +
+
+ +function CarouselDemoCtrl($scope) { + $scope.myInterval = 5000; +} + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + +
+*/ + +.directive('slide', function() { + return { + require: '^carousel', + restrict: 'EA', + transclude: true, + replace: true, + templateUrl: 'template/carousel/slide.html', + scope: { + active: '=?' + }, + link: function (scope, element, attrs, carouselCtrl) { + carouselCtrl.addSlide(scope, element); + //when the scope is destroyed then remove the slide from the current slides array + scope.$on('$destroy', function() { + carouselCtrl.removeSlide(scope); + }); + + scope.$watch('active', function(active) { + if (active) { + carouselCtrl.select(scope); + } + }); + } + }; +}); + +angular.module('ui.bootstrap.dateparser', []) + +.service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) { + + this.parsers = {}; + + var formatCodeToRegex = { + 'yyyy': { + regex: '\\d{4}', + apply: function(value) { this.year = +value; } + }, + 'yy': { + regex: '\\d{2}', + apply: function(value) { this.year = +value + 2000; } + }, + 'y': { + regex: '\\d{1,4}', + apply: function(value) { this.year = +value; } + }, + 'MMMM': { + regex: $locale.DATETIME_FORMATS.MONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } + }, + 'MMM': { + regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } + }, + 'MM': { + regex: '0[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'M': { + regex: '[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'dd': { + regex: '[0-2][0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'd': { + regex: '[1-2]?[0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'EEEE': { + regex: $locale.DATETIME_FORMATS.DAY.join('|') + }, + 'EEE': { + regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') + } + }; + + this.createParser = function(format) { + var map = [], regex = format.split(''); + + angular.forEach(formatCodeToRegex, function(data, code) { + var index = format.indexOf(code); + + if (index > -1) { + format = format.split(''); + + regex[index] = '(' + data.regex + ')'; + format[index] = '$'; // Custom symbol to define consumed part of format + for (var i = index + 1, n = index + code.length; i < n; i++) { + regex[i] = ''; + format[i] = '$'; + } + format = format.join(''); + + map.push({ index: index, apply: data.apply }); + } + }); + + return { + regex: new RegExp('^' + regex.join('') + '$'), + map: orderByFilter(map, 'index') + }; + }; + + this.parse = function(input, format) { + if ( !angular.isString(input) ) { + return input; + } + + format = $locale.DATETIME_FORMATS[format] || format; + + if ( !this.parsers[format] ) { + this.parsers[format] = this.createParser(format); + } + + var parser = this.parsers[format], + regex = parser.regex, + map = parser.map, + results = input.match(regex); + + if ( results && results.length ) { + var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt; + + for( var i = 1, n = results.length; i < n; i++ ) { + var mapper = map[i-1]; + if ( mapper.apply ) { + mapper.apply.call(fields, results[i]); + } + } + + if ( isValid(fields.year, fields.month, fields.date) ) { + dt = new Date( fields.year, fields.month, fields.date, fields.hours); + } + + 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 ( 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; + } +}]); + +angular.module('ui.bootstrap.position', []) + +/** + * A set of utility methods that can be use to retrieve position of DOM elements. + * It is meant to be used where we need to absolute-position DOM elements in + * relation to other, existing elements (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$position', ['$document', '$window', function ($document, $window) { + + function getStyle(el, cssprop) { + if (el.currentStyle) { //IE + return el.currentStyle[cssprop]; + } else if ($window.getComputedStyle) { + return $window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; + } + + /** + * Checks if a given element is statically positioned + * @param element - raw DOM element + */ + function isStaticPositioned(element) { + return (getStyle(element, 'position') || 'static' ) === 'static'; + } + + /** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ + var parentOffsetEl = function (element) { + var docDomEl = $document[0]; + var offsetParent = element.offsetParent || docDomEl; + while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || docDomEl; + }; + + return { + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ + */ + position: function (element) { + var elBCR = this.offset(element); + var offsetParentBCR = { top: 0, left: 0 }; + var offsetParentEl = parentOffsetEl(element[0]); + if (offsetParentEl != $document[0]) { + offsetParentBCR = this.offset(angular.element(offsetParentEl)); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ + */ + offset: function (element) { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop), + left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft) + }; + }, + + /** + * Provides coordinates for the targetEl in relation to hostEl + */ + positionElements: function (hostEl, targetEl, positionStr, appendToBody) { + + var positionStrParts = positionStr.split('-'); + var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; + + var hostElPos, + targetElWidth, + targetElHeight, + targetElPos; + + hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); + + targetElWidth = targetEl.prop('offsetWidth'); + targetElHeight = targetEl.prop('offsetHeight'); + + var shiftWidth = { + center: function () { + return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; + }, + left: function () { + return hostElPos.left; + }, + right: function () { + return hostElPos.left + hostElPos.width; + } + }; + + var shiftHeight = { + center: function () { + return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; + }, + top: function () { + return hostElPos.top; + }, + bottom: function () { + return hostElPos.top + hostElPos.height; + } + }; + + switch (pos0) { + case 'right': + targetElPos = { + top: shiftHeight[pos1](), + left: shiftWidth[pos0]() + }; + break; + case 'left': + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left - targetElWidth + }; + break; + case 'bottom': + targetElPos = { + top: shiftHeight[pos0](), + left: shiftWidth[pos1]() + }; + break; + default: + targetElPos = { + top: hostElPos.top - targetElHeight, + left: shiftWidth[pos1]() + }; + break; + } + + return targetElPos; + } + }; + }]); + +angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position']) + +.constant('datepickerConfig', { + formatDay: 'dd', + formatMonth: 'MMMM', + formatYear: 'yyyy', + formatDayHeader: 'EEE', + formatDayTitle: 'MMMM yyyy', + formatMonthTitle: 'yyyy', + datepickerMode: 'day', + minMode: 'day', + maxMode: 'year', + showWeeks: true, + startingDay: 0, + yearRange: 20, + minDate: null, + maxDate: null +}) + +.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; + + // Modes chain + this.modes = ['day', 'month', 'year']; + + // Configuration attributes + angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', + 'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function( key, index ) { + self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; + }); + + // Watchable attributes + angular.forEach(['minDate', 'maxDate'], function( key ) { + if ( $attrs[key] ) { + $scope.$parent.$watch($parse($attrs[key]), function(value) { + self[key] = value ? new Date(value) : null; + self.refreshView(); + }); + } else { + self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null; + } + }); + + $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; + $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); + this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date(); + + $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_; + + ngModelCtrl.$render = function() { + self.render(); + }; + }; + + this.render = function() { + if ( ngModelCtrl.$modelValue ) { + var date = new Date( ngModelCtrl.$modelValue ), + isValid = !isNaN(date); + + if ( isValid ) { + this.activeDate = date; + } else { + $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } + ngModelCtrl.$setValidity('date', isValid); + } + this.refreshView(); + }; + + this.refreshView = function() { + if ( this.element ) { + this._refreshView(); + + var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date))); + } + }; + + this.createDateObject = function(date, format) { + var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + return { + date: date, + label: dateFilter(date, format), + selected: model && this.compare(date, model) === 0, + disabled: this.isDisabled(date), + current: this.compare(date, new Date()) === 0 + }; + }; + + this.isDisabled = function( date ) { + return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({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.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); + ngModelCtrl.$setViewValue( dt ); + ngModelCtrl.$render(); + } else { + self.activeDate = date; + $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ]; + } + }; + + $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; + } + + $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ]; + }; + + // 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() { + $timeout(function() { + self.element[0].focus(); + }, 0 , false); + }; + + // Listen for focus requests from popup directive + $scope.$on('datepicker.focus', focusElement); + + $scope.keydown = function( evt ) { + var key = $scope.keys[evt.which]; + + if ( !key || evt.shiftKey || evt.altKey ) { + return; + } + + evt.preventDefault(); + evt.stopPropagation(); + + if (key === 'enter' || key === 'space') { + if ( self.isDisabled(self.activeDate)) { + return; // do nothing + } + $scope.select(self.activeDate); + focusElement(); + } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { + $scope.toggleMode(key === 'up' ? 1 : -1); + focusElement(); + } else { + self.handleKeyDown(key, evt); + self.refreshView(); + } + }; +}]) + +.directive( 'datepicker', function () { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/datepicker.html', + scope: { + datepickerMode: '=?', + dateDisabled: '&' + }, + require: ['datepicker', '?^ngModel'], + controller: 'DatepickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + datepickerCtrl.init( ngModelCtrl ); + } + } + }; +}) + +.directive('daypicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/day.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + scope.showWeeks = ctrl.showWeeks; + + ctrl.step = { months: 1 }; + ctrl.element = element; + + var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + function getDaysInMonth( year, month ) { + return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; + } + + function getDates(startDate, n) { + var dates = new Array(n), current = new Date(startDate), i = 0; + current.setHours(12); // Prevent repeated dates because of timezone bug + while ( i < n ) { + dates[i++] = new Date(current); + current.setDate( current.getDate() + 1 ); + } + return dates; + } + + ctrl._refreshView = function() { + var year = ctrl.activeDate.getFullYear(), + month = ctrl.activeDate.getMonth(), + firstDayOfMonth = new Date(year, month, 1), + difference = ctrl.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-month calendar + var days = getDates(firstDate, 42); + for (var i = 0; i < 42; i ++) { + days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.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, ctrl.formatDayHeader), + full: dateFilter(days[j].date, 'EEEE') + }; + } + + scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle); + scope.rows = ctrl.split(days, 7); + + if ( scope.showWeeks ) { + scope.weekNumbers = []; + var weekNumber = getISO8601WeekNumber( scope.rows[0][0].date ), + numWeeks = scope.rows.length; + while( scope.weekNumbers.push(weekNumber++) < numWeeks ) {} + } + }; + + ctrl.compare = function(date1, date2) { + return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); + }; + + 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; + } + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getDate(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 7; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 7; + } else if (key === 'pageup' || key === 'pagedown') { + var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); + ctrl.activeDate.setMonth(month, 1); + date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date); + } else if (key === 'home') { + date = 1; + } else if (key === 'end') { + date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()); + } + ctrl.activeDate.setDate(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.directive('monthpicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/month.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + ctrl.step = { years: 1 }; + ctrl.element = element; + + ctrl._refreshView = function() { + var months = new Array(12), + year = ctrl.activeDate.getFullYear(); + + for ( var i = 0; i < 12; i++ ) { + months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle); + scope.rows = ctrl.split(months, 3); + }; + + ctrl.compare = function(date1, date2) { + return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); + }; + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getMonth(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 3; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 3; + } else if (key === 'pageup' || key === 'pagedown') { + var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); + ctrl.activeDate.setFullYear(year); + } else if (key === 'home') { + date = 0; + } else if (key === 'end') { + date = 11; + } + ctrl.activeDate.setMonth(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.directive('yearpicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/year.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + var range = ctrl.yearRange; + + ctrl.step = { years: range }; + ctrl.element = element; + + function getStartingYear( year ) { + return parseInt((year - 1) / range, 10) * range + 1; + } + + ctrl._refreshView = function() { + var years = new Array(range); + + for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) { + years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = [years[0].label, years[range - 1].label].join(' - '); + scope.rows = ctrl.split(years, 5); + }; + + ctrl.compare = function(date1, date2) { + return date1.getFullYear() - date2.getFullYear(); + }; + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getFullYear(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 5; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 5; + } else if (key === 'pageup' || key === 'pagedown') { + date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years; + } else if (key === 'home') { + date = getStartingYear( ctrl.activeDate.getFullYear() ); + } else if (key === 'end') { + date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1; + } + ctrl.activeDate.setFullYear(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.constant('datepickerPopupConfig', { + datepickerPopup: 'yyyy-MM-dd', + currentText: 'Today', + clearText: 'Clear', + closeText: 'Done', + closeOnDateSelection: true, + appendToBody: false, + showButtonBar: true +}) + +.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', +function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) { + return { + restrict: 'EA', + require: 'ngModel', + scope: { + isOpen: '=?', + currentText: '@', + clearText: '@', + closeText: '@', + dateDisabled: '&' + }, + link: function(scope, element, attrs, ngModel) { + var dateFormat, + closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, + appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; + + scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; + + scope.getText = function( key ) { + return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; + }; + + attrs.$observe('datepickerPopup', function(value) { + dateFormat = value || datepickerPopupConfig.datepickerPopup; + ngModel.$render(); + }); + + // popup element used to display calendar + var popupEl = angular.element('
'); + popupEl.attr({ + 'ng-model': 'date', + 'ng-change': 'dateSelection()' + }); + + function cameltoDash( string ){ + return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); + } + + // datepicker element + var datepickerEl = angular.element(popupEl.children()[0]); + if ( attrs.datepickerOptions ) { + angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function( value, option ) { + datepickerEl.attr( cameltoDash(option), value ); + }); + } + + angular.forEach(['minDate', 'maxDate'], function( key ) { + if ( attrs[key] ) { + scope.$parent.$watch($parse(attrs[key]), function(value){ + scope[key] = value; + }); + datepickerEl.attr(cameltoDash(key), key); + } + }); + if (attrs.dateDisabled) { + datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); + } + + function parseDate(viewValue) { + if (!viewValue) { + ngModel.$setValidity('date', true); + return null; + } else if (angular.isDate(viewValue) && !isNaN(viewValue)) { + ngModel.$setValidity('date', true); + return viewValue; + } else if (angular.isString(viewValue)) { + var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue); + if (isNaN(date)) { + ngModel.$setValidity('date', false); + return undefined; + } else { + ngModel.$setValidity('date', true); + return date; + } + } else { + ngModel.$setValidity('date', false); + return undefined; + } + } + ngModel.$parsers.unshift(parseDate); + + // Inner change + scope.dateSelection = function(dt) { + if (angular.isDefined(dt)) { + scope.date = dt; + } + ngModel.$setViewValue(scope.date); + ngModel.$render(); + + if ( closeOnDateSelection ) { + scope.isOpen = false; + element[0].focus(); + } + }; + + element.bind('input change keyup', function() { + scope.$apply(function() { + scope.date = ngModel.$modelValue; + }); + }); + + // Outter change + ngModel.$render = function() { + var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; + element.val(date); + scope.date = parseDate( ngModel.$modelValue ); + }; + + var documentClickBind = function(event) { + if (scope.isOpen && event.target !== element[0]) { + scope.$apply(function() { + scope.isOpen = false; + }); + } + }; + + var keydown = function(evt, noApply) { + scope.keydown(evt); + }; + element.bind('keydown', keydown); + + scope.keydown = function(evt) { + if (evt.which === 27) { + evt.preventDefault(); + evt.stopPropagation(); + scope.close(); + } else if (evt.which === 40 && !scope.isOpen) { + scope.isOpen = true; + } + }; + + scope.$watch('isOpen', function(value) { + if (value) { + scope.$broadcast('datepicker.focus'); + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top = scope.position.top + element.prop('offsetHeight'); + + $document.bind('click', documentClickBind); + } else { + $document.unbind('click', documentClickBind); + } + }); + + scope.select = function( date ) { + if (date === 'today') { + var today = new Date(); + if (angular.isDate(ngModel.$modelValue)) { + date = new Date(ngModel.$modelValue); + date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); + } else { + date = new Date(today.setHours(0, 0, 0, 0)); + } + } + scope.dateSelection( date ); + }; + + scope.close = function() { + scope.isOpen = false; + element[0].focus(); + }; + + var $popup = $compile(popupEl)(scope); + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + + scope.$on('$destroy', function() { + $popup.remove(); + element.unbind('keydown', keydown); + $document.unbind('click', documentClickBind); + }); + } + }; +}]) + +.directive('datepickerPopupWrap', function() { + return { + restrict:'EA', + replace: true, + transclude: true, + templateUrl: 'template/datepicker/popup.html', + link:function (scope, element, attrs) { + element.bind('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + }); + } + }; +}); + +angular.module('ui.bootstrap.dropdown', []) + +.constant('dropdownConfig', { + openClass: 'open' +}) + +.service('dropdownService', ['$document', function($document) { + var openScope = null; + + this.open = function( dropdownScope ) { + if ( !openScope ) { + $document.bind('click', closeDropdown); + $document.bind('keydown', escapeKeyBind); + } + + if ( openScope && openScope !== dropdownScope ) { + openScope.isOpen = false; + } + + openScope = dropdownScope; + }; + + this.close = function( dropdownScope ) { + if ( openScope === dropdownScope ) { + openScope = null; + $document.unbind('click', closeDropdown); + $document.unbind('keydown', escapeKeyBind); + } + }; + + var closeDropdown = function( evt ) { + if (evt && evt.isDefaultPrevented()) { + return; + } + + openScope.$apply(function() { + openScope.isOpen = false; + }); + }; + + var escapeKeyBind = function( evt ) { + if ( evt.which === 27 ) { + openScope.focusToggleElement(); + closeDropdown(); + } + }; +}]) + +.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) { + var self = this, + scope = $scope.$new(), // create a child scope so we are not polluting original one + openClass = dropdownConfig.openClass, + getIsOpen, + setIsOpen = angular.noop, + toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop; + + this.init = function( element ) { + self.$element = element; + + if ( $attrs.isOpen ) { + getIsOpen = $parse($attrs.isOpen); + setIsOpen = getIsOpen.assign; + + $scope.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); + } + }; + + this.toggle = function( open ) { + return scope.isOpen = arguments.length ? !!open : !scope.isOpen; + }; + + // Allow other directives to watch status + this.isOpen = function() { + return scope.isOpen; + }; + + scope.focusToggleElement = function() { + if ( self.toggleElement ) { + self.toggleElement[0].focus(); + } + }; + + scope.$watch('isOpen', function( isOpen, wasOpen ) { + $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass); + + if ( isOpen ) { + scope.focusToggleElement(); + dropdownService.open( scope ); + } else { + dropdownService.close( scope ); + } + + setIsOpen($scope, isOpen); + if (angular.isDefined(isOpen) && isOpen !== wasOpen) { + toggleInvoker($scope, { open: !!isOpen }); + } + }); + + $scope.$on('$locationChangeSuccess', function() { + scope.isOpen = false; + }); + + $scope.$on('$destroy', function() { + scope.$destroy(); + }); +}]) + +.directive('dropdown', function() { + return { + restrict: 'CA', + controller: 'DropdownController', + link: function(scope, element, attrs, dropdownCtrl) { + dropdownCtrl.init( element ); + } + }; +}) + +.directive('dropdownToggle', function() { + return { + restrict: 'CA', + require: '?^dropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if ( !dropdownCtrl ) { + return; + } + + dropdownCtrl.toggleElement = element; + + var toggleDropdown = function(event) { + event.preventDefault(); + + if ( !element.hasClass('disabled') && !attrs.disabled ) { + scope.$apply(function() { + dropdownCtrl.toggle(); + }); + } + }; + + element.bind('click', toggleDropdown); + + // WAI-ARIA + element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); + scope.$watch(dropdownCtrl.isOpen, function( isOpen ) { + element.attr('aria-expanded', !!isOpen); + }); + + scope.$on('$destroy', function() { + element.unbind('click', toggleDropdown); + }); + } + }; +}); + +angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) + +/** + * A helper, internal data structure that acts as a map but also allows getting / removing + * elements in the LIFO order + */ + .factory('$$stackedMap', function () { + return { + createNew: function () { + var stack = []; + + return { + add: function (key, value) { + stack.push({ + key: key, + value: value + }); + }, + get: function (key) { + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + return stack[i]; + } + } + }, + keys: function() { + var keys = []; + for (var i = 0; i < stack.length; i++) { + keys.push(stack[i].key); + } + return keys; + }, + top: function () { + return stack[stack.length - 1]; + }, + remove: function (key) { + var idx = -1; + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + idx = i; + break; + } + } + return stack.splice(idx, 1)[0]; + }, + removeTop: function () { + return stack.splice(stack.length - 1, 1)[0]; + }, + length: function () { + return stack.length; + } + }; + } + }; + }) + +/** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('modalBackdrop', ['$timeout', function ($timeout) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/modal/backdrop.html', + link: function (scope) { + + scope.animate = false; + + //trigger CSS transitions + $timeout(function () { + scope.animate = true; + }); + } + }; + }]) + + .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { + return { + restrict: 'EA', + scope: { + index: '@', + animate: '=' + }, + replace: true, + transclude: true, + templateUrl: function(tElement, tAttrs) { + return tAttrs.templateUrl || 'template/modal/window.html'; + }, + link: function (scope, element, attrs) { + element.addClass(attrs.windowClass || ''); + scope.size = attrs.size; + + $timeout(function () { + // trigger CSS transitions + scope.animate = true; + // focus a freshly-opened modal + element[0].focus(); + }); + + scope.close = function (evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + } + }; + }]) + + .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', + function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { + + var OPENED_MODAL_CLASS = 'modal-open'; + + var backdropDomEl, backdropScope; + var openedWindows = $$stackedMap.createNew(); + var $modalStack = {}; + + function backdropIndex() { + var topBackdropIndex = -1; + var opened = openedWindows.keys(); + for (var i = 0; i < opened.length; i++) { + if (openedWindows.get(opened[i]).value.backdrop) { + topBackdropIndex = i; + } + } + return topBackdropIndex; + } + + $rootScope.$watch(backdropIndex, function(newBackdropIndex){ + if (backdropScope) { + backdropScope.index = newBackdropIndex; + } + }); + + function removeModalWindow(modalInstance) { + + var body = $document.find('body').eq(0); + var modalWindow = openedWindows.get(modalInstance).value; + + //clean up the stack + openedWindows.remove(modalInstance); + + //remove window DOM element + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() { + modalWindow.modalScope.$destroy(); + body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); + checkRemoveBackdrop(); + }); + } + + function checkRemoveBackdrop() { + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() == -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, 150, function () { + backdropScopeRef.$destroy(); + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } + } + + function removeAfterAnimate(domEl, scope, emulateTime, done) { + // Closing animation + scope.animate = false; + + var transitionEndEventName = $transition.transitionEndEventName; + if (transitionEndEventName) { + // transition out + var timeout = $timeout(afterAnimating, emulateTime); + + domEl.bind(transitionEndEventName, function () { + $timeout.cancel(timeout); + afterAnimating(); + scope.$apply(); + }); + } else { + // Ensure this call is async + $timeout(afterAnimating, 0); + } + + function afterAnimating() { + if (afterAnimating.done) { + return; + } + afterAnimating.done = true; + + domEl.remove(); + if (done) { + done(); + } + } + } + + $document.bind('keydown', function (evt) { + var modal; + + if (evt.which === 27) { + modal = openedWindows.top(); + if (modal && modal.value.keyboard) { + evt.preventDefault(); + $rootScope.$apply(function () { + $modalStack.dismiss(modal.key, 'escape key press'); + }); + } + } + }); + + $modalStack.open = function (modalInstance, modal) { + + openedWindows.add(modalInstance, { + deferred: modal.deferred, + modalScope: modal.scope, + backdrop: modal.backdrop, + keyboard: modal.keyboard + }); + + var body = $document.find('body').eq(0), + currBackdropIndex = backdropIndex(); + + if (currBackdropIndex >= 0 && !backdropDomEl) { + backdropScope = $rootScope.$new(true); + backdropScope.index = currBackdropIndex; + backdropDomEl = $compile('
')(backdropScope); + body.append(backdropDomEl); + } + + var angularDomEl = angular.element('
'); + angularDomEl.attr({ + 'template-url': modal.windowTemplateUrl, + 'window-class': modal.windowClass, + 'size': modal.size, + 'index': openedWindows.length() - 1, + 'animate': 'animate' + }).html(modal.content); + + var modalDomEl = $compile(angularDomEl)(modal.scope); + openedWindows.top().value.modalDomEl = modalDomEl; + body.append(modalDomEl); + body.addClass(OPENED_MODAL_CLASS); + }; + + $modalStack.close = function (modalInstance, result) { + var modalWindow = openedWindows.get(modalInstance).value; + if (modalWindow) { + modalWindow.deferred.resolve(result); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismiss = function (modalInstance, reason) { + var modalWindow = openedWindows.get(modalInstance).value; + if (modalWindow) { + modalWindow.deferred.reject(reason); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismissAll = function (reason) { + var topModal = this.getTop(); + while (topModal) { + this.dismiss(topModal.key, reason); + topModal = this.getTop(); + } + }; + + $modalStack.getTop = function () { + return openedWindows.top(); + }; + + return $modalStack; + }]) + + .provider('$modal', function () { + + var $modalProvider = { + options: { + backdrop: true, //can be also false or 'static' + keyboard: true + }, + $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', + function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { + + var $modal = {}; + + function getTemplatePromise(options) { + return options.template ? $q.when(options.template) : + $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) { + return result.data; + }); + } + + function getResolvePromises(resolves) { + var promisesArr = []; + angular.forEach(resolves, function (value, key) { + if (angular.isFunction(value) || angular.isArray(value)) { + promisesArr.push($q.when($injector.invoke(value))); + } + }); + return promisesArr; + } + + $modal.open = function (modalOptions) { + + var modalResultDeferred = $q.defer(); + var modalOpenedDeferred = $q.defer(); + + //prepare an instance of a modal to be injected into controllers and returned to a caller + var modalInstance = { + result: modalResultDeferred.promise, + opened: modalOpenedDeferred.promise, + close: function (result) { + $modalStack.close(modalInstance, result); + }, + dismiss: function (reason) { + $modalStack.dismiss(modalInstance, reason); + } + }; + + //merge and clean up options + modalOptions = angular.extend({}, $modalProvider.options, modalOptions); + modalOptions.resolve = modalOptions.resolve || {}; + + //verify options + if (!modalOptions.template && !modalOptions.templateUrl) { + throw new Error('One of template or templateUrl options is required.'); + } + + var templateAndResolvePromise = + $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); + + + templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { + + var modalScope = (modalOptions.scope || $rootScope).$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; + + var ctrlInstance, ctrlLocals = {}; + var resolveIter = 1; + + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$modalInstance = modalInstance; + angular.forEach(modalOptions.resolve, function (value, key) { + ctrlLocals[key] = tplAndVars[resolveIter++]; + }); + + ctrlInstance = $controller(modalOptions.controller, ctrlLocals); + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + content: tplAndVars[0], + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + windowClass: modalOptions.windowClass, + windowTemplateUrl: modalOptions.windowTemplateUrl, + size: modalOptions.size + }); + + }, function resolveError(reason) { + modalResultDeferred.reject(reason); + }); + + templateAndResolvePromise.then(function () { + modalOpenedDeferred.resolve(true); + }, function () { + modalOpenedDeferred.reject(false); + }); + + return modalInstance; + }; + + return $modal; + }] + }; + + return $modalProvider; + }); + +angular.module('ui.bootstrap.pagination', []) + +.controller('PaginationController', ['$scope', '$attrs', '$parse', function ($scope, $attrs, $parse) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; + + this.init = function(ngModelCtrl_, config) { + ngModelCtrl = ngModelCtrl_; + this.config = config; + + ngModelCtrl.$render = function() { + self.render(); + }; + + if ($attrs.itemsPerPage) { + $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { + self.itemsPerPage = parseInt(value, 10); + $scope.totalPages = self.calculateTotalPages(); + }); + } else { + this.itemsPerPage = config.itemsPerPage; + } + }; + + this.calculateTotalPages = function() { + var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); + return Math.max(totalPages || 0, 1); + }; + + this.render = function() { + $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1; + }; + + $scope.selectPage = function(page) { + if ( $scope.page !== page && page > 0 && page <= $scope.totalPages) { + ngModelCtrl.$setViewValue(page); + ngModelCtrl.$render(); + } + }; + + $scope.getText = function( key ) { + return $scope[key + 'Text'] || self.config[key + 'Text']; + }; + $scope.noPrevious = function() { + return $scope.page === 1; + }; + $scope.noNext = function() { + return $scope.page === $scope.totalPages; + }; + + $scope.$watch('totalItems', function() { + $scope.totalPages = self.calculateTotalPages(); + }); + + $scope.$watch('totalPages', function(value) { + setNumPages($scope.$parent, value); // Readonly variable + + if ( $scope.page > value ) { + $scope.selectPage(value); + } else { + ngModelCtrl.$render(); + } + }); +}]) + +.constant('paginationConfig', { + itemsPerPage: 10, + boundaryLinks: false, + directionLinks: true, + firstText: 'First', + previousText: 'Previous', + nextText: 'Next', + lastText: 'Last', + rotate: true +}) + +.directive('pagination', ['$parse', 'paginationConfig', function($parse, paginationConfig) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + firstText: '@', + previousText: '@', + nextText: '@', + lastText: '@' + }, + require: ['pagination', '?ngModel'], + controller: 'PaginationController', + templateUrl: 'template/pagination/pagination.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + // Setup configuration parameters + var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize, + rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate; + scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks; + scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks; + + paginationCtrl.init(ngModelCtrl, paginationConfig); + + if (attrs.maxSize) { + scope.$parent.$watch($parse(attrs.maxSize), function(value) { + maxSize = parseInt(value, 10); + paginationCtrl.render(); + }); + } + + // Create page object used in template + function makePage(number, text, isActive) { + return { + number: number, + text: text, + active: isActive + }; + } + + function getPages(currentPage, totalPages) { + var pages = []; + + // Default page limits + var startPage = 1, endPage = totalPages; + var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages ); + + // recompute if maxSize + if ( isMaxSized ) { + if ( rotate ) { + // Current page is displayed in the middle of the visible ones + startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); + endPage = startPage + maxSize - 1; + + // Adjust if limit is exceeded + if (endPage > totalPages) { + endPage = totalPages; + startPage = endPage - maxSize + 1; + } + } else { + // Visible pages are paginated with maxSize + startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1; + + // Adjust last page if limit is exceeded + endPage = Math.min(startPage + maxSize - 1, totalPages); + } + } + + // Add page number links + for (var number = startPage; number <= endPage; number++) { + var page = makePage(number, number, number === currentPage); + pages.push(page); + } + + // Add links to move between page sets + if ( isMaxSized && ! rotate ) { + if ( startPage > 1 ) { + var previousPageSet = makePage(startPage - 1, '...', false); + pages.unshift(previousPageSet); + } + + if ( endPage < totalPages ) { + var nextPageSet = makePage(endPage + 1, '...', false); + pages.push(nextPageSet); + } + } + + return pages; + } + + var originalRender = paginationCtrl.render; + paginationCtrl.render = function() { + originalRender(); + if (scope.page > 0 && scope.page <= scope.totalPages) { + scope.pages = getPages(scope.page, scope.totalPages); + } + }; + } + }; +}]) + +.constant('pagerConfig', { + itemsPerPage: 10, + previousText: '« Previous', + nextText: 'Next »', + align: true +}) + +.directive('pager', ['pagerConfig', function(pagerConfig) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + previousText: '@', + nextText: '@' + }, + require: ['pager', '?ngModel'], + controller: 'PaginationController', + templateUrl: 'template/pagination/pager.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align; + paginationCtrl.init(ngModelCtrl, pagerConfig); + } + }; +}]); + +/** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ +angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider( '$tooltip', function () { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + animation: true, + popupDelay: 0 + }; + + // Default hide triggers for each show trigger + var triggerMap = { + 'mouseenter': 'mouseleave', + 'click': 'click', + 'focus': 'blur' + }; + + // The options specified to the provider globally. + var globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function( value ) { + angular.extend( globalOptions, value ); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); + */ + this.setTriggers = function setTriggers ( triggers ) { + angular.extend( triggerMap, triggers ); + }; + + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name){ + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) { + return function $tooltip ( type, prefix, defaultTriggerShow ) { + var options = angular.extend( {}, defaultOptions, globalOptions ); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers ( trigger ) { + var show = trigger || options.trigger || defaultTriggerShow; + var hide = triggerMap[show] || show; + return { + show: show, + hide: hide + }; + } + + var directiveName = snake_case( type ); + + var startSym = $interpolate.startSymbol(); + var endSym = $interpolate.endSymbol(); + var template = + '
'+ + '
'; + + return { + restrict: 'EA', + scope: true, + compile: function (tElem, tAttrs) { + var tooltipLinker = $compile( template ); + + return function link ( scope, element, attrs ) { + var tooltip; + var transitionTimeout; + var popupTimeout; + var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; + var triggers = getTriggers( undefined ); + var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + + var positionTooltip = function () { + + var ttPosition = $position.positionElements(element, tooltip, scope.tt_placement, appendToBody); + ttPosition.top += 'px'; + ttPosition.left += 'px'; + + // Now set the calculated positioning. + tooltip.css( ttPosition ); + }; + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + scope.tt_isOpen = false; + + function toggleTooltipBind () { + if ( ! scope.tt_isOpen ) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { + return; + } + if ( scope.tt_popupDelay ) { + // Do nothing if the tooltip was already scheduled to pop-up. + // This happens if show is triggered multiple times before any hide is triggered. + if (!popupTimeout) { + popupTimeout = $timeout( show, scope.tt_popupDelay, false ); + popupTimeout.then(function(reposition){reposition();}); + } + } else { + show()(); + } + } + + function hideTooltipBind () { + scope.$apply(function () { + hide(); + }); + } + + // Show the tooltip popup element. + function show() { + + popupTimeout = null; + + // If there is a pending remove transition, we must cancel it, lest the + // tooltip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + transitionTimeout = null; + } + + // Don't show empty tooltips. + if ( ! scope.tt_content ) { + return angular.noop; + } + + createTooltip(); + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + if ( appendToBody ) { + $document.find( 'body' ).append( tooltip ); + } else { + element.after( tooltip ); + } + + positionTooltip(); + + // And show the tooltip. + scope.tt_isOpen = true; + scope.$digest(); // digest required as $apply is not called + + // Return positioning function as promise callback for correct + // positioning after draw. + return positionTooltip; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + scope.tt_isOpen = false; + + //if tooltip is going to be shown after delay, we must cancel this + $timeout.cancel( popupTimeout ); + popupTimeout = null; + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( scope.tt_animation ) { + if (!transitionTimeout) { + transitionTimeout = $timeout(removeTooltip, 500); + } + } else { + removeTooltip(); + } + } + + function createTooltip() { + // There can only be one tooltip element per directive shown at once. + if (tooltip) { + removeTooltip(); + } + tooltip = tooltipLinker(scope, function () {}); + + // Get contents rendered into the tooltip + scope.$digest(); + } + + function removeTooltip() { + transitionTimeout = null; + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + } + + /** + * Observe the relevant attributes. + */ + attrs.$observe( type, function ( val ) { + scope.tt_content = val; + + if (!val && scope.tt_isOpen ) { + hide(); + } + }); + + attrs.$observe( prefix+'Title', function ( val ) { + scope.tt_title = val; + }); + + attrs.$observe( prefix+'Placement', function ( val ) { + scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + }); + + attrs.$observe( prefix+'PopupDelay', function ( val ) { + var delay = parseInt( val, 10 ); + scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + }); + + var unregisterTriggers = function () { + element.unbind(triggers.show, showTooltipBind); + element.unbind(triggers.hide, hideTooltipBind); + }; + + attrs.$observe( prefix+'Trigger', function ( val ) { + unregisterTriggers(); + + triggers = getTriggers( val ); + + if ( triggers.show === triggers.hide ) { + element.bind( triggers.show, toggleTooltipBind ); + } else { + element.bind( triggers.show, showTooltipBind ); + element.bind( triggers.hide, hideTooltipBind ); + } + }); + + var animation = scope.$eval(attrs[prefix + 'Animation']); + scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation; + + attrs.$observe( prefix+'AppendToBody', function ( val ) { + appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody; + }); + + // if a tooltip is attached to we need to remove it on + // location change as its parent scope will probably not be destroyed + // by the change. + if ( appendToBody ) { + scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { + if ( scope.tt_isOpen ) { + hide(); + } + }); + } + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + $timeout.cancel( transitionTimeout ); + $timeout.cancel( popupTimeout ); + unregisterTriggers(); + removeTooltip(); + }); + }; + } + }; + }; + }]; +}) + +.directive( 'tooltipPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html' + }; +}) + +.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); +}]) + +.directive( 'tooltipHtmlUnsafePopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + }; +}) + +.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); +}]); + +/** + * The following features are still outstanding: popup delay, animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html popovers, and selector delegatation. + */ +angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) + +.directive( 'popoverPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover.html' + }; +}) + +.directive( 'popover', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'popover', 'popover', 'click' ); +}]); + +angular.module('ui.bootstrap.progressbar', []) + +.constant('progressConfig', { + animate: true, + max: 100 +}) + +.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) { + var self = this, + animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; + + this.bars = []; + $scope.max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max; + + this.addBar = function(bar, element) { + if ( !animate ) { + element.css({'transition': 'none'}); + } + + this.bars.push(bar); + + bar.$watch('value', function( value ) { + bar.percent = +(100 * value / $scope.max).toFixed(2); + }); + + bar.$on('$destroy', function() { + element = null; + self.removeBar(bar); + }); + }; + + this.removeBar = function(bar) { + this.bars.splice(this.bars.indexOf(bar), 1); + }; +}]) + +.directive('progress', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + require: 'progress', + scope: {}, + templateUrl: 'template/progressbar/progress.html' + }; +}) + +.directive('bar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + require: '^progress', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/bar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, element); + } + }; +}) + +.directive('progressbar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/progressbar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, angular.element(element.children()[0])); + } + }; +}); +angular.module('ui.bootstrap.rating', []) + +.constant('ratingConfig', { + max: 5, + stateOn: null, + stateOff: null +}) + +.controller('RatingController', ['$scope', '$attrs', 'ratingConfig', function($scope, $attrs, ratingConfig) { + var ngModelCtrl = { $setViewValue: angular.noop }; + + this.init = function(ngModelCtrl_) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; + this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; + + var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) : + new Array( angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max ); + $scope.range = this.buildTemplateObjects(ratingStates); + }; + + this.buildTemplateObjects = function(states) { + for (var i = 0, n = states.length; i < n; i++) { + states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff }, states[i]); + } + return states; + }; + + $scope.rate = function(value) { + if ( !$scope.readonly && value >= 0 && value <= $scope.range.length ) { + ngModelCtrl.$setViewValue(value); + ngModelCtrl.$render(); + } + }; + + $scope.enter = function(value) { + if ( !$scope.readonly ) { + $scope.value = value; + } + $scope.onHover({value: value}); + }; + + $scope.reset = function() { + $scope.value = ngModelCtrl.$viewValue; + $scope.onLeave(); + }; + + $scope.onKeydown = function(evt) { + if (/(37|38|39|40)/.test(evt.which)) { + evt.preventDefault(); + evt.stopPropagation(); + $scope.rate( $scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1) ); + } + }; + + this.render = function() { + $scope.value = ngModelCtrl.$viewValue; + }; +}]) + +.directive('rating', function() { + return { + restrict: 'EA', + require: ['rating', 'ngModel'], + scope: { + readonly: '=?', + onHover: '&', + onLeave: '&' + }, + controller: 'RatingController', + templateUrl: 'template/rating/rating.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + ratingCtrl.init( ngModelCtrl ); + } + } + }; +}); + +/** + * @ngdoc overview + * @name ui.bootstrap.tabs + * + * @description + * AngularJS version of the tabs directive. + */ + +angular.module('ui.bootstrap.tabs', []) + +.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { + var ctrl = this, + tabs = ctrl.tabs = $scope.tabs = []; + + ctrl.select = function(selectedTab) { + angular.forEach(tabs, function(tab) { + if (tab.active && tab !== selectedTab) { + tab.active = false; + tab.onDeselect(); + } + }); + selectedTab.active = true; + selectedTab.onSelect(); + }; + + ctrl.addTab = function addTab(tab) { + tabs.push(tab); + // we can't run the select function on the first tab + // since that would select it twice + if (tabs.length === 1) { + tab.active = true; + } else if (tab.active) { + ctrl.select(tab); + } + }; + + ctrl.removeTab = function removeTab(tab) { + var index = tabs.indexOf(tab); + //Select a new tab if the tab to be removed is selected + if (tab.active && tabs.length > 1) { + //If this is the last tab, select the previous tab. else, the next tab. + var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; + ctrl.select(tabs[newActiveIndex]); + } + tabs.splice(index, 1); + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabset + * @restrict EA + * + * @description + * Tabset is the outer container for the tabs directive + * + * @param {boolean=} vertical Whether or not to use vertical styling for the tabs. + * @param {boolean=} justified Whether or not to use justified styling for the tabs. + * + * @example + + + + First Content! + Second Content! + +
+ + First Vertical Content! + Second Vertical Content! + + + First Justified Content! + Second Justified Content! + +
+
+ */ +.directive('tabset', function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + scope: { + type: '@' + }, + controller: 'TabsetController', + templateUrl: 'template/tabs/tabset.html', + link: function(scope, element, attrs) { + scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; + scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; + } + }; +}) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tab + * @restrict EA + * + * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. + * @param {string=} select An expression to evaluate when the tab is selected. + * @param {boolean=} active A binding, telling whether or not this tab is selected. + * @param {boolean=} disabled A binding, telling whether or not this tab is disabled. + * + * @description + * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. + * + * @example + + +
+ + +
+ + First Tab + + Alert me! + Second Tab, with alert callback and html heading! + + + {{item.content}} + + +
+
+ + function TabsDemoCtrl($scope) { + $scope.items = [ + { title:"Dynamic Title 1", content:"Dynamic Item 0" }, + { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } + ]; + + $scope.alertMe = function() { + setTimeout(function() { + alert("You've selected the alert tab!"); + }); + }; + }; + +
+ */ + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabHeading + * @restrict EA + * + * @description + * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. + * + * @example + + + + + HTML in my titles?! + And some content, too! + + + Icon heading?!? + That's right. + + + + + */ +.directive('tab', ['$parse', function($parse) { + return { + require: '^tabset', + restrict: 'EA', + replace: true, + templateUrl: 'template/tabs/tab.html', + transclude: true, + scope: { + active: '=?', + heading: '@', + onSelect: '&select', //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + onDeselect: '&deselect' + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab + }, + compile: function(elm, attrs, transclude) { + return function postLink(scope, elm, attrs, tabsetCtrl) { + scope.$watch('active', function(active) { + if (active) { + tabsetCtrl.select(scope); + } + }); + + scope.disabled = false; + if ( attrs.disabled ) { + scope.$parent.$watch($parse(attrs.disabled), function(value) { + scope.disabled = !! value; + }); + } + + scope.select = function() { + if ( !scope.disabled ) { + scope.active = true; + } + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; + }; + } + }; +}]) + +.directive('tabHeadingTransclude', [function() { + return { + restrict: 'A', + require: '^tab', + link: function(scope, elm, attrs, tabCtrl) { + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); + } + }); + } + }; +}]) + +.directive('tabContentTransclude', function() { + return { + restrict: 'A', + require: '^tabset', + link: function(scope, elm, attrs) { + var tab = scope.$eval(attrs.tabContentTransclude); + + //Now our tab is ready to be transcluded: both the tab heading area + //and the tab content area are loaded. Transclude 'em both. + tab.$transcludeFn(tab.$parent, function(contents) { + angular.forEach(contents, function(node) { + if (isTabHeading(node)) { + //Let tabHeadingTransclude know. + tab.headingElement = node; + } else { + elm.append(node); + } + }); + }); + } + }; + function isTabHeading(node) { + return node.tagName && ( + node.hasAttribute('tab-heading') || + node.hasAttribute('data-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || + node.tagName.toLowerCase() === 'data-tab-heading' + ); + } +}) + +; + +angular.module('ui.bootstrap.timepicker', []) + +.constant('timepickerConfig', { + hourStep: 1, + minuteStep: 1, + showMeridian: true, + meridians: null, + readonlyInput: false, + mousewheel: true +}) + +.controller('TimepickerController', ['$scope', '$attrs', '$parse', '$log', '$locale', 'timepickerConfig', function($scope, $attrs, $parse, $log, $locale, timepickerConfig) { + var selected = new Date(), + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; + + this.init = function( ngModelCtrl_, inputs ) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + var hoursInputEl = inputs.eq(0), + minutesInputEl = inputs.eq(1); + + var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; + if ( mousewheel ) { + this.setupMousewheelEvents( hoursInputEl, minutesInputEl ); + } + + $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; + this.setupInputEvents( hoursInputEl, minutesInputEl ); + }; + + var hourStep = timepickerConfig.hourStep; + if ($attrs.hourStep) { + $scope.$parent.$watch($parse($attrs.hourStep), function(value) { + hourStep = parseInt(value, 10); + }); + } + + var minuteStep = timepickerConfig.minuteStep; + if ($attrs.minuteStep) { + $scope.$parent.$watch($parse($attrs.minuteStep), function(value) { + minuteStep = parseInt(value, 10); + }); + } + + // 12H / 24H mode + $scope.showMeridian = timepickerConfig.showMeridian; + if ($attrs.showMeridian) { + $scope.$parent.$watch($parse($attrs.showMeridian), function(value) { + $scope.showMeridian = !!value; + + if ( ngModelCtrl.$error.time ) { + // Evaluate from template + var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); + if (angular.isDefined( hours ) && angular.isDefined( minutes )) { + selected.setHours( hours ); + refresh(); + } + } else { + updateTemplate(); + } + }); + } + + // Get $scope.hours in 24H mode if valid + function getHoursFromTemplate ( ) { + var hours = parseInt( $scope.hours, 10 ); + var valid = ( $scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); + if ( !valid ) { + return undefined; + } + + if ( $scope.showMeridian ) { + if ( hours === 12 ) { + hours = 0; + } + if ( $scope.meridian === meridians[1] ) { + hours = hours + 12; + } + } + return hours; + } + + function getMinutesFromTemplate() { + var minutes = parseInt($scope.minutes, 10); + return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined; + } + + function pad( value ) { + return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value; + } + + // Respond on mousewheel spin + this.setupMousewheelEvents = function( hoursInputEl, minutesInputEl ) { + var isScrollingUp = function(e) { + if (e.originalEvent) { + e = e.originalEvent; + } + //pick correct delta variable depending on event + var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY; + return (e.detail || delta > 0); + }; + + hoursInputEl.bind('mousewheel wheel', function(e) { + $scope.$apply( (isScrollingUp(e)) ? $scope.incrementHours() : $scope.decrementHours() ); + e.preventDefault(); + }); + + minutesInputEl.bind('mousewheel wheel', function(e) { + $scope.$apply( (isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes() ); + e.preventDefault(); + }); + + }; + + this.setupInputEvents = function( hoursInputEl, minutesInputEl ) { + if ( $scope.readonlyInput ) { + $scope.updateHours = angular.noop; + $scope.updateMinutes = angular.noop; + return; + } + + var invalidate = function(invalidHours, invalidMinutes) { + ngModelCtrl.$setViewValue( null ); + ngModelCtrl.$setValidity('time', false); + if (angular.isDefined(invalidHours)) { + $scope.invalidHours = invalidHours; + } + if (angular.isDefined(invalidMinutes)) { + $scope.invalidMinutes = invalidMinutes; + } + }; + + $scope.updateHours = function() { + var hours = getHoursFromTemplate(); + + if ( angular.isDefined(hours) ) { + selected.setHours( hours ); + refresh( 'h' ); + } else { + invalidate(true); + } + }; + + hoursInputEl.bind('blur', function(e) { + if ( !$scope.invalidHours && $scope.hours < 10) { + $scope.$apply( function() { + $scope.hours = pad( $scope.hours ); + }); + } + }); + + $scope.updateMinutes = function() { + var minutes = getMinutesFromTemplate(); + + if ( angular.isDefined(minutes) ) { + selected.setMinutes( minutes ); + refresh( 'm' ); + } else { + invalidate(undefined, true); + } + }; + + minutesInputEl.bind('blur', function(e) { + if ( !$scope.invalidMinutes && $scope.minutes < 10 ) { + $scope.$apply( function() { + $scope.minutes = pad( $scope.minutes ); + }); + } + }); + + }; + + this.render = function() { + var date = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : null; + + if ( isNaN(date) ) { + ngModelCtrl.$setValidity('time', false); + $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } else { + if ( date ) { + selected = date; + } + makeValid(); + updateTemplate(); + } + }; + + // Call internally when we know that model is valid. + function refresh( keyboardChange ) { + makeValid(); + ngModelCtrl.$setViewValue( new Date(selected) ); + updateTemplate( keyboardChange ); + } + + function makeValid() { + ngModelCtrl.$setValidity('time', true); + $scope.invalidHours = false; + $scope.invalidMinutes = false; + } + + function updateTemplate( keyboardChange ) { + var hours = selected.getHours(), minutes = selected.getMinutes(); + + if ( $scope.showMeridian ) { + hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system + } + + $scope.hours = keyboardChange === 'h' ? hours : pad(hours); + $scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes); + $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; + } + + function addMinutes( minutes ) { + var dt = new Date( selected.getTime() + minutes * 60000 ); + selected.setHours( dt.getHours(), dt.getMinutes() ); + refresh(); + } + + $scope.incrementHours = function() { + addMinutes( hourStep * 60 ); + }; + $scope.decrementHours = function() { + addMinutes( - hourStep * 60 ); + }; + $scope.incrementMinutes = function() { + addMinutes( minuteStep ); + }; + $scope.decrementMinutes = function() { + addMinutes( - minuteStep ); + }; + $scope.toggleMeridian = function() { + addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) ); + }; +}]) + +.directive('timepicker', function () { + return { + restrict: 'EA', + require: ['timepicker', '?^ngModel'], + controller:'TimepickerController', + replace: true, + scope: {}, + templateUrl: 'template/timepicker/timepicker.html', + link: function(scope, element, attrs, ctrls) { + var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + timepickerCtrl.init( ngModelCtrl, element.find('input') ); + } + } + }; +}); + +angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml']) + +/** + * A helper service that can parse typeahead's syntax (string provided by users) + * Extracted to a separate service for ease of unit testing + */ + .factory('typeaheadParser', ['$parse', function ($parse) { + + // 00000111000000000000022200000000000000003333333333333330000000000044000 + var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/; + + return { + parse:function (input) { + + var match = input.match(TYPEAHEAD_REGEXP); + if (!match) { + throw new Error( + 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + + ' but got "' + input + '".'); + } + + return { + itemName:match[3], + source:$parse(match[4]), + viewMapper:$parse(match[2] || match[1]), + modelMapper:$parse(match[1]) + }; + } + }; +}]) + + .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', + function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) { + + var HOT_KEYS = [9, 13, 27, 38, 40]; + + return { + require:'ngModel', + link:function (originalScope, element, attrs, modelCtrl) { + + //SUPPORTED ATTRIBUTES (OPTIONS) + + //minimal no of characters that needs to be entered before typeahead kicks-in + var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; + + //minimal wait time after last character typed before typehead kicks-in + var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; + + //should it restrict model values to the ones selected from the popup only? + var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; + + //binding to a variable that indicates if matches are being retrieved asynchronously + var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + + //a callback executed when a match is selected + var onSelectCallback = $parse(attrs.typeaheadOnSelect); + + var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; + + var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; + + //INTERNAL VARIABLES + + //model setter executed upon match selection + var $setModelValue = $parse(attrs.ngModel).assign; + + //expressions used by typeahead + var parserResult = typeaheadParser.parse(attrs.typeahead); + + var hasFocus; + + //create a child scope for the typeahead directive so we are not polluting original scope + //with typeahead-specific data (matches, query etc.) + var scope = originalScope.$new(); + originalScope.$on('$destroy', function(){ + scope.$destroy(); + }); + + // WAI-ARIA + var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); + element.attr({ + 'aria-autocomplete': 'list', + 'aria-expanded': false, + 'aria-owns': popupId + }); + + //pop-up element used to display matches + var popUpEl = angular.element('
'); + popUpEl.attr({ + id: popupId, + matches: 'matches', + active: 'activeIdx', + select: 'select(activeIdx)', + query: 'query', + position: 'position' + }); + //custom item template + if (angular.isDefined(attrs.typeaheadTemplateUrl)) { + popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); + } + + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + element.attr('aria-expanded', false); + }; + + var getMatchId = function(index) { + return popupId + '-option-' + index; + }; + + // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. + // This attribute is added or removed automatically when the `activeIdx` changes. + scope.$watch('activeIdx', function(index) { + if (index < 0) { + element.removeAttr('aria-activedescendant'); + } else { + element.attr('aria-activedescendant', getMatchId(index)); + } + }); + + var getMatchesAsync = function(inputValue) { + + var locals = {$viewValue: inputValue}; + isLoadingSetter(originalScope, true); + $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + + //it might happen that several async queries were in progress if a user were typing fast + //but we are interested only in responses that correspond to the current view value + var onCurrentRequest = (inputValue === modelCtrl.$viewValue); + if (onCurrentRequest && hasFocus) { + if (matches.length > 0) { + + scope.activeIdx = 0; + scope.matches.length = 0; + + //transform labels + for(var i=0; i= minSearch) { + if (waitTime > 0) { + if (timeoutPromise) { + $timeout.cancel(timeoutPromise);//cancel previous timeout + } + timeoutPromise = $timeout(function () { + getMatchesAsync(inputValue); + }, waitTime); + } else { + getMatchesAsync(inputValue); + } + } else { + isLoadingSetter(originalScope, false); + resetMatches(); + } + + if (isEditable) { + return inputValue; + } else { + if (!inputValue) { + // Reset in case user had typed something previously. + modelCtrl.$setValidity('editable', true); + return inputValue; + } else { + modelCtrl.$setValidity('editable', false); + return undefined; + } + } + }); + + modelCtrl.$formatters.push(function (modelValue) { + + var candidateViewValue, emptyViewValue; + var locals = {}; + + if (inputFormatter) { + + locals['$model'] = modelValue; + return inputFormatter(originalScope, locals); + + } else { + + //it might happen that we don't have enough info to properly render input value + //we need to check for this situation and simply return model value if we can't apply custom formatting + locals[parserResult.itemName] = modelValue; + candidateViewValue = parserResult.viewMapper(originalScope, locals); + locals[parserResult.itemName] = undefined; + emptyViewValue = parserResult.viewMapper(originalScope, locals); + + return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; + } + }); + + scope.select = function (activeIdx) { + //called from within the $digest() cycle + var locals = {}; + var model, item; + + locals[parserResult.itemName] = item = scope.matches[activeIdx].model; + model = parserResult.modelMapper(originalScope, locals); + $setModelValue(originalScope, model); + modelCtrl.$setValidity('editable', true); + + onSelectCallback(originalScope, { + $item: item, + $model: model, + $label: parserResult.viewMapper(originalScope, locals) + }); + + resetMatches(); + + //return focus to the input element if a match was selected via a mouse click event + // use timeout to avoid $rootScope:inprog error + $timeout(function() { element[0].focus(); }, 0, false); + }; + + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) + element.bind('keydown', function (evt) { + + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } + + evt.preventDefault(); + + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); + + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); + + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); + + } else if (evt.which === 27) { + evt.stopPropagation(); + + resetMatches(); + scope.$digest(); + } + }); + + element.bind('blur', function (evt) { + hasFocus = false; + }); + + // Keep reference to click handler to unbind it. + var dismissClickHandler = function (evt) { + if (element[0] !== evt.target) { + resetMatches(); + scope.$digest(); + } + }; + + $document.bind('click', dismissClickHandler); + + originalScope.$on('$destroy', function(){ + $document.unbind('click', dismissClickHandler); + }); + + var $popup = $compile(popUpEl)(scope); + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + } + }; + +}]) + + .directive('typeaheadPopup', function () { + return { + restrict:'EA', + scope:{ + matches:'=', + query:'=', + active:'=', + position:'=', + select:'&' + }, + replace:true, + templateUrl:'template/typeahead/typeahead-popup.html', + link:function (scope, element, attrs) { + + scope.templateUrl = attrs.templateUrl; + + scope.isOpen = function () { + return scope.matches.length > 0; + }; + + scope.isActive = function (matchIdx) { + return scope.active == matchIdx; + }; + + scope.selectActive = function (matchIdx) { + scope.active = matchIdx; + }; + + scope.selectMatch = function (activeIdx) { + scope.select({activeIdx:activeIdx}); + }; + } + }; + }) + + .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) { + return { + restrict:'EA', + scope:{ + index:'=', + match:'=', + query:'=' + }, + link:function (scope, element, attrs) { + var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; + $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){ + element.replaceWith($compile(tplContent.trim())(scope)); + }); + } + }; + }]) + + .filter('typeaheadHighlight', function() { + + function escapeRegexp(queryToEscape) { + return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); + } + + return function(matchItem, query) { + return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; + }; + }); + +angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/accordion/accordion-group.html", + "
\n" + + "
\n" + + "

\n" + + " {{heading}}\n" + + "

\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
"); +}]); + +angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/accordion/accordion.html", + "
"); +}]); + +angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/alert/alert.html", + "
\n" + + " \n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/carousel/carousel.html", + "
\n" + + "
    1\">\n" + + "
  1. \n" + + "
\n" + + "
\n" + + " 1\">\n" + + " 1\">\n" + + "
\n" + + ""); +}]); + +angular.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/carousel/slide.html", + "
\n" + + ""); +}]); + +angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/datepicker.html", + "
\n" + + " \n" + + " \n" + + " \n" + + "
"); +}]); + +angular.module("template/datepicker/day.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/day.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
{{label.abbr}}
{{ weekNumbers[$index] }}\n" + + " \n" + + "
\n" + + ""); +}]); + +angular.module("template/datepicker/month.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/month.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + ""); +}]); + +angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/popup.html", + "
    \n" + + "
  • \n" + + "
  • \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
  • \n" + + "
\n" + + ""); +}]); + +angular.module("template/datepicker/year.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/year.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + ""); +}]); + +angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/modal/backdrop.html", + "
\n" + + ""); +}]); + +angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/modal/window.html", + "
\n" + + "
\n" + + "
"); +}]); + +angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/pagination/pager.html", + ""); +}]); + +angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/pagination/pagination.html", + ""); +}]); + +angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html", + "
\n" + + "
\n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-popup.html", + "
\n" + + "
\n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/popover/popover.html", + "
\n" + + "
\n" + + "\n" + + "
\n" + + "

\n" + + "
\n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/bar.html", + "
"); +}]); + +angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/progress.html", + "
"); +}]); + +angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/progressbar.html", + "
\n" + + "
\n" + + "
"); +}]); + +angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/rating/rating.html", + "\n" + + " \n" + + " ({{ $index < value ? '*' : ' ' }})\n" + + " \n" + + ""); +}]); + +angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tabs/tab.html", + "
  • \n" + + " {{heading}}\n" + + "
  • \n" + + ""); +}]); + +angular.module("template/tabs/tabset-titles.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tabs/tabset-titles.html", + "
      \n" + + "
    \n" + + ""); +}]); + +angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tabs/tabset.html", + "\n" + + "
    \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/timepicker/timepicker.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
       
      \n" + + " \n" + + " :\n" + + " \n" + + "
       
      \n" + + ""); +}]); + +angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/typeahead/typeahead-match.html", + ""); +}]); + +angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/typeahead/typeahead-popup.html", + "
        \n" + + "
      • \n" + + "
        \n" + + "
      • \n" + + "
      "); +}]); diff --git a/src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap-tpls.min.js b/src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap-tpls.min.js new file mode 100644 index 0000000..fa6a861 --- /dev/null +++ b/src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap-tpls.min.js @@ -0,0 +1,10 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.11.0 - 2014-05-01 + * License: MIT + */ +angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(a){return{link:function(b,c,d){function e(b){function d(){j===e&&(j=void 0)}var e=a(c,b);return j&&j.cancel(),j=e,e.then(d,d),e}function f(){k?(k=!1,g()):(c.removeClass("collapse").addClass("collapsing"),e({height:c[0].scrollHeight+"px"}).then(g))}function g(){c.removeClass("collapsing"),c.addClass("collapse in"),c.css({height:"auto"})}function h(){if(k)k=!1,i(),c.css({height:0});else{c.css({height:c[0].scrollHeight+"px"});{c[0].offsetWidth}c.removeClass("collapse in").addClass("collapsing"),e({height:0}).then(i)}}function i(){c.removeClass("collapsing"),c.addClass("collapse")}var j,k=!0;b.$watch(d.collapse,function(a){a?h():f()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.$watch("isOpen",function(b){b&&d.closeOthers(a)}),a.toggleOpen=function(){a.isDisabled||(a.isOpen=!a.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("ui.bootstrap.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}),angular.module("ui.bootstrap.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("ui.bootstrap.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){var d=b.hasClass(e.activeClass);(!d||angular.isDefined(c.uncheckable))&&a.$apply(function(){f.$setViewValue(d?null:a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.carousel",["ui.bootstrap.transition"]).controller("CarouselController",["$scope","$timeout","$transition",function(a,b,c){function d(){e();var c=+a.interval;!isNaN(c)&&c>=0&&(g=b(f,c))}function e(){g&&(b.cancel(g),g=null)}function f(){h?(a.next(),d()):a.pause()}var g,h,i=this,j=i.slides=a.slides=[],k=-1;i.currentSlide=null;var l=!1;i.select=a.select=function(e,f){function g(){if(!l){if(i.currentSlide&&angular.isString(f)&&!a.noTransition&&e.$element){e.$element.addClass(f);{e.$element[0].offsetWidth}angular.forEach(j,function(a){angular.extend(a,{direction:"",entering:!1,leaving:!1,active:!1})}),angular.extend(e,{direction:f,active:!0,entering:!0}),angular.extend(i.currentSlide||{},{direction:f,leaving:!0}),a.$currentTransition=c(e.$element,{}),function(b,c){a.$currentTransition.then(function(){h(b,c)},function(){h(b,c)})}(e,i.currentSlide)}else h(e,i.currentSlide);i.currentSlide=e,k=m,d()}}function h(b,c){angular.extend(b,{direction:"",active:!0,leaving:!1,entering:!1}),angular.extend(c||{},{direction:"",active:!1,leaving:!1,entering:!1}),a.$currentTransition=null}var m=j.indexOf(e);void 0===f&&(f=m>k?"next":"prev"),e&&e!==i.currentSlide&&(a.$currentTransition?(a.$currentTransition.cancel(),b(g)):g())},a.$on("$destroy",function(){l=!0}),i.indexOfSlide=function(a){return j.indexOf(a)},a.next=function(){var b=(k+1)%j.length;return a.$currentTransition?void 0:i.select(j[b],"next")},a.prev=function(){var b=0>k-1?j.length-1:k-1;return a.$currentTransition?void 0:i.select(j[b],"prev")},a.isActive=function(a){return i.currentSlide===a},a.$watch("interval",d),a.$on("$destroy",e),a.play=function(){h||(h=!0,d())},a.pause=function(){a.noPause||(h=!1,e())},i.addSlide=function(b,c){b.$element=c,j.push(b),1===j.length||b.active?(i.select(j[j.length-1]),1==j.length&&a.play()):b.active=!1},i.removeSlide=function(a){var b=j.indexOf(a);j.splice(b,1),j.length>0&&a.active?i.select(b>=j.length?j[b-1]:j[b]):k>b&&k--}}]).directive("carousel",[function(){return{restrict:"EA",transclude:!0,replace:!0,controller:"CarouselController",require:"carousel",templateUrl:"template/carousel/carousel.html",scope:{interval:"=",noTransition:"=",noPause:"="}}}]).directive("slide",function(){return{require:"^carousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/carousel/slide.html",scope:{active:"=?"},link:function(a,b,c,d){d.addSlide(a,b),a.$on("$destroy",function(){d.removeSlide(a)}),a.$watch("active",function(b){b&&d.select(a)})}}}),angular.module("ui.bootstrap.dateparser",[]).service("dateParser",["$locale","orderByFilter",function(a,b){function c(a,b,c){return 1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}this.parsers={};var d={yyyy:{regex:"\\d{4}",apply:function(a){this.year=+a}},yy:{regex:"\\d{2}",apply:function(a){this.year=+a+2e3}},y:{regex:"\\d{1,4}",apply:function(a){this.year=+a}},MMMM:{regex:a.DATETIME_FORMATS.MONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.MONTH.indexOf(b)}},MMM:{regex:a.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.SHORTMONTH.indexOf(b)}},MM:{regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1}},M:{regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1}},dd:{regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},d:{regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},EEEE:{regex:a.DATETIME_FORMATS.DAY.join("|")},EEE:{regex:a.DATETIME_FORMATS.SHORTDAY.join("|")}};this.createParser=function(a){var c=[],e=a.split("");return angular.forEach(d,function(b,d){var f=a.indexOf(d);if(f>-1){a=a.split(""),e[f]="("+b.regex+")",a[f]="$";for(var g=f+1,h=f+d.length;h>g;g++)e[g]="",a[g]="$";a=a.join(""),c.push({index:f,apply:b.apply})}}),{regex:new RegExp("^"+e.join("")+"$"),map:b(c,"index")}},this.parse=function(b,d){if(!angular.isString(b))return b;d=a.DATETIME_FORMATS[d]||d,this.parsers[d]||(this.parsers[d]=this.createParser(d));var e=this.parsers[d],f=e.regex,g=e.map,h=b.match(f);if(h&&h.length){for(var i,j={year:1900,month:0,date:1,hours:0},k=1,l=h.length;l>k;k++){var m=g[k-1];m.apply&&m.apply.call(j,h[k])}return c(j.year,j.month,j.date)&&(i=new Date(j.year,j.month,j.date,j.hours)),i}}}]),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].documentElement.scrollLeft)}},positionElements:function(a,b,c,d){var e,f,g,h,i=c.split("-"),j=i[0],k=i[1]||"center";e=d?this.offset(a):this.position(a),f=b.prop("offsetWidth"),g=b.prop("offsetHeight");var l={center:function(){return e.left+e.width/2-f/2},left:function(){return e.left},right:function(){return e.left+e.width}},m={center:function(){return e.top+e.height/2-g/2},top:function(){return e.top},bottom:function(){return e.top+e.height}};switch(j){case"right":h={top:m[k](),left:l[j]()};break;case"left":h={top:m[k](),left:e.left-f};break;case"bottom":h={top:m[j](),left:l[k]()};break;default:h={top:e.top-g,left:l[k]()}}return h}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.position"]).constant("datepickerConfig",{formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",datepickerMode:"day",minMode:"day",maxMode:"year",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null}).controller("DatepickerController",["$scope","$attrs","$parse","$interpolate","$timeout","$log","dateFilter","datepickerConfig",function(a,b,c,d,e,f,g,h){var i=this,j={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","minMode","maxMode","showWeeks","startingDay","yearRange"],function(c,e){i[c]=angular.isDefined(b[c])?8>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):h[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){i[d]=a?new Date(a):null,i.refreshView()}):i[d]=h[d]?new Date(h[d]):null}),a.datepickerMode=a.datepickerMode||h.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),this.activeDate=angular.isDefined(b.initDate)?a.$parent.$eval(b.initDate):new Date,a.isActive=function(b){return 0===i.compare(b.date,i.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){j=a,j.$render=function(){i.render()}},this.render=function(){if(j.$modelValue){var a=new Date(j.$modelValue),b=!isNaN(a);b?this.activeDate=a:f.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'),j.$setValidity("date",b)}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=j.$modelValue?new Date(j.$modelValue):null;j.$setValidity("date-disabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=j.$modelValue?new Date(j.$modelValue):null;return{date:a,label:g(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===i.minMode){var c=j.$modelValue?new Date(j.$modelValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),j.$setViewValue(c),j.$render()}else i.activeDate=b,a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=i.activeDate.getFullYear()+a*(i.step.years||0),c=i.activeDate.getMonth()+a*(i.step.months||0);i.activeDate.setFullYear(b,c,1),i.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===i.maxMode&&1===b||a.datepickerMode===i.minMode&&-1===b||(a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var k=function(){e(function(){i.element[0].focus()},0,!1)};a.$on("datepicker.focus",k),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),b.stopPropagation(),"enter"===c||"space"===c){if(i.isDisabled(i.activeDate))return;a.select(i.activeDate),k()}else!b.ctrlKey||"up"!==c&&"down"!==c?(i.handleKeyDown(c,b),i.refreshView()):(a.toggleMode("up"===c?1:-1),k())}}]).directive("datepicker",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/datepicker.html",scope:{datepickerMode:"=?",dateDisabled:"&"},require:["datepicker","?^ngModel"],controller:"DatepickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}).directive("daypicker",["dateFilter",function(a){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/day.html",require:"^datepicker",link:function(b,c,d,e){function f(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?i[b]:29}function g(a,b){var c=new Array(b),d=new Date(a),e=0;for(d.setHours(12);b>e;)c[e++]=new Date(d),d.setDate(d.getDate()+1);return c}function h(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}b.showWeeks=e.showWeeks,e.step={months:1},e.element=c;var i=[31,28,31,30,31,30,31,31,30,31,30,31];e._refreshView=function(){var c=e.activeDate.getFullYear(),d=e.activeDate.getMonth(),f=new Date(c,d,1),i=e.startingDay-f.getDay(),j=i>0?7-i:-i,k=new Date(f);j>0&&k.setDate(-j+1);for(var l=g(k,42),m=0;42>m;m++)l[m]=angular.extend(e.createDateObject(l[m],e.formatDay),{secondary:l[m].getMonth()!==d,uid:b.uniqueId+"-"+m});b.labels=new Array(7);for(var n=0;7>n;n++)b.labels[n]={abbr:a(l[n].date,e.formatDayHeader),full:a(l[n].date,"EEEE")};if(b.title=a(e.activeDate,e.formatDayTitle),b.rows=e.split(l,7),b.showWeeks){b.weekNumbers=[];for(var o=h(b.rows[0][0].date),p=b.rows.length;b.weekNumbers.push(o++)f;f++)c[f]=angular.extend(e.createDateObject(new Date(d,f,1),e.formatMonth),{uid:b.uniqueId+"-"+f});b.title=a(e.activeDate,e.formatMonthTitle),b.rows=e.split(c,3)},e.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth())-new Date(b.getFullYear(),b.getMonth())},e.handleKeyDown=function(a){var b=e.activeDate.getMonth();if("left"===a)b-=1;else if("up"===a)b-=3;else if("right"===a)b+=1;else if("down"===a)b+=3;else if("pageup"===a||"pagedown"===a){var c=e.activeDate.getFullYear()+("pageup"===a?-1:1);e.activeDate.setFullYear(c)}else"home"===a?b=0:"end"===a&&(b=11);e.activeDate.setMonth(b)},e.refreshView()}}}]).directive("yearpicker",["dateFilter",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/year.html",require:"^datepicker",link:function(a,b,c,d){function e(a){return parseInt((a-1)/f,10)*f+1}var f=d.yearRange;d.step={years:f},d.element=b,d._refreshView=function(){for(var b=new Array(f),c=0,g=e(d.activeDate.getFullYear());f>c;c++)b[c]=angular.extend(d.createDateObject(new Date(g+c,0,1),d.formatYear),{uid:a.uniqueId+"-"+c});a.title=[b[0].label,b[f-1].label].join(" - "),a.rows=d.split(b,5)},d.compare=function(a,b){return a.getFullYear()-b.getFullYear()},d.handleKeyDown=function(a){var b=d.activeDate.getFullYear();"left"===a?b-=1:"up"===a?b-=5:"right"===a?b+=1:"down"===a?b+=5:"pageup"===a||"pagedown"===a?b+=("pageup"===a?-1:1)*d.step.years:"home"===a?b=e(d.activeDate.getFullYear()):"end"===a&&(b=e(d.activeDate.getFullYear())+f-1),d.activeDate.setFullYear(b)},d.refreshView()}}}]).constant("datepickerPopupConfig",{datepickerPopup:"yyyy-MM-dd",currentText:"Today",clearText:"Clear",closeText:"Done",closeOnDateSelection:!0,appendToBody:!1,showButtonBar:!0}).directive("datepickerPopup",["$compile","$parse","$document","$position","dateFilter","dateParser","datepickerPopupConfig",function(a,b,c,d,e,f,g){return{restrict:"EA",require:"ngModel",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&"},link:function(h,i,j,k){function l(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}function m(a){if(a){if(angular.isDate(a)&&!isNaN(a))return k.$setValidity("date",!0),a;if(angular.isString(a)){var b=f.parse(a,n)||new Date(a);return isNaN(b)?void k.$setValidity("date",!1):(k.$setValidity("date",!0),b)}return void k.$setValidity("date",!1)}return k.$setValidity("date",!0),null}var n,o=angular.isDefined(j.closeOnDateSelection)?h.$parent.$eval(j.closeOnDateSelection):g.closeOnDateSelection,p=angular.isDefined(j.datepickerAppendToBody)?h.$parent.$eval(j.datepickerAppendToBody):g.appendToBody;h.showButtonBar=angular.isDefined(j.showButtonBar)?h.$parent.$eval(j.showButtonBar):g.showButtonBar,h.getText=function(a){return h[a+"Text"]||g[a+"Text"]},j.$observe("datepickerPopup",function(a){n=a||g.datepickerPopup,k.$render()});var q=angular.element("
      ");q.attr({"ng-model":"date","ng-change":"dateSelection()"});var r=angular.element(q.children()[0]);j.datepickerOptions&&angular.forEach(h.$parent.$eval(j.datepickerOptions),function(a,b){r.attr(l(b),a)}),angular.forEach(["minDate","maxDate"],function(a){j[a]&&(h.$parent.$watch(b(j[a]),function(b){h[a]=b}),r.attr(l(a),a))}),j.dateDisabled&&r.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),k.$parsers.unshift(m),h.dateSelection=function(a){angular.isDefined(a)&&(h.date=a),k.$setViewValue(h.date),k.$render(),o&&(h.isOpen=!1,i[0].focus())},i.bind("input change keyup",function(){h.$apply(function(){h.date=k.$modelValue})}),k.$render=function(){var a=k.$viewValue?e(k.$viewValue,n):"";i.val(a),h.date=m(k.$modelValue)};var s=function(a){h.isOpen&&a.target!==i[0]&&h.$apply(function(){h.isOpen=!1})},t=function(a){h.keydown(a)};i.bind("keydown",t),h.keydown=function(a){27===a.which?(a.preventDefault(),a.stopPropagation(),h.close()):40!==a.which||h.isOpen||(h.isOpen=!0)},h.$watch("isOpen",function(a){a?(h.$broadcast("datepicker.focus"),h.position=p?d.offset(i):d.position(i),h.position.top=h.position.top+i.prop("offsetHeight"),c.bind("click",s)):c.unbind("click",s)}),h.select=function(a){if("today"===a){var b=new Date;angular.isDate(k.$modelValue)?(a=new Date(k.$modelValue),a.setFullYear(b.getFullYear(),b.getMonth(),b.getDate())):a=new Date(b.setHours(0,0,0,0))}h.dateSelection(a)},h.close=function(){h.isOpen=!1,i[0].focus()};var u=a(q)(h);p?c.find("body").append(u):i.after(u),h.$on("$destroy",function(){u.remove(),i.unbind("keydown",t),c.unbind("click",s)})}}}]).directive("datepickerPopupWrap",function(){return{restrict:"EA",replace:!0,transclude:!0,templateUrl:"template/datepicker/popup.html",link:function(a,b){b.bind("click",function(a){a.preventDefault(),a.stopPropagation()})}}}),angular.module("ui.bootstrap.dropdown",[]).constant("dropdownConfig",{openClass:"open"}).service("dropdownService",["$document",function(a){var b=null;this.open=function(e){b||(a.bind("click",c),a.bind("keydown",d)),b&&b!==e&&(b.isOpen=!1),b=e},this.close=function(e){b===e&&(b=null,a.unbind("click",c),a.unbind("keydown",d))};var c=function(a){a&&a.isDefaultPrevented()||b.$apply(function(){b.isOpen=!1})},d=function(a){27===a.which&&(b.focusToggleElement(),c())}}]).controller("DropdownController",["$scope","$attrs","$parse","dropdownConfig","dropdownService","$animate",function(a,b,c,d,e,f){var g,h=this,i=a.$new(),j=d.openClass,k=angular.noop,l=b.onToggle?c(b.onToggle):angular.noop;this.init=function(d){h.$element=d,b.isOpen&&(g=c(b.isOpen),k=g.assign,a.$watch(g,function(a){i.isOpen=!!a}))},this.toggle=function(a){return i.isOpen=arguments.length?!!a:!i.isOpen},this.isOpen=function(){return i.isOpen},i.focusToggleElement=function(){h.toggleElement&&h.toggleElement[0].focus()},i.$watch("isOpen",function(b,c){f[b?"addClass":"removeClass"](h.$element,j),b?(i.focusToggleElement(),e.open(i)):e.close(i),k(a,b),angular.isDefined(b)&&b!==c&&l(a,{open:!!b})}),a.$on("$locationChangeSuccess",function(){i.isOpen=!1}),a.$on("$destroy",function(){i.$destroy()})}]).directive("dropdown",function(){return{restrict:"CA",controller:"DropdownController",link:function(a,b,c,d){d.init(b)}}}).directive("dropdownToggle",function(){return{restrict:"CA",require:"?^dropdown",link:function(a,b,c,d){if(d){d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.modal",["ui.bootstrap.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0),i()})}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g,0)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&(a.preventDefault(),e.$apply(function(){o.dismiss(b.key,"escape key press")})))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();h>=0&&!k&&(l=e.$new(!0),l.index=h,k=d("
      ")(l),f.append(k));var i=angular.element("
      ");i.attr({"template-url":b.windowTemplateUrl,"window-class":b.windowClass,size:b.size,index:n.length()-1,animate:"animate"}).html(b.content);var j=d(i)(b.scope);n.top().value.modalDomEl=j,f.append(j),f.addClass(m)},o.close=function(a,b){var c=n.get(a).value;c&&(c.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a).value;c&&(c.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass,windowTemplateUrl:b.windowTemplateUrl,size:b.size})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("ui.bootstrap.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse",function(a,b,c){var d=this,e={$setViewValue:angular.noop},f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(f,g){e=f,this.config=g,e.$render=function(){d.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){d.itemsPerPage=parseInt(b,10),a.totalPages=d.calculateTotalPages()}):this.itemsPerPage=g.itemsPerPage},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(e.$viewValue,10)||1},a.selectPage=function(b){a.page!==b&&b>0&&b<=a.totalPages&&(e.$setViewValue(b),e.$render())},a.getText=function(b){return a[b+"Text"]||d.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages},a.$watch("totalItems",function(){a.totalPages=d.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),a.page>b?a.selectPage(b):e.$render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@"},require:["pagination","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c){return{number:a,text:b,active:c}}function h(a,b){var c=[],d=1,e=b,f=angular.isDefined(k)&&b>k;f&&(l?(d=Math.max(a-Math.floor(k/2),1),e=d+k-1,e>b&&(e=b,d=e-k+1)):(d=(Math.ceil(a/k)-1)*k+1,e=Math.min(d+k-1,b)));for(var h=d;e>=h;h++){var i=g(h,h,h===a);c.push(i)}if(f&&!l){if(d>1){var j=g(d-1,"...",!1);c.unshift(j)}if(b>e){var m=g(e+1,"...",!1);c.push(m)}}return c}var i=f[0],j=f[1];if(j){var k=angular.isDefined(e.maxSize)?c.$parent.$eval(e.maxSize):b.maxSize,l=angular.isDefined(e.rotate)?c.$parent.$eval(e.rotate):b.rotate;c.boundaryLinks=angular.isDefined(e.boundaryLinks)?c.$parent.$eval(e.boundaryLinks):b.boundaryLinks,c.directionLinks=angular.isDefined(e.directionLinks)?c.$parent.$eval(e.directionLinks):b.directionLinks,i.init(j,b),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){k=parseInt(a,10),i.render()});var m=i.render;i.render=function(){m(),c.page>0&&c.page<=c.totalPages&&(c.pages=h(c.page,c.totalPages))}}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@"},require:["pager","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){var f=e[0],g=e[1];g&&(b.align=angular.isDefined(d.align)?b.$parent.$eval(d.align):a.align,f.init(g,a))}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-"; +return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
      ';return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!y||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?v||(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return v=null,u&&(g.cancel(u),u=null),b.tt_content?(r(),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),z(),b.tt_isOpen=!0,b.$digest(),z):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),v=null,b.tt_animation?u||(u=g(s,500)):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){u=null,t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=angular.isDefined(d[l+"Enable"]),z=function(){var a=j.positionElements(c,t,b.tt_placement,w);a.top+="px",a.left+="px",t.css(a)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)?a:o.placement}),d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var A=function(){c.unbind(x.show,k),c.unbind(x.hide,m)};d.$observe(l+"Trigger",function(a){A(),x=n(a),x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m))});var B=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(B)?!!B:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),A(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig",function(a,b,c){var d=this,e=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,this.addBar=function(b,c){e||c.css({transition:"none"}),this.bars.push(b),b.$watch("value",function(c){b.percent=+(100*c/a.max).toFixed(2)}),b.$on("$destroy",function(){c=null,d.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},templateUrl:"template/progressbar/progress.html"}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("ui.bootstrap.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","ratingConfig",function(a,b,c){var d={$setViewValue:angular.noop};this.init=function(e){d=e,d.$render=this.render,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff;var f=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(f)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff},a[b]);return a},a.rate=function(b){!a.readonly&&b>=0&&b<=a.range.length&&(d.$setViewValue(b),d.$render())},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue}}]).directive("rating",function(){return{restrict:"EA",require:["rating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}),angular.module("ui.bootstrap.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){angular.forEach(c,function(b){b.active&&b!==a&&(b.active=!1,b.onDeselect())}),a.active=!0,a.onSelect()},b.addTab=function(a){c.push(a),1===c.length?a.active=!0:a.active&&b.select(a)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){b.$watch("active",function(a){a&&f.select(b)}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("ui.bootstrap.timepicker",[]).constant("timepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:null,readonlyInput:!1,mousewheel:!0}).controller("TimepickerController",["$scope","$attrs","$parse","$log","$locale","timepickerConfig",function(a,b,c,d,e,f){function g(){var b=parseInt(a.hours,10),c=a.showMeridian?b>0&&13>b:b>=0&&24>b;return c?(a.showMeridian&&(12===b&&(b=0),a.meridian===p[1]&&(b+=12)),b):void 0}function h(){var b=parseInt(a.minutes,10);return b>=0&&60>b?b:void 0}function i(a){return angular.isDefined(a)&&a.toString().length<2?"0"+a:a}function j(a){k(),o.$setViewValue(new Date(n)),l(a)}function k(){o.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1}function l(b){var c=n.getHours(),d=n.getMinutes();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:i(c),a.minutes="m"===b?d:i(d),a.meridian=n.getHours()<12?p[0]:p[1]}function m(a){var b=new Date(n.getTime()+6e4*a);n.setHours(b.getHours(),b.getMinutes()),j()}var n=new Date,o={$setViewValue:angular.noop},p=angular.isDefined(b.meridians)?a.$parent.$eval(b.meridians):f.meridians||e.DATETIME_FORMATS.AMPMS;this.init=function(c,d){o=c,o.$render=this.render;var e=d.eq(0),g=d.eq(1),h=angular.isDefined(b.mousewheel)?a.$parent.$eval(b.mousewheel):f.mousewheel;h&&this.setupMousewheelEvents(e,g),a.readonlyInput=angular.isDefined(b.readonlyInput)?a.$parent.$eval(b.readonlyInput):f.readonlyInput,this.setupInputEvents(e,g)};var q=f.hourStep;b.hourStep&&a.$parent.$watch(c(b.hourStep),function(a){q=parseInt(a,10)});var r=f.minuteStep;b.minuteStep&&a.$parent.$watch(c(b.minuteStep),function(a){r=parseInt(a,10)}),a.showMeridian=f.showMeridian,b.showMeridian&&a.$parent.$watch(c(b.showMeridian),function(b){if(a.showMeridian=!!b,o.$error.time){var c=g(),d=h();angular.isDefined(c)&&angular.isDefined(d)&&(n.setHours(c),j())}else l()}),this.setupMousewheelEvents=function(b,c){var d=function(a){a.originalEvent&&(a=a.originalEvent);var b=a.wheelDelta?a.wheelDelta:-a.deltaY;return a.detail||b>0};b.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()})},this.setupInputEvents=function(b,c){if(a.readonlyInput)return a.updateHours=angular.noop,void(a.updateMinutes=angular.noop);var d=function(b,c){o.$setViewValue(null),o.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c)};a.updateHours=function(){var a=g();angular.isDefined(a)?(n.setHours(a),j("h")):d(!0)},b.bind("blur",function(){!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=i(a.hours)})}),a.updateMinutes=function(){var a=h();angular.isDefined(a)?(n.setMinutes(a),j("m")):d(void 0,!0)},c.bind("blur",function(){!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=i(a.minutes)})})},this.render=function(){var a=o.$modelValue?new Date(o.$modelValue):null;isNaN(a)?(o.$setValidity("time",!1),d.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(a&&(n=a),k(),l())},a.incrementHours=function(){m(60*q)},a.decrementHours=function(){m(60*-q)},a.incrementMinutes=function(){m(r)},a.decrementMinutes=function(){m(-r)},a.toggleMeridian=function(){m(720*(n.getHours()<12?1:-1))}}]).directive("timepicker",function(){return{restrict:"EA",require:["timepicker","?^ngModel"],controller:"TimepickerController",replace:!0,scope:{},templateUrl:"template/timepicker/timepicker.html",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?i.$eval(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=i.$new();i.$on("$destroy",function(){w.$destroy()});var x="typeahead-"+w.$id+"-"+Math.floor(1e4*Math.random());j.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":x});var y=angular.element("
      ");y.attr({id:x,matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&y.attr("template-url",k.typeaheadTemplateUrl);var z=function(){w.matches=[],w.activeIdx=-1,j.attr("aria-expanded",!1)},A=function(a){return x+"-option-"+a};w.$watch("activeIdx",function(a){0>a?j.removeAttr("aria-activedescendant"):j.attr("aria-activedescendant",A(a))});var B=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){var d=a===l.$viewValue;if(d&&m)if(c.length>0){w.activeIdx=0,w.matches.length=0;for(var e=0;e=n?o>0?(C&&d.cancel(C),C=d(function(){B(a)},o)):B(a):(q(i,!1),z()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),w.select=function(a){var b,c,e={};e[v.itemName]=c=w.matches[a].model,b=v.modelMapper(i,e),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,e)}),z(),d(function(){j[0].focus()},0,!1)},j.bind("keydown",function(a){0!==w.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(w.activeIdx=(w.activeIdx+1)%w.matches.length,w.$digest()):38===a.which?(w.activeIdx=(w.activeIdx?w.activeIdx:w.matches.length)-1,w.$digest()):13===a.which||9===a.which?w.$apply(function(){w.select(w.activeIdx)}):27===a.which&&(a.stopPropagation(),z(),w.$digest()))}),j.bind("blur",function(){m=!1});var D=function(a){j[0]!==a.target&&(z(),w.$digest())};e.bind("click",D),i.$on("$destroy",function(){e.unbind("click",D)});var E=a(y)(w);t?e.find("body").append(E):j.after(E)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?(""+b).replace(new RegExp(a(c),"gi"),"$&"):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'
      \n
      \n

      \n {{heading}}\n

      \n
      \n
      \n
      \n
      \n
      ')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
      ')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html",'\n')}]),angular.module("template/carousel/carousel.html",[]).run(["$templateCache",function(a){a.put("template/carousel/carousel.html",'\n')}]),angular.module("template/carousel/slide.html",[]).run(["$templateCache",function(a){a.put("template/carousel/slide.html","
      \n")}]),angular.module("template/datepicker/datepicker.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/datepicker.html",'
      \n \n \n \n
      ')}]),angular.module("template/datepicker/day.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/day.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
      {{label.abbr}}
      {{ weekNumbers[$index] }}\n \n
      \n')}]),angular.module("template/datepicker/month.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/month.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
      \n \n
      \n')}]),angular.module("template/datepicker/popup.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/popup.html",'\n')}]),angular.module("template/datepicker/year.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/year.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
      \n \n
      \n')}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'
      \n
      \n
      \n
      \n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'
      \n
      \n
      \n
      \n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'
      \n
      \n\n
      \n

      \n
      \n
      \n
      \n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'
      ')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'
      ')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'
      \n
      \n
      ')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n \n ({{ $index < value ? \'*\' : \' \' }})\n \n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'
    • \n {{heading}}\n
    • \n')}]),angular.module("template/tabs/tabset-titles.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset-titles.html","
        \n
      \n")}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'\n
      \n \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/timepicker/timepicker.html",[]).run(["$templateCache",function(a){a.put("template/timepicker/timepicker.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
       
      \n \n :\n \n
       
      \n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html",'') +}]); \ No newline at end of file diff --git a/src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap.js b/src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap.js new file mode 100644 index 0000000..c93f0de --- /dev/null +++ b/src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap.js @@ -0,0 +1,3799 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.11.0 - 2014-05-01 + * License: MIT + */ +angular.module("ui.bootstrap", ["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); +angular.module('ui.bootstrap.transition', []) + +/** + * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. + * @param {DOMElement} element The DOMElement that will be animated. + * @param {string|object|function} trigger The thing that will cause the transition to start: + * - As a string, it represents the css class to be added to the element. + * - As an object, it represents a hash of style attributes to be applied to the element. + * - As a function, it represents a function to be called that will cause the transition to occur. + * @return {Promise} A promise that is resolved when the transition finishes. + */ +.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { + + var $transition = function(element, trigger, options) { + options = options || {}; + var deferred = $q.defer(); + var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName']; + + var transitionEndHandler = function(event) { + $rootScope.$apply(function() { + element.unbind(endEventName, transitionEndHandler); + deferred.resolve(element); + }); + }; + + if (endEventName) { + element.bind(endEventName, transitionEndHandler); + } + + // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur + $timeout(function() { + if ( angular.isString(trigger) ) { + element.addClass(trigger); + } else if ( angular.isFunction(trigger) ) { + trigger(element); + } else if ( angular.isObject(trigger) ) { + element.css(trigger); + } + //If browser does not support transitions, instantly resolve + if ( !endEventName ) { + deferred.resolve(element); + } + }); + + // Add our custom cancel function to the promise that is returned + // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, + // i.e. it will therefore never raise a transitionEnd event for that transition + deferred.promise.cancel = function() { + if ( endEventName ) { + element.unbind(endEventName, transitionEndHandler); + } + deferred.reject('Transition cancelled'); + }; + + return deferred.promise; + }; + + // Work out the name of the transitionEnd event + var transElement = document.createElement('trans'); + var transitionEndEventNames = { + 'WebkitTransition': 'webkitTransitionEnd', + 'MozTransition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'transition': 'transitionend' + }; + var animationEndEventNames = { + 'WebkitTransition': 'webkitAnimationEnd', + 'MozTransition': 'animationend', + 'OTransition': 'oAnimationEnd', + 'transition': 'animationend' + }; + function findEndEventName(endEventNames) { + for (var name in endEventNames){ + if (transElement.style[name] !== undefined) { + return endEventNames[name]; + } + } + } + $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); + $transition.animationEndEventName = findEndEventName(animationEndEventNames); + return $transition; +}]); + +angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) + + .directive('collapse', ['$transition', function ($transition) { + + return { + link: function (scope, element, attrs) { + + var initialAnimSkip = true; + var currentTransition; + + function doTransition(change) { + var newTransition = $transition(element, change); + if (currentTransition) { + currentTransition.cancel(); + } + currentTransition = newTransition; + newTransition.then(newTransitionDone, newTransitionDone); + return newTransition; + + function newTransitionDone() { + // Make sure it's this transition, otherwise, leave it alone. + if (currentTransition === newTransition) { + currentTransition = undefined; + } + } + } + + function expand() { + if (initialAnimSkip) { + initialAnimSkip = false; + expandDone(); + } else { + element.removeClass('collapse').addClass('collapsing'); + doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone); + } + } + + function expandDone() { + element.removeClass('collapsing'); + element.addClass('collapse in'); + element.css({height: 'auto'}); + } + + function collapse() { + if (initialAnimSkip) { + initialAnimSkip = false; + collapseDone(); + element.css({height: 0}); + } else { + // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value + element.css({ height: element[0].scrollHeight + 'px' }); + //trigger reflow so a browser realizes that height was updated from auto to a specific value + var x = element[0].offsetWidth; + + element.removeClass('collapse in').addClass('collapsing'); + + doTransition({ height: 0 }).then(collapseDone); + } + } + + function collapseDone() { + element.removeClass('collapsing'); + element.addClass('collapse'); + } + + scope.$watch(attrs.collapse, function (shouldCollapse) { + if (shouldCollapse) { + collapse(); + } else { + expand(); + } + }); + } + }; + }]); + +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(index, 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', function() { + return { + require:'^accordion', // We need this directive to be inside an accordion + restrict:'EA', + transclude:true, // It transcludes the contents of the directive into the template + replace: true, // The element containing the directive will be replaced with the template + templateUrl:'template/accordion/accordion-group.html', + scope: { + heading: '@', // Interpolate the heading attribute onto this scope + isOpen: '=?', + isDisabled: '=?' + }, + controller: function() { + this.setHeading = function(element) { + this.heading = element; + }; + }, + link: function(scope, element, attrs, accordionCtrl) { + accordionCtrl.addGroup(scope); + + scope.$watch('isOpen', function(value) { + if ( value ) { + accordionCtrl.closeOthers(scope); + } + }); + + scope.toggleOpen = function() { + if ( !scope.isDisabled ) { + scope.isOpen = !scope.isOpen; + } + }; + } + }; +}) + +// Use accordion-heading below an accordion-group to provide a heading containing HTML +// +// Heading containing HTML - +// +.directive('accordionHeading', function() { + return { + restrict: 'EA', + transclude: true, // Grab the contents to be used as the heading + template: '', // In effect remove this element! + replace: true, + require: '^accordionGroup', + link: function(scope, element, attr, accordionGroupCtrl, transclude) { + // 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 +//
      +// +// ... +//
      +.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.alert', []) + +.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { + $scope.closeable = 'close' in $attrs; +}]) + +.directive('alert', function () { + return { + restrict:'EA', + controller:'AlertController', + templateUrl:'template/alert/alert.html', + transclude:true, + replace:true, + scope: { + type: '@', + close: '&' + } + }; +}); + +angular.module('ui.bootstrap.bindHtml', []) + + .directive('bindHtmlUnsafe', function () { + return function (scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); + scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { + element.html(value || ''); + }); + }; + }); +angular.module('ui.bootstrap.buttons', []) + +.constant('buttonConfig', { + activeClass: 'active', + toggleEvent: 'click' +}) + +.controller('ButtonsController', ['buttonConfig', function(buttonConfig) { + this.activeClass = buttonConfig.activeClass || 'active'; + this.toggleEvent = buttonConfig.toggleEvent || 'click'; +}]) + +.directive('btnRadio', function () { + return { + require: ['btnRadio', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + var isActive = element.hasClass(buttonsCtrl.activeClass); + + if (!isActive || angular.isDefined(attrs.uncheckable)) { + scope.$apply(function () { + ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio)); + ngModelCtrl.$render(); + }); + } + }); + } + }; +}) + +.directive('btnCheckbox', function () { + return { + require: ['btnCheckbox', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + function getTrueValue() { + return getCheckboxValue(attrs.btnCheckboxTrue, true); + } + + function getFalseValue() { + return getCheckboxValue(attrs.btnCheckboxFalse, false); + } + + function getCheckboxValue(attributeValue, defaultValue) { + var val = scope.$eval(attributeValue); + return angular.isDefined(val) ? val : defaultValue; + } + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + scope.$apply(function () { + ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); + ngModelCtrl.$render(); + }); + }); + } + }; +}); + +/** +* @ngdoc overview +* @name ui.bootstrap.carousel +* +* @description +* AngularJS version of an image carousel. +* +*/ +angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) +.controller('CarouselController', ['$scope', '$timeout', '$transition', function ($scope, $timeout, $transition) { + var self = this, + slides = self.slides = $scope.slides = [], + currentIndex = -1, + currentTimeout, isPlaying; + self.currentSlide = null; + + var destroyed = false; + /* direction: "prev" or "next" */ + self.select = $scope.select = function(nextSlide, direction) { + var nextIndex = slides.indexOf(nextSlide); + //Decide direction if it's not given + if (direction === undefined) { + direction = nextIndex > currentIndex ? 'next' : 'prev'; + } + if (nextSlide && nextSlide !== self.currentSlide) { + if ($scope.$currentTransition) { + $scope.$currentTransition.cancel(); + //Timeout so ng-class in template has time to fix classes for finished slide + $timeout(goNext); + } else { + goNext(); + } + } + function goNext() { + // Scope has been destroyed, stop here. + if (destroyed) { return; } + //If we have a slide to transition from and we have a transition type and we're allowed, go + if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { + //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime + nextSlide.$element.addClass(direction); + var reflow = nextSlide.$element[0].offsetWidth; //force reflow + + //Set all other slides to stop doing their stuff for the new transition + angular.forEach(slides, function(slide) { + angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); + }); + angular.extend(nextSlide, {direction: direction, active: true, entering: true}); + angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); + + $scope.$currentTransition = $transition(nextSlide.$element, {}); + //We have to create new pointers inside a closure since next & current will change + (function(next,current) { + $scope.$currentTransition.then( + function(){ transitionDone(next, current); }, + function(){ transitionDone(next, current); } + ); + }(nextSlide, self.currentSlide)); + } else { + transitionDone(nextSlide, self.currentSlide); + } + self.currentSlide = nextSlide; + currentIndex = nextIndex; + //every time you change slides, reset the timer + restartTimer(); + } + function transitionDone(next, current) { + angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); + angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); + $scope.$currentTransition = null; + } + }; + $scope.$on('$destroy', function () { + destroyed = true; + }); + + /* Allow outside people to call indexOf on slides array */ + self.indexOfSlide = function(slide) { + return slides.indexOf(slide); + }; + + $scope.next = function() { + var newIndex = (currentIndex + 1) % slides.length; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'next'); + } + }; + + $scope.prev = function() { + var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'prev'); + } + }; + + $scope.isActive = function(slide) { + return self.currentSlide === slide; + }; + + $scope.$watch('interval', restartTimer); + $scope.$on('$destroy', resetTimer); + + function restartTimer() { + resetTimer(); + var interval = +$scope.interval; + if (!isNaN(interval) && interval>=0) { + currentTimeout = $timeout(timerFn, interval); + } + } + + function resetTimer() { + if (currentTimeout) { + $timeout.cancel(currentTimeout); + currentTimeout = null; + } + } + + function timerFn() { + if (isPlaying) { + $scope.next(); + restartTimer(); + } else { + $scope.pause(); + } + } + + $scope.play = function() { + if (!isPlaying) { + isPlaying = true; + restartTimer(); + } + }; + $scope.pause = function() { + if (!$scope.noPause) { + isPlaying = false; + resetTimer(); + } + }; + + self.addSlide = function(slide, element) { + slide.$element = element; + slides.push(slide); + //if this is the first slide or the slide is set to active, select it + if(slides.length === 1 || slide.active) { + self.select(slides[slides.length-1]); + if (slides.length == 1) { + $scope.play(); + } + } else { + slide.active = false; + } + }; + + self.removeSlide = function(slide) { + //get the index of the slide inside the carousel + var index = slides.indexOf(slide); + slides.splice(index, 1); + if (slides.length > 0 && slide.active) { + if (index >= slides.length) { + self.select(slides[index-1]); + } else { + self.select(slides[index]); + } + } else if (currentIndex > index) { + currentIndex--; + } + }; + +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:carousel + * @restrict EA + * + * @description + * Carousel is the outer container for a set of image 'slides' to showcase. + * + * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. + * @param {boolean=} noTransition Whether to disable transitions on the carousel. + * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). + * + * @example + + + + + + + + + + + + + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + + + */ +.directive('carousel', [function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + controller: 'CarouselController', + require: 'carousel', + templateUrl: 'template/carousel/carousel.html', + scope: { + interval: '=', + noTransition: '=', + noPause: '=' + } + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:slide + * @restrict EA + * + * @description + * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. + * + * @param {boolean=} active Model binding, whether or not this slide is currently active. + * + * @example + + +
      + + + + + + + Interval, in milliseconds: +
      Enter a negative number to stop the interval. +
      +
      + +function CarouselDemoCtrl($scope) { + $scope.myInterval = 5000; +} + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + +
      +*/ + +.directive('slide', function() { + return { + require: '^carousel', + restrict: 'EA', + transclude: true, + replace: true, + templateUrl: 'template/carousel/slide.html', + scope: { + active: '=?' + }, + link: function (scope, element, attrs, carouselCtrl) { + carouselCtrl.addSlide(scope, element); + //when the scope is destroyed then remove the slide from the current slides array + scope.$on('$destroy', function() { + carouselCtrl.removeSlide(scope); + }); + + scope.$watch('active', function(active) { + if (active) { + carouselCtrl.select(scope); + } + }); + } + }; +}); + +angular.module('ui.bootstrap.dateparser', []) + +.service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) { + + this.parsers = {}; + + var formatCodeToRegex = { + 'yyyy': { + regex: '\\d{4}', + apply: function(value) { this.year = +value; } + }, + 'yy': { + regex: '\\d{2}', + apply: function(value) { this.year = +value + 2000; } + }, + 'y': { + regex: '\\d{1,4}', + apply: function(value) { this.year = +value; } + }, + 'MMMM': { + regex: $locale.DATETIME_FORMATS.MONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } + }, + 'MMM': { + regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } + }, + 'MM': { + regex: '0[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'M': { + regex: '[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'dd': { + regex: '[0-2][0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'd': { + regex: '[1-2]?[0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'EEEE': { + regex: $locale.DATETIME_FORMATS.DAY.join('|') + }, + 'EEE': { + regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') + } + }; + + this.createParser = function(format) { + var map = [], regex = format.split(''); + + angular.forEach(formatCodeToRegex, function(data, code) { + var index = format.indexOf(code); + + if (index > -1) { + format = format.split(''); + + regex[index] = '(' + data.regex + ')'; + format[index] = '$'; // Custom symbol to define consumed part of format + for (var i = index + 1, n = index + code.length; i < n; i++) { + regex[i] = ''; + format[i] = '$'; + } + format = format.join(''); + + map.push({ index: index, apply: data.apply }); + } + }); + + return { + regex: new RegExp('^' + regex.join('') + '$'), + map: orderByFilter(map, 'index') + }; + }; + + this.parse = function(input, format) { + if ( !angular.isString(input) ) { + return input; + } + + format = $locale.DATETIME_FORMATS[format] || format; + + if ( !this.parsers[format] ) { + this.parsers[format] = this.createParser(format); + } + + var parser = this.parsers[format], + regex = parser.regex, + map = parser.map, + results = input.match(regex); + + if ( results && results.length ) { + var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt; + + for( var i = 1, n = results.length; i < n; i++ ) { + var mapper = map[i-1]; + if ( mapper.apply ) { + mapper.apply.call(fields, results[i]); + } + } + + if ( isValid(fields.year, fields.month, fields.date) ) { + dt = new Date( fields.year, fields.month, fields.date, fields.hours); + } + + 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 ( 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; + } +}]); + +angular.module('ui.bootstrap.position', []) + +/** + * A set of utility methods that can be use to retrieve position of DOM elements. + * It is meant to be used where we need to absolute-position DOM elements in + * relation to other, existing elements (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$position', ['$document', '$window', function ($document, $window) { + + function getStyle(el, cssprop) { + if (el.currentStyle) { //IE + return el.currentStyle[cssprop]; + } else if ($window.getComputedStyle) { + return $window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; + } + + /** + * Checks if a given element is statically positioned + * @param element - raw DOM element + */ + function isStaticPositioned(element) { + return (getStyle(element, 'position') || 'static' ) === 'static'; + } + + /** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ + var parentOffsetEl = function (element) { + var docDomEl = $document[0]; + var offsetParent = element.offsetParent || docDomEl; + while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || docDomEl; + }; + + return { + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ + */ + position: function (element) { + var elBCR = this.offset(element); + var offsetParentBCR = { top: 0, left: 0 }; + var offsetParentEl = parentOffsetEl(element[0]); + if (offsetParentEl != $document[0]) { + offsetParentBCR = this.offset(angular.element(offsetParentEl)); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ + */ + offset: function (element) { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop), + left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft) + }; + }, + + /** + * Provides coordinates for the targetEl in relation to hostEl + */ + positionElements: function (hostEl, targetEl, positionStr, appendToBody) { + + var positionStrParts = positionStr.split('-'); + var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; + + var hostElPos, + targetElWidth, + targetElHeight, + targetElPos; + + hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); + + targetElWidth = targetEl.prop('offsetWidth'); + targetElHeight = targetEl.prop('offsetHeight'); + + var shiftWidth = { + center: function () { + return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; + }, + left: function () { + return hostElPos.left; + }, + right: function () { + return hostElPos.left + hostElPos.width; + } + }; + + var shiftHeight = { + center: function () { + return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; + }, + top: function () { + return hostElPos.top; + }, + bottom: function () { + return hostElPos.top + hostElPos.height; + } + }; + + switch (pos0) { + case 'right': + targetElPos = { + top: shiftHeight[pos1](), + left: shiftWidth[pos0]() + }; + break; + case 'left': + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left - targetElWidth + }; + break; + case 'bottom': + targetElPos = { + top: shiftHeight[pos0](), + left: shiftWidth[pos1]() + }; + break; + default: + targetElPos = { + top: hostElPos.top - targetElHeight, + left: shiftWidth[pos1]() + }; + break; + } + + return targetElPos; + } + }; + }]); + +angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position']) + +.constant('datepickerConfig', { + formatDay: 'dd', + formatMonth: 'MMMM', + formatYear: 'yyyy', + formatDayHeader: 'EEE', + formatDayTitle: 'MMMM yyyy', + formatMonthTitle: 'yyyy', + datepickerMode: 'day', + minMode: 'day', + maxMode: 'year', + showWeeks: true, + startingDay: 0, + yearRange: 20, + minDate: null, + maxDate: null +}) + +.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; + + // Modes chain + this.modes = ['day', 'month', 'year']; + + // Configuration attributes + angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', + 'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function( key, index ) { + self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; + }); + + // Watchable attributes + angular.forEach(['minDate', 'maxDate'], function( key ) { + if ( $attrs[key] ) { + $scope.$parent.$watch($parse($attrs[key]), function(value) { + self[key] = value ? new Date(value) : null; + self.refreshView(); + }); + } else { + self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null; + } + }); + + $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; + $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); + this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date(); + + $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_; + + ngModelCtrl.$render = function() { + self.render(); + }; + }; + + this.render = function() { + if ( ngModelCtrl.$modelValue ) { + var date = new Date( ngModelCtrl.$modelValue ), + isValid = !isNaN(date); + + if ( isValid ) { + this.activeDate = date; + } else { + $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } + ngModelCtrl.$setValidity('date', isValid); + } + this.refreshView(); + }; + + this.refreshView = function() { + if ( this.element ) { + this._refreshView(); + + var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date))); + } + }; + + this.createDateObject = function(date, format) { + var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + return { + date: date, + label: dateFilter(date, format), + selected: model && this.compare(date, model) === 0, + disabled: this.isDisabled(date), + current: this.compare(date, new Date()) === 0 + }; + }; + + this.isDisabled = function( date ) { + return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({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.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); + ngModelCtrl.$setViewValue( dt ); + ngModelCtrl.$render(); + } else { + self.activeDate = date; + $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ]; + } + }; + + $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; + } + + $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ]; + }; + + // 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() { + $timeout(function() { + self.element[0].focus(); + }, 0 , false); + }; + + // Listen for focus requests from popup directive + $scope.$on('datepicker.focus', focusElement); + + $scope.keydown = function( evt ) { + var key = $scope.keys[evt.which]; + + if ( !key || evt.shiftKey || evt.altKey ) { + return; + } + + evt.preventDefault(); + evt.stopPropagation(); + + if (key === 'enter' || key === 'space') { + if ( self.isDisabled(self.activeDate)) { + return; // do nothing + } + $scope.select(self.activeDate); + focusElement(); + } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { + $scope.toggleMode(key === 'up' ? 1 : -1); + focusElement(); + } else { + self.handleKeyDown(key, evt); + self.refreshView(); + } + }; +}]) + +.directive( 'datepicker', function () { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/datepicker.html', + scope: { + datepickerMode: '=?', + dateDisabled: '&' + }, + require: ['datepicker', '?^ngModel'], + controller: 'DatepickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + datepickerCtrl.init( ngModelCtrl ); + } + } + }; +}) + +.directive('daypicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/day.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + scope.showWeeks = ctrl.showWeeks; + + ctrl.step = { months: 1 }; + ctrl.element = element; + + var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + function getDaysInMonth( year, month ) { + return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; + } + + function getDates(startDate, n) { + var dates = new Array(n), current = new Date(startDate), i = 0; + current.setHours(12); // Prevent repeated dates because of timezone bug + while ( i < n ) { + dates[i++] = new Date(current); + current.setDate( current.getDate() + 1 ); + } + return dates; + } + + ctrl._refreshView = function() { + var year = ctrl.activeDate.getFullYear(), + month = ctrl.activeDate.getMonth(), + firstDayOfMonth = new Date(year, month, 1), + difference = ctrl.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-month calendar + var days = getDates(firstDate, 42); + for (var i = 0; i < 42; i ++) { + days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.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, ctrl.formatDayHeader), + full: dateFilter(days[j].date, 'EEEE') + }; + } + + scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle); + scope.rows = ctrl.split(days, 7); + + if ( scope.showWeeks ) { + scope.weekNumbers = []; + var weekNumber = getISO8601WeekNumber( scope.rows[0][0].date ), + numWeeks = scope.rows.length; + while( scope.weekNumbers.push(weekNumber++) < numWeeks ) {} + } + }; + + ctrl.compare = function(date1, date2) { + return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); + }; + + 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; + } + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getDate(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 7; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 7; + } else if (key === 'pageup' || key === 'pagedown') { + var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); + ctrl.activeDate.setMonth(month, 1); + date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date); + } else if (key === 'home') { + date = 1; + } else if (key === 'end') { + date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()); + } + ctrl.activeDate.setDate(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.directive('monthpicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/month.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + ctrl.step = { years: 1 }; + ctrl.element = element; + + ctrl._refreshView = function() { + var months = new Array(12), + year = ctrl.activeDate.getFullYear(); + + for ( var i = 0; i < 12; i++ ) { + months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle); + scope.rows = ctrl.split(months, 3); + }; + + ctrl.compare = function(date1, date2) { + return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); + }; + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getMonth(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 3; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 3; + } else if (key === 'pageup' || key === 'pagedown') { + var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); + ctrl.activeDate.setFullYear(year); + } else if (key === 'home') { + date = 0; + } else if (key === 'end') { + date = 11; + } + ctrl.activeDate.setMonth(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.directive('yearpicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/year.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + var range = ctrl.yearRange; + + ctrl.step = { years: range }; + ctrl.element = element; + + function getStartingYear( year ) { + return parseInt((year - 1) / range, 10) * range + 1; + } + + ctrl._refreshView = function() { + var years = new Array(range); + + for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) { + years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = [years[0].label, years[range - 1].label].join(' - '); + scope.rows = ctrl.split(years, 5); + }; + + ctrl.compare = function(date1, date2) { + return date1.getFullYear() - date2.getFullYear(); + }; + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getFullYear(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 5; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 5; + } else if (key === 'pageup' || key === 'pagedown') { + date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years; + } else if (key === 'home') { + date = getStartingYear( ctrl.activeDate.getFullYear() ); + } else if (key === 'end') { + date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1; + } + ctrl.activeDate.setFullYear(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.constant('datepickerPopupConfig', { + datepickerPopup: 'yyyy-MM-dd', + currentText: 'Today', + clearText: 'Clear', + closeText: 'Done', + closeOnDateSelection: true, + appendToBody: false, + showButtonBar: true +}) + +.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', +function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) { + return { + restrict: 'EA', + require: 'ngModel', + scope: { + isOpen: '=?', + currentText: '@', + clearText: '@', + closeText: '@', + dateDisabled: '&' + }, + link: function(scope, element, attrs, ngModel) { + var dateFormat, + closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, + appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; + + scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; + + scope.getText = function( key ) { + return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; + }; + + attrs.$observe('datepickerPopup', function(value) { + dateFormat = value || datepickerPopupConfig.datepickerPopup; + ngModel.$render(); + }); + + // popup element used to display calendar + var popupEl = angular.element('
      '); + popupEl.attr({ + 'ng-model': 'date', + 'ng-change': 'dateSelection()' + }); + + function cameltoDash( string ){ + return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); + } + + // datepicker element + var datepickerEl = angular.element(popupEl.children()[0]); + if ( attrs.datepickerOptions ) { + angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function( value, option ) { + datepickerEl.attr( cameltoDash(option), value ); + }); + } + + angular.forEach(['minDate', 'maxDate'], function( key ) { + if ( attrs[key] ) { + scope.$parent.$watch($parse(attrs[key]), function(value){ + scope[key] = value; + }); + datepickerEl.attr(cameltoDash(key), key); + } + }); + if (attrs.dateDisabled) { + datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); + } + + function parseDate(viewValue) { + if (!viewValue) { + ngModel.$setValidity('date', true); + return null; + } else if (angular.isDate(viewValue) && !isNaN(viewValue)) { + ngModel.$setValidity('date', true); + return viewValue; + } else if (angular.isString(viewValue)) { + var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue); + if (isNaN(date)) { + ngModel.$setValidity('date', false); + return undefined; + } else { + ngModel.$setValidity('date', true); + return date; + } + } else { + ngModel.$setValidity('date', false); + return undefined; + } + } + ngModel.$parsers.unshift(parseDate); + + // Inner change + scope.dateSelection = function(dt) { + if (angular.isDefined(dt)) { + scope.date = dt; + } + ngModel.$setViewValue(scope.date); + ngModel.$render(); + + if ( closeOnDateSelection ) { + scope.isOpen = false; + element[0].focus(); + } + }; + + element.bind('input change keyup', function() { + scope.$apply(function() { + scope.date = ngModel.$modelValue; + }); + }); + + // Outter change + ngModel.$render = function() { + var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; + element.val(date); + scope.date = parseDate( ngModel.$modelValue ); + }; + + var documentClickBind = function(event) { + if (scope.isOpen && event.target !== element[0]) { + scope.$apply(function() { + scope.isOpen = false; + }); + } + }; + + var keydown = function(evt, noApply) { + scope.keydown(evt); + }; + element.bind('keydown', keydown); + + scope.keydown = function(evt) { + if (evt.which === 27) { + evt.preventDefault(); + evt.stopPropagation(); + scope.close(); + } else if (evt.which === 40 && !scope.isOpen) { + scope.isOpen = true; + } + }; + + scope.$watch('isOpen', function(value) { + if (value) { + scope.$broadcast('datepicker.focus'); + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top = scope.position.top + element.prop('offsetHeight'); + + $document.bind('click', documentClickBind); + } else { + $document.unbind('click', documentClickBind); + } + }); + + scope.select = function( date ) { + if (date === 'today') { + var today = new Date(); + if (angular.isDate(ngModel.$modelValue)) { + date = new Date(ngModel.$modelValue); + date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); + } else { + date = new Date(today.setHours(0, 0, 0, 0)); + } + } + scope.dateSelection( date ); + }; + + scope.close = function() { + scope.isOpen = false; + element[0].focus(); + }; + + var $popup = $compile(popupEl)(scope); + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + + scope.$on('$destroy', function() { + $popup.remove(); + element.unbind('keydown', keydown); + $document.unbind('click', documentClickBind); + }); + } + }; +}]) + +.directive('datepickerPopupWrap', function() { + return { + restrict:'EA', + replace: true, + transclude: true, + templateUrl: 'template/datepicker/popup.html', + link:function (scope, element, attrs) { + element.bind('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + }); + } + }; +}); + +angular.module('ui.bootstrap.dropdown', []) + +.constant('dropdownConfig', { + openClass: 'open' +}) + +.service('dropdownService', ['$document', function($document) { + var openScope = null; + + this.open = function( dropdownScope ) { + if ( !openScope ) { + $document.bind('click', closeDropdown); + $document.bind('keydown', escapeKeyBind); + } + + if ( openScope && openScope !== dropdownScope ) { + openScope.isOpen = false; + } + + openScope = dropdownScope; + }; + + this.close = function( dropdownScope ) { + if ( openScope === dropdownScope ) { + openScope = null; + $document.unbind('click', closeDropdown); + $document.unbind('keydown', escapeKeyBind); + } + }; + + var closeDropdown = function( evt ) { + if (evt && evt.isDefaultPrevented()) { + return; + } + + openScope.$apply(function() { + openScope.isOpen = false; + }); + }; + + var escapeKeyBind = function( evt ) { + if ( evt.which === 27 ) { + openScope.focusToggleElement(); + closeDropdown(); + } + }; +}]) + +.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) { + var self = this, + scope = $scope.$new(), // create a child scope so we are not polluting original one + openClass = dropdownConfig.openClass, + getIsOpen, + setIsOpen = angular.noop, + toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop; + + this.init = function( element ) { + self.$element = element; + + if ( $attrs.isOpen ) { + getIsOpen = $parse($attrs.isOpen); + setIsOpen = getIsOpen.assign; + + $scope.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); + } + }; + + this.toggle = function( open ) { + return scope.isOpen = arguments.length ? !!open : !scope.isOpen; + }; + + // Allow other directives to watch status + this.isOpen = function() { + return scope.isOpen; + }; + + scope.focusToggleElement = function() { + if ( self.toggleElement ) { + self.toggleElement[0].focus(); + } + }; + + scope.$watch('isOpen', function( isOpen, wasOpen ) { + $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass); + + if ( isOpen ) { + scope.focusToggleElement(); + dropdownService.open( scope ); + } else { + dropdownService.close( scope ); + } + + setIsOpen($scope, isOpen); + if (angular.isDefined(isOpen) && isOpen !== wasOpen) { + toggleInvoker($scope, { open: !!isOpen }); + } + }); + + $scope.$on('$locationChangeSuccess', function() { + scope.isOpen = false; + }); + + $scope.$on('$destroy', function() { + scope.$destroy(); + }); +}]) + +.directive('dropdown', function() { + return { + restrict: 'CA', + controller: 'DropdownController', + link: function(scope, element, attrs, dropdownCtrl) { + dropdownCtrl.init( element ); + } + }; +}) + +.directive('dropdownToggle', function() { + return { + restrict: 'CA', + require: '?^dropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if ( !dropdownCtrl ) { + return; + } + + dropdownCtrl.toggleElement = element; + + var toggleDropdown = function(event) { + event.preventDefault(); + + if ( !element.hasClass('disabled') && !attrs.disabled ) { + scope.$apply(function() { + dropdownCtrl.toggle(); + }); + } + }; + + element.bind('click', toggleDropdown); + + // WAI-ARIA + element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); + scope.$watch(dropdownCtrl.isOpen, function( isOpen ) { + element.attr('aria-expanded', !!isOpen); + }); + + scope.$on('$destroy', function() { + element.unbind('click', toggleDropdown); + }); + } + }; +}); + +angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) + +/** + * A helper, internal data structure that acts as a map but also allows getting / removing + * elements in the LIFO order + */ + .factory('$$stackedMap', function () { + return { + createNew: function () { + var stack = []; + + return { + add: function (key, value) { + stack.push({ + key: key, + value: value + }); + }, + get: function (key) { + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + return stack[i]; + } + } + }, + keys: function() { + var keys = []; + for (var i = 0; i < stack.length; i++) { + keys.push(stack[i].key); + } + return keys; + }, + top: function () { + return stack[stack.length - 1]; + }, + remove: function (key) { + var idx = -1; + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + idx = i; + break; + } + } + return stack.splice(idx, 1)[0]; + }, + removeTop: function () { + return stack.splice(stack.length - 1, 1)[0]; + }, + length: function () { + return stack.length; + } + }; + } + }; + }) + +/** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('modalBackdrop', ['$timeout', function ($timeout) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/modal/backdrop.html', + link: function (scope) { + + scope.animate = false; + + //trigger CSS transitions + $timeout(function () { + scope.animate = true; + }); + } + }; + }]) + + .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { + return { + restrict: 'EA', + scope: { + index: '@', + animate: '=' + }, + replace: true, + transclude: true, + templateUrl: function(tElement, tAttrs) { + return tAttrs.templateUrl || 'template/modal/window.html'; + }, + link: function (scope, element, attrs) { + element.addClass(attrs.windowClass || ''); + scope.size = attrs.size; + + $timeout(function () { + // trigger CSS transitions + scope.animate = true; + // focus a freshly-opened modal + element[0].focus(); + }); + + scope.close = function (evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + } + }; + }]) + + .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', + function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { + + var OPENED_MODAL_CLASS = 'modal-open'; + + var backdropDomEl, backdropScope; + var openedWindows = $$stackedMap.createNew(); + var $modalStack = {}; + + function backdropIndex() { + var topBackdropIndex = -1; + var opened = openedWindows.keys(); + for (var i = 0; i < opened.length; i++) { + if (openedWindows.get(opened[i]).value.backdrop) { + topBackdropIndex = i; + } + } + return topBackdropIndex; + } + + $rootScope.$watch(backdropIndex, function(newBackdropIndex){ + if (backdropScope) { + backdropScope.index = newBackdropIndex; + } + }); + + function removeModalWindow(modalInstance) { + + var body = $document.find('body').eq(0); + var modalWindow = openedWindows.get(modalInstance).value; + + //clean up the stack + openedWindows.remove(modalInstance); + + //remove window DOM element + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() { + modalWindow.modalScope.$destroy(); + body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); + checkRemoveBackdrop(); + }); + } + + function checkRemoveBackdrop() { + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() == -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, 150, function () { + backdropScopeRef.$destroy(); + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } + } + + function removeAfterAnimate(domEl, scope, emulateTime, done) { + // Closing animation + scope.animate = false; + + var transitionEndEventName = $transition.transitionEndEventName; + if (transitionEndEventName) { + // transition out + var timeout = $timeout(afterAnimating, emulateTime); + + domEl.bind(transitionEndEventName, function () { + $timeout.cancel(timeout); + afterAnimating(); + scope.$apply(); + }); + } else { + // Ensure this call is async + $timeout(afterAnimating, 0); + } + + function afterAnimating() { + if (afterAnimating.done) { + return; + } + afterAnimating.done = true; + + domEl.remove(); + if (done) { + done(); + } + } + } + + $document.bind('keydown', function (evt) { + var modal; + + if (evt.which === 27) { + modal = openedWindows.top(); + if (modal && modal.value.keyboard) { + evt.preventDefault(); + $rootScope.$apply(function () { + $modalStack.dismiss(modal.key, 'escape key press'); + }); + } + } + }); + + $modalStack.open = function (modalInstance, modal) { + + openedWindows.add(modalInstance, { + deferred: modal.deferred, + modalScope: modal.scope, + backdrop: modal.backdrop, + keyboard: modal.keyboard + }); + + var body = $document.find('body').eq(0), + currBackdropIndex = backdropIndex(); + + if (currBackdropIndex >= 0 && !backdropDomEl) { + backdropScope = $rootScope.$new(true); + backdropScope.index = currBackdropIndex; + backdropDomEl = $compile('
      ')(backdropScope); + body.append(backdropDomEl); + } + + var angularDomEl = angular.element('
      '); + angularDomEl.attr({ + 'template-url': modal.windowTemplateUrl, + 'window-class': modal.windowClass, + 'size': modal.size, + 'index': openedWindows.length() - 1, + 'animate': 'animate' + }).html(modal.content); + + var modalDomEl = $compile(angularDomEl)(modal.scope); + openedWindows.top().value.modalDomEl = modalDomEl; + body.append(modalDomEl); + body.addClass(OPENED_MODAL_CLASS); + }; + + $modalStack.close = function (modalInstance, result) { + var modalWindow = openedWindows.get(modalInstance).value; + if (modalWindow) { + modalWindow.deferred.resolve(result); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismiss = function (modalInstance, reason) { + var modalWindow = openedWindows.get(modalInstance).value; + if (modalWindow) { + modalWindow.deferred.reject(reason); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismissAll = function (reason) { + var topModal = this.getTop(); + while (topModal) { + this.dismiss(topModal.key, reason); + topModal = this.getTop(); + } + }; + + $modalStack.getTop = function () { + return openedWindows.top(); + }; + + return $modalStack; + }]) + + .provider('$modal', function () { + + var $modalProvider = { + options: { + backdrop: true, //can be also false or 'static' + keyboard: true + }, + $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', + function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { + + var $modal = {}; + + function getTemplatePromise(options) { + return options.template ? $q.when(options.template) : + $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) { + return result.data; + }); + } + + function getResolvePromises(resolves) { + var promisesArr = []; + angular.forEach(resolves, function (value, key) { + if (angular.isFunction(value) || angular.isArray(value)) { + promisesArr.push($q.when($injector.invoke(value))); + } + }); + return promisesArr; + } + + $modal.open = function (modalOptions) { + + var modalResultDeferred = $q.defer(); + var modalOpenedDeferred = $q.defer(); + + //prepare an instance of a modal to be injected into controllers and returned to a caller + var modalInstance = { + result: modalResultDeferred.promise, + opened: modalOpenedDeferred.promise, + close: function (result) { + $modalStack.close(modalInstance, result); + }, + dismiss: function (reason) { + $modalStack.dismiss(modalInstance, reason); + } + }; + + //merge and clean up options + modalOptions = angular.extend({}, $modalProvider.options, modalOptions); + modalOptions.resolve = modalOptions.resolve || {}; + + //verify options + if (!modalOptions.template && !modalOptions.templateUrl) { + throw new Error('One of template or templateUrl options is required.'); + } + + var templateAndResolvePromise = + $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); + + + templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { + + var modalScope = (modalOptions.scope || $rootScope).$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; + + var ctrlInstance, ctrlLocals = {}; + var resolveIter = 1; + + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$modalInstance = modalInstance; + angular.forEach(modalOptions.resolve, function (value, key) { + ctrlLocals[key] = tplAndVars[resolveIter++]; + }); + + ctrlInstance = $controller(modalOptions.controller, ctrlLocals); + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + content: tplAndVars[0], + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + windowClass: modalOptions.windowClass, + windowTemplateUrl: modalOptions.windowTemplateUrl, + size: modalOptions.size + }); + + }, function resolveError(reason) { + modalResultDeferred.reject(reason); + }); + + templateAndResolvePromise.then(function () { + modalOpenedDeferred.resolve(true); + }, function () { + modalOpenedDeferred.reject(false); + }); + + return modalInstance; + }; + + return $modal; + }] + }; + + return $modalProvider; + }); + +angular.module('ui.bootstrap.pagination', []) + +.controller('PaginationController', ['$scope', '$attrs', '$parse', function ($scope, $attrs, $parse) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; + + this.init = function(ngModelCtrl_, config) { + ngModelCtrl = ngModelCtrl_; + this.config = config; + + ngModelCtrl.$render = function() { + self.render(); + }; + + if ($attrs.itemsPerPage) { + $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { + self.itemsPerPage = parseInt(value, 10); + $scope.totalPages = self.calculateTotalPages(); + }); + } else { + this.itemsPerPage = config.itemsPerPage; + } + }; + + this.calculateTotalPages = function() { + var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); + return Math.max(totalPages || 0, 1); + }; + + this.render = function() { + $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1; + }; + + $scope.selectPage = function(page) { + if ( $scope.page !== page && page > 0 && page <= $scope.totalPages) { + ngModelCtrl.$setViewValue(page); + ngModelCtrl.$render(); + } + }; + + $scope.getText = function( key ) { + return $scope[key + 'Text'] || self.config[key + 'Text']; + }; + $scope.noPrevious = function() { + return $scope.page === 1; + }; + $scope.noNext = function() { + return $scope.page === $scope.totalPages; + }; + + $scope.$watch('totalItems', function() { + $scope.totalPages = self.calculateTotalPages(); + }); + + $scope.$watch('totalPages', function(value) { + setNumPages($scope.$parent, value); // Readonly variable + + if ( $scope.page > value ) { + $scope.selectPage(value); + } else { + ngModelCtrl.$render(); + } + }); +}]) + +.constant('paginationConfig', { + itemsPerPage: 10, + boundaryLinks: false, + directionLinks: true, + firstText: 'First', + previousText: 'Previous', + nextText: 'Next', + lastText: 'Last', + rotate: true +}) + +.directive('pagination', ['$parse', 'paginationConfig', function($parse, paginationConfig) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + firstText: '@', + previousText: '@', + nextText: '@', + lastText: '@' + }, + require: ['pagination', '?ngModel'], + controller: 'PaginationController', + templateUrl: 'template/pagination/pagination.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + // Setup configuration parameters + var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize, + rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate; + scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks; + scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks; + + paginationCtrl.init(ngModelCtrl, paginationConfig); + + if (attrs.maxSize) { + scope.$parent.$watch($parse(attrs.maxSize), function(value) { + maxSize = parseInt(value, 10); + paginationCtrl.render(); + }); + } + + // Create page object used in template + function makePage(number, text, isActive) { + return { + number: number, + text: text, + active: isActive + }; + } + + function getPages(currentPage, totalPages) { + var pages = []; + + // Default page limits + var startPage = 1, endPage = totalPages; + var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages ); + + // recompute if maxSize + if ( isMaxSized ) { + if ( rotate ) { + // Current page is displayed in the middle of the visible ones + startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); + endPage = startPage + maxSize - 1; + + // Adjust if limit is exceeded + if (endPage > totalPages) { + endPage = totalPages; + startPage = endPage - maxSize + 1; + } + } else { + // Visible pages are paginated with maxSize + startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1; + + // Adjust last page if limit is exceeded + endPage = Math.min(startPage + maxSize - 1, totalPages); + } + } + + // Add page number links + for (var number = startPage; number <= endPage; number++) { + var page = makePage(number, number, number === currentPage); + pages.push(page); + } + + // Add links to move between page sets + if ( isMaxSized && ! rotate ) { + if ( startPage > 1 ) { + var previousPageSet = makePage(startPage - 1, '...', false); + pages.unshift(previousPageSet); + } + + if ( endPage < totalPages ) { + var nextPageSet = makePage(endPage + 1, '...', false); + pages.push(nextPageSet); + } + } + + return pages; + } + + var originalRender = paginationCtrl.render; + paginationCtrl.render = function() { + originalRender(); + if (scope.page > 0 && scope.page <= scope.totalPages) { + scope.pages = getPages(scope.page, scope.totalPages); + } + }; + } + }; +}]) + +.constant('pagerConfig', { + itemsPerPage: 10, + previousText: '« Previous', + nextText: 'Next »', + align: true +}) + +.directive('pager', ['pagerConfig', function(pagerConfig) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + previousText: '@', + nextText: '@' + }, + require: ['pager', '?ngModel'], + controller: 'PaginationController', + templateUrl: 'template/pagination/pager.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align; + paginationCtrl.init(ngModelCtrl, pagerConfig); + } + }; +}]); + +/** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ +angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider( '$tooltip', function () { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + animation: true, + popupDelay: 0 + }; + + // Default hide triggers for each show trigger + var triggerMap = { + 'mouseenter': 'mouseleave', + 'click': 'click', + 'focus': 'blur' + }; + + // The options specified to the provider globally. + var globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function( value ) { + angular.extend( globalOptions, value ); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); + */ + this.setTriggers = function setTriggers ( triggers ) { + angular.extend( triggerMap, triggers ); + }; + + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name){ + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) { + return function $tooltip ( type, prefix, defaultTriggerShow ) { + var options = angular.extend( {}, defaultOptions, globalOptions ); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers ( trigger ) { + var show = trigger || options.trigger || defaultTriggerShow; + var hide = triggerMap[show] || show; + return { + show: show, + hide: hide + }; + } + + var directiveName = snake_case( type ); + + var startSym = $interpolate.startSymbol(); + var endSym = $interpolate.endSymbol(); + var template = + '
      '+ + '
      '; + + return { + restrict: 'EA', + scope: true, + compile: function (tElem, tAttrs) { + var tooltipLinker = $compile( template ); + + return function link ( scope, element, attrs ) { + var tooltip; + var transitionTimeout; + var popupTimeout; + var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; + var triggers = getTriggers( undefined ); + var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + + var positionTooltip = function () { + + var ttPosition = $position.positionElements(element, tooltip, scope.tt_placement, appendToBody); + ttPosition.top += 'px'; + ttPosition.left += 'px'; + + // Now set the calculated positioning. + tooltip.css( ttPosition ); + }; + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + scope.tt_isOpen = false; + + function toggleTooltipBind () { + if ( ! scope.tt_isOpen ) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { + return; + } + if ( scope.tt_popupDelay ) { + // Do nothing if the tooltip was already scheduled to pop-up. + // This happens if show is triggered multiple times before any hide is triggered. + if (!popupTimeout) { + popupTimeout = $timeout( show, scope.tt_popupDelay, false ); + popupTimeout.then(function(reposition){reposition();}); + } + } else { + show()(); + } + } + + function hideTooltipBind () { + scope.$apply(function () { + hide(); + }); + } + + // Show the tooltip popup element. + function show() { + + popupTimeout = null; + + // If there is a pending remove transition, we must cancel it, lest the + // tooltip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + transitionTimeout = null; + } + + // Don't show empty tooltips. + if ( ! scope.tt_content ) { + return angular.noop; + } + + createTooltip(); + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + if ( appendToBody ) { + $document.find( 'body' ).append( tooltip ); + } else { + element.after( tooltip ); + } + + positionTooltip(); + + // And show the tooltip. + scope.tt_isOpen = true; + scope.$digest(); // digest required as $apply is not called + + // Return positioning function as promise callback for correct + // positioning after draw. + return positionTooltip; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + scope.tt_isOpen = false; + + //if tooltip is going to be shown after delay, we must cancel this + $timeout.cancel( popupTimeout ); + popupTimeout = null; + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( scope.tt_animation ) { + if (!transitionTimeout) { + transitionTimeout = $timeout(removeTooltip, 500); + } + } else { + removeTooltip(); + } + } + + function createTooltip() { + // There can only be one tooltip element per directive shown at once. + if (tooltip) { + removeTooltip(); + } + tooltip = tooltipLinker(scope, function () {}); + + // Get contents rendered into the tooltip + scope.$digest(); + } + + function removeTooltip() { + transitionTimeout = null; + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + } + + /** + * Observe the relevant attributes. + */ + attrs.$observe( type, function ( val ) { + scope.tt_content = val; + + if (!val && scope.tt_isOpen ) { + hide(); + } + }); + + attrs.$observe( prefix+'Title', function ( val ) { + scope.tt_title = val; + }); + + attrs.$observe( prefix+'Placement', function ( val ) { + scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + }); + + attrs.$observe( prefix+'PopupDelay', function ( val ) { + var delay = parseInt( val, 10 ); + scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + }); + + var unregisterTriggers = function () { + element.unbind(triggers.show, showTooltipBind); + element.unbind(triggers.hide, hideTooltipBind); + }; + + attrs.$observe( prefix+'Trigger', function ( val ) { + unregisterTriggers(); + + triggers = getTriggers( val ); + + if ( triggers.show === triggers.hide ) { + element.bind( triggers.show, toggleTooltipBind ); + } else { + element.bind( triggers.show, showTooltipBind ); + element.bind( triggers.hide, hideTooltipBind ); + } + }); + + var animation = scope.$eval(attrs[prefix + 'Animation']); + scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation; + + attrs.$observe( prefix+'AppendToBody', function ( val ) { + appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody; + }); + + // if a tooltip is attached to we need to remove it on + // location change as its parent scope will probably not be destroyed + // by the change. + if ( appendToBody ) { + scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { + if ( scope.tt_isOpen ) { + hide(); + } + }); + } + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + $timeout.cancel( transitionTimeout ); + $timeout.cancel( popupTimeout ); + unregisterTriggers(); + removeTooltip(); + }); + }; + } + }; + }; + }]; +}) + +.directive( 'tooltipPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html' + }; +}) + +.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); +}]) + +.directive( 'tooltipHtmlUnsafePopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + }; +}) + +.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); +}]); + +/** + * The following features are still outstanding: popup delay, animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html popovers, and selector delegatation. + */ +angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) + +.directive( 'popoverPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover.html' + }; +}) + +.directive( 'popover', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'popover', 'popover', 'click' ); +}]); + +angular.module('ui.bootstrap.progressbar', []) + +.constant('progressConfig', { + animate: true, + max: 100 +}) + +.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) { + var self = this, + animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; + + this.bars = []; + $scope.max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max; + + this.addBar = function(bar, element) { + if ( !animate ) { + element.css({'transition': 'none'}); + } + + this.bars.push(bar); + + bar.$watch('value', function( value ) { + bar.percent = +(100 * value / $scope.max).toFixed(2); + }); + + bar.$on('$destroy', function() { + element = null; + self.removeBar(bar); + }); + }; + + this.removeBar = function(bar) { + this.bars.splice(this.bars.indexOf(bar), 1); + }; +}]) + +.directive('progress', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + require: 'progress', + scope: {}, + templateUrl: 'template/progressbar/progress.html' + }; +}) + +.directive('bar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + require: '^progress', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/bar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, element); + } + }; +}) + +.directive('progressbar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/progressbar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, angular.element(element.children()[0])); + } + }; +}); +angular.module('ui.bootstrap.rating', []) + +.constant('ratingConfig', { + max: 5, + stateOn: null, + stateOff: null +}) + +.controller('RatingController', ['$scope', '$attrs', 'ratingConfig', function($scope, $attrs, ratingConfig) { + var ngModelCtrl = { $setViewValue: angular.noop }; + + this.init = function(ngModelCtrl_) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; + this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; + + var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) : + new Array( angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max ); + $scope.range = this.buildTemplateObjects(ratingStates); + }; + + this.buildTemplateObjects = function(states) { + for (var i = 0, n = states.length; i < n; i++) { + states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff }, states[i]); + } + return states; + }; + + $scope.rate = function(value) { + if ( !$scope.readonly && value >= 0 && value <= $scope.range.length ) { + ngModelCtrl.$setViewValue(value); + ngModelCtrl.$render(); + } + }; + + $scope.enter = function(value) { + if ( !$scope.readonly ) { + $scope.value = value; + } + $scope.onHover({value: value}); + }; + + $scope.reset = function() { + $scope.value = ngModelCtrl.$viewValue; + $scope.onLeave(); + }; + + $scope.onKeydown = function(evt) { + if (/(37|38|39|40)/.test(evt.which)) { + evt.preventDefault(); + evt.stopPropagation(); + $scope.rate( $scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1) ); + } + }; + + this.render = function() { + $scope.value = ngModelCtrl.$viewValue; + }; +}]) + +.directive('rating', function() { + return { + restrict: 'EA', + require: ['rating', 'ngModel'], + scope: { + readonly: '=?', + onHover: '&', + onLeave: '&' + }, + controller: 'RatingController', + templateUrl: 'template/rating/rating.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + ratingCtrl.init( ngModelCtrl ); + } + } + }; +}); + +/** + * @ngdoc overview + * @name ui.bootstrap.tabs + * + * @description + * AngularJS version of the tabs directive. + */ + +angular.module('ui.bootstrap.tabs', []) + +.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { + var ctrl = this, + tabs = ctrl.tabs = $scope.tabs = []; + + ctrl.select = function(selectedTab) { + angular.forEach(tabs, function(tab) { + if (tab.active && tab !== selectedTab) { + tab.active = false; + tab.onDeselect(); + } + }); + selectedTab.active = true; + selectedTab.onSelect(); + }; + + ctrl.addTab = function addTab(tab) { + tabs.push(tab); + // we can't run the select function on the first tab + // since that would select it twice + if (tabs.length === 1) { + tab.active = true; + } else if (tab.active) { + ctrl.select(tab); + } + }; + + ctrl.removeTab = function removeTab(tab) { + var index = tabs.indexOf(tab); + //Select a new tab if the tab to be removed is selected + if (tab.active && tabs.length > 1) { + //If this is the last tab, select the previous tab. else, the next tab. + var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; + ctrl.select(tabs[newActiveIndex]); + } + tabs.splice(index, 1); + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabset + * @restrict EA + * + * @description + * Tabset is the outer container for the tabs directive + * + * @param {boolean=} vertical Whether or not to use vertical styling for the tabs. + * @param {boolean=} justified Whether or not to use justified styling for the tabs. + * + * @example + + + + First Content! + Second Content! + +
      + + First Vertical Content! + Second Vertical Content! + + + First Justified Content! + Second Justified Content! + +
      +
      + */ +.directive('tabset', function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + scope: { + type: '@' + }, + controller: 'TabsetController', + templateUrl: 'template/tabs/tabset.html', + link: function(scope, element, attrs) { + scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; + scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; + } + }; +}) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tab + * @restrict EA + * + * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. + * @param {string=} select An expression to evaluate when the tab is selected. + * @param {boolean=} active A binding, telling whether or not this tab is selected. + * @param {boolean=} disabled A binding, telling whether or not this tab is disabled. + * + * @description + * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. + * + * @example + + +
      + + +
      + + First Tab + + Alert me! + Second Tab, with alert callback and html heading! + + + {{item.content}} + + +
      +
      + + function TabsDemoCtrl($scope) { + $scope.items = [ + { title:"Dynamic Title 1", content:"Dynamic Item 0" }, + { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } + ]; + + $scope.alertMe = function() { + setTimeout(function() { + alert("You've selected the alert tab!"); + }); + }; + }; + +
      + */ + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabHeading + * @restrict EA + * + * @description + * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. + * + * @example + + + + + HTML in my titles?! + And some content, too! + + + Icon heading?!? + That's right. + + + + + */ +.directive('tab', ['$parse', function($parse) { + return { + require: '^tabset', + restrict: 'EA', + replace: true, + templateUrl: 'template/tabs/tab.html', + transclude: true, + scope: { + active: '=?', + heading: '@', + onSelect: '&select', //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + onDeselect: '&deselect' + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab + }, + compile: function(elm, attrs, transclude) { + return function postLink(scope, elm, attrs, tabsetCtrl) { + scope.$watch('active', function(active) { + if (active) { + tabsetCtrl.select(scope); + } + }); + + scope.disabled = false; + if ( attrs.disabled ) { + scope.$parent.$watch($parse(attrs.disabled), function(value) { + scope.disabled = !! value; + }); + } + + scope.select = function() { + if ( !scope.disabled ) { + scope.active = true; + } + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; + }; + } + }; +}]) + +.directive('tabHeadingTransclude', [function() { + return { + restrict: 'A', + require: '^tab', + link: function(scope, elm, attrs, tabCtrl) { + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); + } + }); + } + }; +}]) + +.directive('tabContentTransclude', function() { + return { + restrict: 'A', + require: '^tabset', + link: function(scope, elm, attrs) { + var tab = scope.$eval(attrs.tabContentTransclude); + + //Now our tab is ready to be transcluded: both the tab heading area + //and the tab content area are loaded. Transclude 'em both. + tab.$transcludeFn(tab.$parent, function(contents) { + angular.forEach(contents, function(node) { + if (isTabHeading(node)) { + //Let tabHeadingTransclude know. + tab.headingElement = node; + } else { + elm.append(node); + } + }); + }); + } + }; + function isTabHeading(node) { + return node.tagName && ( + node.hasAttribute('tab-heading') || + node.hasAttribute('data-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || + node.tagName.toLowerCase() === 'data-tab-heading' + ); + } +}) + +; + +angular.module('ui.bootstrap.timepicker', []) + +.constant('timepickerConfig', { + hourStep: 1, + minuteStep: 1, + showMeridian: true, + meridians: null, + readonlyInput: false, + mousewheel: true +}) + +.controller('TimepickerController', ['$scope', '$attrs', '$parse', '$log', '$locale', 'timepickerConfig', function($scope, $attrs, $parse, $log, $locale, timepickerConfig) { + var selected = new Date(), + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; + + this.init = function( ngModelCtrl_, inputs ) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + var hoursInputEl = inputs.eq(0), + minutesInputEl = inputs.eq(1); + + var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; + if ( mousewheel ) { + this.setupMousewheelEvents( hoursInputEl, minutesInputEl ); + } + + $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; + this.setupInputEvents( hoursInputEl, minutesInputEl ); + }; + + var hourStep = timepickerConfig.hourStep; + if ($attrs.hourStep) { + $scope.$parent.$watch($parse($attrs.hourStep), function(value) { + hourStep = parseInt(value, 10); + }); + } + + var minuteStep = timepickerConfig.minuteStep; + if ($attrs.minuteStep) { + $scope.$parent.$watch($parse($attrs.minuteStep), function(value) { + minuteStep = parseInt(value, 10); + }); + } + + // 12H / 24H mode + $scope.showMeridian = timepickerConfig.showMeridian; + if ($attrs.showMeridian) { + $scope.$parent.$watch($parse($attrs.showMeridian), function(value) { + $scope.showMeridian = !!value; + + if ( ngModelCtrl.$error.time ) { + // Evaluate from template + var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); + if (angular.isDefined( hours ) && angular.isDefined( minutes )) { + selected.setHours( hours ); + refresh(); + } + } else { + updateTemplate(); + } + }); + } + + // Get $scope.hours in 24H mode if valid + function getHoursFromTemplate ( ) { + var hours = parseInt( $scope.hours, 10 ); + var valid = ( $scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); + if ( !valid ) { + return undefined; + } + + if ( $scope.showMeridian ) { + if ( hours === 12 ) { + hours = 0; + } + if ( $scope.meridian === meridians[1] ) { + hours = hours + 12; + } + } + return hours; + } + + function getMinutesFromTemplate() { + var minutes = parseInt($scope.minutes, 10); + return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined; + } + + function pad( value ) { + return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value; + } + + // Respond on mousewheel spin + this.setupMousewheelEvents = function( hoursInputEl, minutesInputEl ) { + var isScrollingUp = function(e) { + if (e.originalEvent) { + e = e.originalEvent; + } + //pick correct delta variable depending on event + var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY; + return (e.detail || delta > 0); + }; + + hoursInputEl.bind('mousewheel wheel', function(e) { + $scope.$apply( (isScrollingUp(e)) ? $scope.incrementHours() : $scope.decrementHours() ); + e.preventDefault(); + }); + + minutesInputEl.bind('mousewheel wheel', function(e) { + $scope.$apply( (isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes() ); + e.preventDefault(); + }); + + }; + + this.setupInputEvents = function( hoursInputEl, minutesInputEl ) { + if ( $scope.readonlyInput ) { + $scope.updateHours = angular.noop; + $scope.updateMinutes = angular.noop; + return; + } + + var invalidate = function(invalidHours, invalidMinutes) { + ngModelCtrl.$setViewValue( null ); + ngModelCtrl.$setValidity('time', false); + if (angular.isDefined(invalidHours)) { + $scope.invalidHours = invalidHours; + } + if (angular.isDefined(invalidMinutes)) { + $scope.invalidMinutes = invalidMinutes; + } + }; + + $scope.updateHours = function() { + var hours = getHoursFromTemplate(); + + if ( angular.isDefined(hours) ) { + selected.setHours( hours ); + refresh( 'h' ); + } else { + invalidate(true); + } + }; + + hoursInputEl.bind('blur', function(e) { + if ( !$scope.invalidHours && $scope.hours < 10) { + $scope.$apply( function() { + $scope.hours = pad( $scope.hours ); + }); + } + }); + + $scope.updateMinutes = function() { + var minutes = getMinutesFromTemplate(); + + if ( angular.isDefined(minutes) ) { + selected.setMinutes( minutes ); + refresh( 'm' ); + } else { + invalidate(undefined, true); + } + }; + + minutesInputEl.bind('blur', function(e) { + if ( !$scope.invalidMinutes && $scope.minutes < 10 ) { + $scope.$apply( function() { + $scope.minutes = pad( $scope.minutes ); + }); + } + }); + + }; + + this.render = function() { + var date = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : null; + + if ( isNaN(date) ) { + ngModelCtrl.$setValidity('time', false); + $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } else { + if ( date ) { + selected = date; + } + makeValid(); + updateTemplate(); + } + }; + + // Call internally when we know that model is valid. + function refresh( keyboardChange ) { + makeValid(); + ngModelCtrl.$setViewValue( new Date(selected) ); + updateTemplate( keyboardChange ); + } + + function makeValid() { + ngModelCtrl.$setValidity('time', true); + $scope.invalidHours = false; + $scope.invalidMinutes = false; + } + + function updateTemplate( keyboardChange ) { + var hours = selected.getHours(), minutes = selected.getMinutes(); + + if ( $scope.showMeridian ) { + hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system + } + + $scope.hours = keyboardChange === 'h' ? hours : pad(hours); + $scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes); + $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; + } + + function addMinutes( minutes ) { + var dt = new Date( selected.getTime() + minutes * 60000 ); + selected.setHours( dt.getHours(), dt.getMinutes() ); + refresh(); + } + + $scope.incrementHours = function() { + addMinutes( hourStep * 60 ); + }; + $scope.decrementHours = function() { + addMinutes( - hourStep * 60 ); + }; + $scope.incrementMinutes = function() { + addMinutes( minuteStep ); + }; + $scope.decrementMinutes = function() { + addMinutes( - minuteStep ); + }; + $scope.toggleMeridian = function() { + addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) ); + }; +}]) + +.directive('timepicker', function () { + return { + restrict: 'EA', + require: ['timepicker', '?^ngModel'], + controller:'TimepickerController', + replace: true, + scope: {}, + templateUrl: 'template/timepicker/timepicker.html', + link: function(scope, element, attrs, ctrls) { + var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + timepickerCtrl.init( ngModelCtrl, element.find('input') ); + } + } + }; +}); + +angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml']) + +/** + * A helper service that can parse typeahead's syntax (string provided by users) + * Extracted to a separate service for ease of unit testing + */ + .factory('typeaheadParser', ['$parse', function ($parse) { + + // 00000111000000000000022200000000000000003333333333333330000000000044000 + var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/; + + return { + parse:function (input) { + + var match = input.match(TYPEAHEAD_REGEXP); + if (!match) { + throw new Error( + 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + + ' but got "' + input + '".'); + } + + return { + itemName:match[3], + source:$parse(match[4]), + viewMapper:$parse(match[2] || match[1]), + modelMapper:$parse(match[1]) + }; + } + }; +}]) + + .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', + function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) { + + var HOT_KEYS = [9, 13, 27, 38, 40]; + + return { + require:'ngModel', + link:function (originalScope, element, attrs, modelCtrl) { + + //SUPPORTED ATTRIBUTES (OPTIONS) + + //minimal no of characters that needs to be entered before typeahead kicks-in + var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; + + //minimal wait time after last character typed before typehead kicks-in + var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; + + //should it restrict model values to the ones selected from the popup only? + var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; + + //binding to a variable that indicates if matches are being retrieved asynchronously + var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + + //a callback executed when a match is selected + var onSelectCallback = $parse(attrs.typeaheadOnSelect); + + var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; + + var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; + + //INTERNAL VARIABLES + + //model setter executed upon match selection + var $setModelValue = $parse(attrs.ngModel).assign; + + //expressions used by typeahead + var parserResult = typeaheadParser.parse(attrs.typeahead); + + var hasFocus; + + //create a child scope for the typeahead directive so we are not polluting original scope + //with typeahead-specific data (matches, query etc.) + var scope = originalScope.$new(); + originalScope.$on('$destroy', function(){ + scope.$destroy(); + }); + + // WAI-ARIA + var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); + element.attr({ + 'aria-autocomplete': 'list', + 'aria-expanded': false, + 'aria-owns': popupId + }); + + //pop-up element used to display matches + var popUpEl = angular.element('
      '); + popUpEl.attr({ + id: popupId, + matches: 'matches', + active: 'activeIdx', + select: 'select(activeIdx)', + query: 'query', + position: 'position' + }); + //custom item template + if (angular.isDefined(attrs.typeaheadTemplateUrl)) { + popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); + } + + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + element.attr('aria-expanded', false); + }; + + var getMatchId = function(index) { + return popupId + '-option-' + index; + }; + + // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. + // This attribute is added or removed automatically when the `activeIdx` changes. + scope.$watch('activeIdx', function(index) { + if (index < 0) { + element.removeAttr('aria-activedescendant'); + } else { + element.attr('aria-activedescendant', getMatchId(index)); + } + }); + + var getMatchesAsync = function(inputValue) { + + var locals = {$viewValue: inputValue}; + isLoadingSetter(originalScope, true); + $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + + //it might happen that several async queries were in progress if a user were typing fast + //but we are interested only in responses that correspond to the current view value + var onCurrentRequest = (inputValue === modelCtrl.$viewValue); + if (onCurrentRequest && hasFocus) { + if (matches.length > 0) { + + scope.activeIdx = 0; + scope.matches.length = 0; + + //transform labels + for(var i=0; i= minSearch) { + if (waitTime > 0) { + if (timeoutPromise) { + $timeout.cancel(timeoutPromise);//cancel previous timeout + } + timeoutPromise = $timeout(function () { + getMatchesAsync(inputValue); + }, waitTime); + } else { + getMatchesAsync(inputValue); + } + } else { + isLoadingSetter(originalScope, false); + resetMatches(); + } + + if (isEditable) { + return inputValue; + } else { + if (!inputValue) { + // Reset in case user had typed something previously. + modelCtrl.$setValidity('editable', true); + return inputValue; + } else { + modelCtrl.$setValidity('editable', false); + return undefined; + } + } + }); + + modelCtrl.$formatters.push(function (modelValue) { + + var candidateViewValue, emptyViewValue; + var locals = {}; + + if (inputFormatter) { + + locals['$model'] = modelValue; + return inputFormatter(originalScope, locals); + + } else { + + //it might happen that we don't have enough info to properly render input value + //we need to check for this situation and simply return model value if we can't apply custom formatting + locals[parserResult.itemName] = modelValue; + candidateViewValue = parserResult.viewMapper(originalScope, locals); + locals[parserResult.itemName] = undefined; + emptyViewValue = parserResult.viewMapper(originalScope, locals); + + return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; + } + }); + + scope.select = function (activeIdx) { + //called from within the $digest() cycle + var locals = {}; + var model, item; + + locals[parserResult.itemName] = item = scope.matches[activeIdx].model; + model = parserResult.modelMapper(originalScope, locals); + $setModelValue(originalScope, model); + modelCtrl.$setValidity('editable', true); + + onSelectCallback(originalScope, { + $item: item, + $model: model, + $label: parserResult.viewMapper(originalScope, locals) + }); + + resetMatches(); + + //return focus to the input element if a match was selected via a mouse click event + // use timeout to avoid $rootScope:inprog error + $timeout(function() { element[0].focus(); }, 0, false); + }; + + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) + element.bind('keydown', function (evt) { + + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } + + evt.preventDefault(); + + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); + + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); + + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); + + } else if (evt.which === 27) { + evt.stopPropagation(); + + resetMatches(); + scope.$digest(); + } + }); + + element.bind('blur', function (evt) { + hasFocus = false; + }); + + // Keep reference to click handler to unbind it. + var dismissClickHandler = function (evt) { + if (element[0] !== evt.target) { + resetMatches(); + scope.$digest(); + } + }; + + $document.bind('click', dismissClickHandler); + + originalScope.$on('$destroy', function(){ + $document.unbind('click', dismissClickHandler); + }); + + var $popup = $compile(popUpEl)(scope); + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + } + }; + +}]) + + .directive('typeaheadPopup', function () { + return { + restrict:'EA', + scope:{ + matches:'=', + query:'=', + active:'=', + position:'=', + select:'&' + }, + replace:true, + templateUrl:'template/typeahead/typeahead-popup.html', + link:function (scope, element, attrs) { + + scope.templateUrl = attrs.templateUrl; + + scope.isOpen = function () { + return scope.matches.length > 0; + }; + + scope.isActive = function (matchIdx) { + return scope.active == matchIdx; + }; + + scope.selectActive = function (matchIdx) { + scope.active = matchIdx; + }; + + scope.selectMatch = function (activeIdx) { + scope.select({activeIdx:activeIdx}); + }; + } + }; + }) + + .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) { + return { + restrict:'EA', + scope:{ + index:'=', + match:'=', + query:'=' + }, + link:function (scope, element, attrs) { + var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; + $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){ + element.replaceWith($compile(tplContent.trim())(scope)); + }); + } + }; + }]) + + .filter('typeaheadHighlight', function() { + + function escapeRegexp(queryToEscape) { + return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); + } + + return function(matchItem, query) { + return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; + }; + }); diff --git a/src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap.min.js b/src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap.min.js new file mode 100644 index 0000000..53fd24e --- /dev/null +++ b/src/main/webapp/subscriber/ui/js/angular-bootstrap/ui-bootstrap.min.js @@ -0,0 +1,9 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.11.0 - 2014-05-01 + * License: MIT + */ +angular.module("ui.bootstrap",["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(a){return{link:function(b,c,d){function e(b){function d(){j===e&&(j=void 0)}var e=a(c,b);return j&&j.cancel(),j=e,e.then(d,d),e}function f(){k?(k=!1,g()):(c.removeClass("collapse").addClass("collapsing"),e({height:c[0].scrollHeight+"px"}).then(g))}function g(){c.removeClass("collapsing"),c.addClass("collapse in"),c.css({height:"auto"})}function h(){if(k)k=!1,i(),c.css({height:0});else{c.css({height:c[0].scrollHeight+"px"});{c[0].offsetWidth}c.removeClass("collapse in").addClass("collapsing"),e({height:0}).then(i)}}function i(){c.removeClass("collapsing"),c.addClass("collapse")}var j,k=!0;b.$watch(d.collapse,function(a){a?h():f()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.$watch("isOpen",function(b){b&&d.closeOthers(a)}),a.toggleOpen=function(){a.isDisabled||(a.isOpen=!a.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("ui.bootstrap.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}),angular.module("ui.bootstrap.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("ui.bootstrap.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){var d=b.hasClass(e.activeClass);(!d||angular.isDefined(c.uncheckable))&&a.$apply(function(){f.$setViewValue(d?null:a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.carousel",["ui.bootstrap.transition"]).controller("CarouselController",["$scope","$timeout","$transition",function(a,b,c){function d(){e();var c=+a.interval;!isNaN(c)&&c>=0&&(g=b(f,c))}function e(){g&&(b.cancel(g),g=null)}function f(){h?(a.next(),d()):a.pause()}var g,h,i=this,j=i.slides=a.slides=[],k=-1;i.currentSlide=null;var l=!1;i.select=a.select=function(e,f){function g(){if(!l){if(i.currentSlide&&angular.isString(f)&&!a.noTransition&&e.$element){e.$element.addClass(f);{e.$element[0].offsetWidth}angular.forEach(j,function(a){angular.extend(a,{direction:"",entering:!1,leaving:!1,active:!1})}),angular.extend(e,{direction:f,active:!0,entering:!0}),angular.extend(i.currentSlide||{},{direction:f,leaving:!0}),a.$currentTransition=c(e.$element,{}),function(b,c){a.$currentTransition.then(function(){h(b,c)},function(){h(b,c)})}(e,i.currentSlide)}else h(e,i.currentSlide);i.currentSlide=e,k=m,d()}}function h(b,c){angular.extend(b,{direction:"",active:!0,leaving:!1,entering:!1}),angular.extend(c||{},{direction:"",active:!1,leaving:!1,entering:!1}),a.$currentTransition=null}var m=j.indexOf(e);void 0===f&&(f=m>k?"next":"prev"),e&&e!==i.currentSlide&&(a.$currentTransition?(a.$currentTransition.cancel(),b(g)):g())},a.$on("$destroy",function(){l=!0}),i.indexOfSlide=function(a){return j.indexOf(a)},a.next=function(){var b=(k+1)%j.length;return a.$currentTransition?void 0:i.select(j[b],"next")},a.prev=function(){var b=0>k-1?j.length-1:k-1;return a.$currentTransition?void 0:i.select(j[b],"prev")},a.isActive=function(a){return i.currentSlide===a},a.$watch("interval",d),a.$on("$destroy",e),a.play=function(){h||(h=!0,d())},a.pause=function(){a.noPause||(h=!1,e())},i.addSlide=function(b,c){b.$element=c,j.push(b),1===j.length||b.active?(i.select(j[j.length-1]),1==j.length&&a.play()):b.active=!1},i.removeSlide=function(a){var b=j.indexOf(a);j.splice(b,1),j.length>0&&a.active?i.select(b>=j.length?j[b-1]:j[b]):k>b&&k--}}]).directive("carousel",[function(){return{restrict:"EA",transclude:!0,replace:!0,controller:"CarouselController",require:"carousel",templateUrl:"template/carousel/carousel.html",scope:{interval:"=",noTransition:"=",noPause:"="}}}]).directive("slide",function(){return{require:"^carousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/carousel/slide.html",scope:{active:"=?"},link:function(a,b,c,d){d.addSlide(a,b),a.$on("$destroy",function(){d.removeSlide(a)}),a.$watch("active",function(b){b&&d.select(a)})}}}),angular.module("ui.bootstrap.dateparser",[]).service("dateParser",["$locale","orderByFilter",function(a,b){function c(a,b,c){return 1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}this.parsers={};var d={yyyy:{regex:"\\d{4}",apply:function(a){this.year=+a}},yy:{regex:"\\d{2}",apply:function(a){this.year=+a+2e3}},y:{regex:"\\d{1,4}",apply:function(a){this.year=+a}},MMMM:{regex:a.DATETIME_FORMATS.MONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.MONTH.indexOf(b)}},MMM:{regex:a.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.SHORTMONTH.indexOf(b)}},MM:{regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1}},M:{regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1}},dd:{regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},d:{regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},EEEE:{regex:a.DATETIME_FORMATS.DAY.join("|")},EEE:{regex:a.DATETIME_FORMATS.SHORTDAY.join("|")}};this.createParser=function(a){var c=[],e=a.split("");return angular.forEach(d,function(b,d){var f=a.indexOf(d);if(f>-1){a=a.split(""),e[f]="("+b.regex+")",a[f]="$";for(var g=f+1,h=f+d.length;h>g;g++)e[g]="",a[g]="$";a=a.join(""),c.push({index:f,apply:b.apply})}}),{regex:new RegExp("^"+e.join("")+"$"),map:b(c,"index")}},this.parse=function(b,d){if(!angular.isString(b))return b;d=a.DATETIME_FORMATS[d]||d,this.parsers[d]||(this.parsers[d]=this.createParser(d));var e=this.parsers[d],f=e.regex,g=e.map,h=b.match(f);if(h&&h.length){for(var i,j={year:1900,month:0,date:1,hours:0},k=1,l=h.length;l>k;k++){var m=g[k-1];m.apply&&m.apply.call(j,h[k])}return c(j.year,j.month,j.date)&&(i=new Date(j.year,j.month,j.date,j.hours)),i}}}]),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].documentElement.scrollLeft)}},positionElements:function(a,b,c,d){var e,f,g,h,i=c.split("-"),j=i[0],k=i[1]||"center";e=d?this.offset(a):this.position(a),f=b.prop("offsetWidth"),g=b.prop("offsetHeight");var l={center:function(){return e.left+e.width/2-f/2},left:function(){return e.left},right:function(){return e.left+e.width}},m={center:function(){return e.top+e.height/2-g/2},top:function(){return e.top},bottom:function(){return e.top+e.height}};switch(j){case"right":h={top:m[k](),left:l[j]()};break;case"left":h={top:m[k](),left:e.left-f};break;case"bottom":h={top:m[j](),left:l[k]()};break;default:h={top:e.top-g,left:l[k]()}}return h}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.position"]).constant("datepickerConfig",{formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",datepickerMode:"day",minMode:"day",maxMode:"year",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null}).controller("DatepickerController",["$scope","$attrs","$parse","$interpolate","$timeout","$log","dateFilter","datepickerConfig",function(a,b,c,d,e,f,g,h){var i=this,j={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","minMode","maxMode","showWeeks","startingDay","yearRange"],function(c,e){i[c]=angular.isDefined(b[c])?8>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):h[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){i[d]=a?new Date(a):null,i.refreshView()}):i[d]=h[d]?new Date(h[d]):null}),a.datepickerMode=a.datepickerMode||h.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),this.activeDate=angular.isDefined(b.initDate)?a.$parent.$eval(b.initDate):new Date,a.isActive=function(b){return 0===i.compare(b.date,i.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){j=a,j.$render=function(){i.render()}},this.render=function(){if(j.$modelValue){var a=new Date(j.$modelValue),b=!isNaN(a);b?this.activeDate=a:f.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'),j.$setValidity("date",b)}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=j.$modelValue?new Date(j.$modelValue):null;j.$setValidity("date-disabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=j.$modelValue?new Date(j.$modelValue):null;return{date:a,label:g(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===i.minMode){var c=j.$modelValue?new Date(j.$modelValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),j.$setViewValue(c),j.$render()}else i.activeDate=b,a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=i.activeDate.getFullYear()+a*(i.step.years||0),c=i.activeDate.getMonth()+a*(i.step.months||0);i.activeDate.setFullYear(b,c,1),i.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===i.maxMode&&1===b||a.datepickerMode===i.minMode&&-1===b||(a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var k=function(){e(function(){i.element[0].focus()},0,!1)};a.$on("datepicker.focus",k),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),b.stopPropagation(),"enter"===c||"space"===c){if(i.isDisabled(i.activeDate))return;a.select(i.activeDate),k()}else!b.ctrlKey||"up"!==c&&"down"!==c?(i.handleKeyDown(c,b),i.refreshView()):(a.toggleMode("up"===c?1:-1),k())}}]).directive("datepicker",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/datepicker.html",scope:{datepickerMode:"=?",dateDisabled:"&"},require:["datepicker","?^ngModel"],controller:"DatepickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}).directive("daypicker",["dateFilter",function(a){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/day.html",require:"^datepicker",link:function(b,c,d,e){function f(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?i[b]:29}function g(a,b){var c=new Array(b),d=new Date(a),e=0;for(d.setHours(12);b>e;)c[e++]=new Date(d),d.setDate(d.getDate()+1);return c}function h(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}b.showWeeks=e.showWeeks,e.step={months:1},e.element=c;var i=[31,28,31,30,31,30,31,31,30,31,30,31];e._refreshView=function(){var c=e.activeDate.getFullYear(),d=e.activeDate.getMonth(),f=new Date(c,d,1),i=e.startingDay-f.getDay(),j=i>0?7-i:-i,k=new Date(f);j>0&&k.setDate(-j+1);for(var l=g(k,42),m=0;42>m;m++)l[m]=angular.extend(e.createDateObject(l[m],e.formatDay),{secondary:l[m].getMonth()!==d,uid:b.uniqueId+"-"+m});b.labels=new Array(7);for(var n=0;7>n;n++)b.labels[n]={abbr:a(l[n].date,e.formatDayHeader),full:a(l[n].date,"EEEE")};if(b.title=a(e.activeDate,e.formatDayTitle),b.rows=e.split(l,7),b.showWeeks){b.weekNumbers=[];for(var o=h(b.rows[0][0].date),p=b.rows.length;b.weekNumbers.push(o++)f;f++)c[f]=angular.extend(e.createDateObject(new Date(d,f,1),e.formatMonth),{uid:b.uniqueId+"-"+f});b.title=a(e.activeDate,e.formatMonthTitle),b.rows=e.split(c,3)},e.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth())-new Date(b.getFullYear(),b.getMonth())},e.handleKeyDown=function(a){var b=e.activeDate.getMonth();if("left"===a)b-=1;else if("up"===a)b-=3;else if("right"===a)b+=1;else if("down"===a)b+=3;else if("pageup"===a||"pagedown"===a){var c=e.activeDate.getFullYear()+("pageup"===a?-1:1);e.activeDate.setFullYear(c)}else"home"===a?b=0:"end"===a&&(b=11);e.activeDate.setMonth(b)},e.refreshView()}}}]).directive("yearpicker",["dateFilter",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/year.html",require:"^datepicker",link:function(a,b,c,d){function e(a){return parseInt((a-1)/f,10)*f+1}var f=d.yearRange;d.step={years:f},d.element=b,d._refreshView=function(){for(var b=new Array(f),c=0,g=e(d.activeDate.getFullYear());f>c;c++)b[c]=angular.extend(d.createDateObject(new Date(g+c,0,1),d.formatYear),{uid:a.uniqueId+"-"+c});a.title=[b[0].label,b[f-1].label].join(" - "),a.rows=d.split(b,5)},d.compare=function(a,b){return a.getFullYear()-b.getFullYear()},d.handleKeyDown=function(a){var b=d.activeDate.getFullYear();"left"===a?b-=1:"up"===a?b-=5:"right"===a?b+=1:"down"===a?b+=5:"pageup"===a||"pagedown"===a?b+=("pageup"===a?-1:1)*d.step.years:"home"===a?b=e(d.activeDate.getFullYear()):"end"===a&&(b=e(d.activeDate.getFullYear())+f-1),d.activeDate.setFullYear(b)},d.refreshView()}}}]).constant("datepickerPopupConfig",{datepickerPopup:"yyyy-MM-dd",currentText:"Today",clearText:"Clear",closeText:"Done",closeOnDateSelection:!0,appendToBody:!1,showButtonBar:!0}).directive("datepickerPopup",["$compile","$parse","$document","$position","dateFilter","dateParser","datepickerPopupConfig",function(a,b,c,d,e,f,g){return{restrict:"EA",require:"ngModel",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&"},link:function(h,i,j,k){function l(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}function m(a){if(a){if(angular.isDate(a)&&!isNaN(a))return k.$setValidity("date",!0),a;if(angular.isString(a)){var b=f.parse(a,n)||new Date(a);return isNaN(b)?void k.$setValidity("date",!1):(k.$setValidity("date",!0),b)}return void k.$setValidity("date",!1)}return k.$setValidity("date",!0),null}var n,o=angular.isDefined(j.closeOnDateSelection)?h.$parent.$eval(j.closeOnDateSelection):g.closeOnDateSelection,p=angular.isDefined(j.datepickerAppendToBody)?h.$parent.$eval(j.datepickerAppendToBody):g.appendToBody;h.showButtonBar=angular.isDefined(j.showButtonBar)?h.$parent.$eval(j.showButtonBar):g.showButtonBar,h.getText=function(a){return h[a+"Text"]||g[a+"Text"]},j.$observe("datepickerPopup",function(a){n=a||g.datepickerPopup,k.$render()});var q=angular.element("
      ");q.attr({"ng-model":"date","ng-change":"dateSelection()"});var r=angular.element(q.children()[0]);j.datepickerOptions&&angular.forEach(h.$parent.$eval(j.datepickerOptions),function(a,b){r.attr(l(b),a)}),angular.forEach(["minDate","maxDate"],function(a){j[a]&&(h.$parent.$watch(b(j[a]),function(b){h[a]=b}),r.attr(l(a),a))}),j.dateDisabled&&r.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),k.$parsers.unshift(m),h.dateSelection=function(a){angular.isDefined(a)&&(h.date=a),k.$setViewValue(h.date),k.$render(),o&&(h.isOpen=!1,i[0].focus())},i.bind("input change keyup",function(){h.$apply(function(){h.date=k.$modelValue})}),k.$render=function(){var a=k.$viewValue?e(k.$viewValue,n):"";i.val(a),h.date=m(k.$modelValue)};var s=function(a){h.isOpen&&a.target!==i[0]&&h.$apply(function(){h.isOpen=!1})},t=function(a){h.keydown(a)};i.bind("keydown",t),h.keydown=function(a){27===a.which?(a.preventDefault(),a.stopPropagation(),h.close()):40!==a.which||h.isOpen||(h.isOpen=!0)},h.$watch("isOpen",function(a){a?(h.$broadcast("datepicker.focus"),h.position=p?d.offset(i):d.position(i),h.position.top=h.position.top+i.prop("offsetHeight"),c.bind("click",s)):c.unbind("click",s)}),h.select=function(a){if("today"===a){var b=new Date;angular.isDate(k.$modelValue)?(a=new Date(k.$modelValue),a.setFullYear(b.getFullYear(),b.getMonth(),b.getDate())):a=new Date(b.setHours(0,0,0,0))}h.dateSelection(a)},h.close=function(){h.isOpen=!1,i[0].focus()};var u=a(q)(h);p?c.find("body").append(u):i.after(u),h.$on("$destroy",function(){u.remove(),i.unbind("keydown",t),c.unbind("click",s)})}}}]).directive("datepickerPopupWrap",function(){return{restrict:"EA",replace:!0,transclude:!0,templateUrl:"template/datepicker/popup.html",link:function(a,b){b.bind("click",function(a){a.preventDefault(),a.stopPropagation()})}}}),angular.module("ui.bootstrap.dropdown",[]).constant("dropdownConfig",{openClass:"open"}).service("dropdownService",["$document",function(a){var b=null;this.open=function(e){b||(a.bind("click",c),a.bind("keydown",d)),b&&b!==e&&(b.isOpen=!1),b=e},this.close=function(e){b===e&&(b=null,a.unbind("click",c),a.unbind("keydown",d))};var c=function(a){a&&a.isDefaultPrevented()||b.$apply(function(){b.isOpen=!1})},d=function(a){27===a.which&&(b.focusToggleElement(),c())}}]).controller("DropdownController",["$scope","$attrs","$parse","dropdownConfig","dropdownService","$animate",function(a,b,c,d,e,f){var g,h=this,i=a.$new(),j=d.openClass,k=angular.noop,l=b.onToggle?c(b.onToggle):angular.noop;this.init=function(d){h.$element=d,b.isOpen&&(g=c(b.isOpen),k=g.assign,a.$watch(g,function(a){i.isOpen=!!a}))},this.toggle=function(a){return i.isOpen=arguments.length?!!a:!i.isOpen},this.isOpen=function(){return i.isOpen},i.focusToggleElement=function(){h.toggleElement&&h.toggleElement[0].focus()},i.$watch("isOpen",function(b,c){f[b?"addClass":"removeClass"](h.$element,j),b?(i.focusToggleElement(),e.open(i)):e.close(i),k(a,b),angular.isDefined(b)&&b!==c&&l(a,{open:!!b})}),a.$on("$locationChangeSuccess",function(){i.isOpen=!1}),a.$on("$destroy",function(){i.$destroy()})}]).directive("dropdown",function(){return{restrict:"CA",controller:"DropdownController",link:function(a,b,c,d){d.init(b)}}}).directive("dropdownToggle",function(){return{restrict:"CA",require:"?^dropdown",link:function(a,b,c,d){if(d){d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.modal",["ui.bootstrap.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0),i()})}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g,0)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&(a.preventDefault(),e.$apply(function(){o.dismiss(b.key,"escape key press")})))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();h>=0&&!k&&(l=e.$new(!0),l.index=h,k=d("
      ")(l),f.append(k));var i=angular.element("
      ");i.attr({"template-url":b.windowTemplateUrl,"window-class":b.windowClass,size:b.size,index:n.length()-1,animate:"animate"}).html(b.content);var j=d(i)(b.scope);n.top().value.modalDomEl=j,f.append(j),f.addClass(m)},o.close=function(a,b){var c=n.get(a).value;c&&(c.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a).value;c&&(c.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass,windowTemplateUrl:b.windowTemplateUrl,size:b.size})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("ui.bootstrap.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse",function(a,b,c){var d=this,e={$setViewValue:angular.noop},f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(f,g){e=f,this.config=g,e.$render=function(){d.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){d.itemsPerPage=parseInt(b,10),a.totalPages=d.calculateTotalPages()}):this.itemsPerPage=g.itemsPerPage},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(e.$viewValue,10)||1},a.selectPage=function(b){a.page!==b&&b>0&&b<=a.totalPages&&(e.$setViewValue(b),e.$render())},a.getText=function(b){return a[b+"Text"]||d.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages},a.$watch("totalItems",function(){a.totalPages=d.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),a.page>b?a.selectPage(b):e.$render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@"},require:["pagination","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c){return{number:a,text:b,active:c}}function h(a,b){var c=[],d=1,e=b,f=angular.isDefined(k)&&b>k;f&&(l?(d=Math.max(a-Math.floor(k/2),1),e=d+k-1,e>b&&(e=b,d=e-k+1)):(d=(Math.ceil(a/k)-1)*k+1,e=Math.min(d+k-1,b)));for(var h=d;e>=h;h++){var i=g(h,h,h===a);c.push(i)}if(f&&!l){if(d>1){var j=g(d-1,"...",!1);c.unshift(j)}if(b>e){var m=g(e+1,"...",!1);c.push(m)}}return c}var i=f[0],j=f[1];if(j){var k=angular.isDefined(e.maxSize)?c.$parent.$eval(e.maxSize):b.maxSize,l=angular.isDefined(e.rotate)?c.$parent.$eval(e.rotate):b.rotate;c.boundaryLinks=angular.isDefined(e.boundaryLinks)?c.$parent.$eval(e.boundaryLinks):b.boundaryLinks,c.directionLinks=angular.isDefined(e.directionLinks)?c.$parent.$eval(e.directionLinks):b.directionLinks,i.init(j,b),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){k=parseInt(a,10),i.render()});var m=i.render;i.render=function(){m(),c.page>0&&c.page<=c.totalPages&&(c.pages=h(c.page,c.totalPages))}}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@"},require:["pager","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){var f=e[0],g=e[1];g&&(b.align=angular.isDefined(d.align)?b.$parent.$eval(d.align):a.align,f.init(g,a))}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
      ';return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!y||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?v||(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a() +})):p()())}function m(){b.$apply(function(){q()})}function p(){return v=null,u&&(g.cancel(u),u=null),b.tt_content?(r(),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),z(),b.tt_isOpen=!0,b.$digest(),z):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),v=null,b.tt_animation?u||(u=g(s,500)):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){u=null,t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=angular.isDefined(d[l+"Enable"]),z=function(){var a=j.positionElements(c,t,b.tt_placement,w);a.top+="px",a.left+="px",t.css(a)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)?a:o.placement}),d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var A=function(){c.unbind(x.show,k),c.unbind(x.hide,m)};d.$observe(l+"Trigger",function(a){A(),x=n(a),x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m))});var B=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(B)?!!B:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),A(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig",function(a,b,c){var d=this,e=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,this.addBar=function(b,c){e||c.css({transition:"none"}),this.bars.push(b),b.$watch("value",function(c){b.percent=+(100*c/a.max).toFixed(2)}),b.$on("$destroy",function(){c=null,d.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},templateUrl:"template/progressbar/progress.html"}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("ui.bootstrap.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","ratingConfig",function(a,b,c){var d={$setViewValue:angular.noop};this.init=function(e){d=e,d.$render=this.render,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff;var f=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(f)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff},a[b]);return a},a.rate=function(b){!a.readonly&&b>=0&&b<=a.range.length&&(d.$setViewValue(b),d.$render())},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue}}]).directive("rating",function(){return{restrict:"EA",require:["rating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}),angular.module("ui.bootstrap.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){angular.forEach(c,function(b){b.active&&b!==a&&(b.active=!1,b.onDeselect())}),a.active=!0,a.onSelect()},b.addTab=function(a){c.push(a),1===c.length?a.active=!0:a.active&&b.select(a)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){b.$watch("active",function(a){a&&f.select(b)}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("ui.bootstrap.timepicker",[]).constant("timepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:null,readonlyInput:!1,mousewheel:!0}).controller("TimepickerController",["$scope","$attrs","$parse","$log","$locale","timepickerConfig",function(a,b,c,d,e,f){function g(){var b=parseInt(a.hours,10),c=a.showMeridian?b>0&&13>b:b>=0&&24>b;return c?(a.showMeridian&&(12===b&&(b=0),a.meridian===p[1]&&(b+=12)),b):void 0}function h(){var b=parseInt(a.minutes,10);return b>=0&&60>b?b:void 0}function i(a){return angular.isDefined(a)&&a.toString().length<2?"0"+a:a}function j(a){k(),o.$setViewValue(new Date(n)),l(a)}function k(){o.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1}function l(b){var c=n.getHours(),d=n.getMinutes();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:i(c),a.minutes="m"===b?d:i(d),a.meridian=n.getHours()<12?p[0]:p[1]}function m(a){var b=new Date(n.getTime()+6e4*a);n.setHours(b.getHours(),b.getMinutes()),j()}var n=new Date,o={$setViewValue:angular.noop},p=angular.isDefined(b.meridians)?a.$parent.$eval(b.meridians):f.meridians||e.DATETIME_FORMATS.AMPMS;this.init=function(c,d){o=c,o.$render=this.render;var e=d.eq(0),g=d.eq(1),h=angular.isDefined(b.mousewheel)?a.$parent.$eval(b.mousewheel):f.mousewheel;h&&this.setupMousewheelEvents(e,g),a.readonlyInput=angular.isDefined(b.readonlyInput)?a.$parent.$eval(b.readonlyInput):f.readonlyInput,this.setupInputEvents(e,g)};var q=f.hourStep;b.hourStep&&a.$parent.$watch(c(b.hourStep),function(a){q=parseInt(a,10)});var r=f.minuteStep;b.minuteStep&&a.$parent.$watch(c(b.minuteStep),function(a){r=parseInt(a,10)}),a.showMeridian=f.showMeridian,b.showMeridian&&a.$parent.$watch(c(b.showMeridian),function(b){if(a.showMeridian=!!b,o.$error.time){var c=g(),d=h();angular.isDefined(c)&&angular.isDefined(d)&&(n.setHours(c),j())}else l()}),this.setupMousewheelEvents=function(b,c){var d=function(a){a.originalEvent&&(a=a.originalEvent);var b=a.wheelDelta?a.wheelDelta:-a.deltaY;return a.detail||b>0};b.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()})},this.setupInputEvents=function(b,c){if(a.readonlyInput)return a.updateHours=angular.noop,void(a.updateMinutes=angular.noop);var d=function(b,c){o.$setViewValue(null),o.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c)};a.updateHours=function(){var a=g();angular.isDefined(a)?(n.setHours(a),j("h")):d(!0)},b.bind("blur",function(){!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=i(a.hours)})}),a.updateMinutes=function(){var a=h();angular.isDefined(a)?(n.setMinutes(a),j("m")):d(void 0,!0)},c.bind("blur",function(){!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=i(a.minutes)})})},this.render=function(){var a=o.$modelValue?new Date(o.$modelValue):null;isNaN(a)?(o.$setValidity("time",!1),d.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(a&&(n=a),k(),l())},a.incrementHours=function(){m(60*q)},a.decrementHours=function(){m(60*-q)},a.incrementMinutes=function(){m(r)},a.decrementMinutes=function(){m(-r)},a.toggleMeridian=function(){m(720*(n.getHours()<12?1:-1))}}]).directive("timepicker",function(){return{restrict:"EA",require:["timepicker","?^ngModel"],controller:"TimepickerController",replace:!0,scope:{},templateUrl:"template/timepicker/timepicker.html",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?i.$eval(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=i.$new();i.$on("$destroy",function(){w.$destroy()});var x="typeahead-"+w.$id+"-"+Math.floor(1e4*Math.random());j.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":x});var y=angular.element("
      ");y.attr({id:x,matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&y.attr("template-url",k.typeaheadTemplateUrl);var z=function(){w.matches=[],w.activeIdx=-1,j.attr("aria-expanded",!1)},A=function(a){return x+"-option-"+a};w.$watch("activeIdx",function(a){0>a?j.removeAttr("aria-activedescendant"):j.attr("aria-activedescendant",A(a))});var B=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){var d=a===l.$viewValue;if(d&&m)if(c.length>0){w.activeIdx=0,w.matches.length=0;for(var e=0;e=n?o>0?(C&&d.cancel(C),C=d(function(){B(a)},o)):B(a):(q(i,!1),z()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),w.select=function(a){var b,c,e={};e[v.itemName]=c=w.matches[a].model,b=v.modelMapper(i,e),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,e)}),z(),d(function(){j[0].focus()},0,!1)},j.bind("keydown",function(a){0!==w.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(w.activeIdx=(w.activeIdx+1)%w.matches.length,w.$digest()):38===a.which?(w.activeIdx=(w.activeIdx?w.activeIdx:w.matches.length)-1,w.$digest()):13===a.which||9===a.which?w.$apply(function(){w.select(w.activeIdx)}):27===a.which&&(a.stopPropagation(),z(),w.$digest()))}),j.bind("blur",function(){m=!1});var D=function(a){j[0]!==a.target&&(z(),w.$digest())};e.bind("click",D),i.$on("$destroy",function(){e.unbind("click",D)});var E=a(y)(w);t?e.find("body").append(E):j.after(E)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?(""+b).replace(new RegExp(a(c),"gi"),"$&"):b}}); \ No newline at end of file diff --git a/src/main/webapp/subscriber/ui/js/angular-ui/unique.js b/src/main/webapp/subscriber/ui/js/angular-ui/unique.js new file mode 100644 index 0000000..653916c --- /dev/null +++ b/src/main/webapp/subscriber/ui/js/angular-ui/unique.js @@ -0,0 +1,44 @@ +'use strict'; + +/** + * Filters out all duplicate items from an array by checking the specified key + * @param [key] {string} the name of the attribute of each object to compare for uniqueness + if the key is empty, the entire object will be compared + if the key === false then no filtering will be performed + * @return {array} + */ +angular.module('ui.unique',[]).filter('unique', ['$parse', function ($parse) { + + return function (items, filterOn) { + + if (filterOn === false) { + return items; + } + + if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) { + var newItems = [], + get = angular.isString(filterOn) ? $parse(filterOn) : function (item) { return item; }; + + var extractValueToCompare = function (item) { + return angular.isObject(item) ? get(item) : item; + }; + + angular.forEach(items, function (item) { + var isDuplicate = false; + + for (var i = 0; i < newItems.length; i++) { + if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) { + isDuplicate = true; + break; + } + } + if (!isDuplicate) { + newItems.push(item); + } + + }); + items = newItems; + } + return items; + }; +}]); diff --git a/src/main/webapp/subscriber/ui/js/angular/angular-animate.js b/src/main/webapp/subscriber/ui/js/angular/angular-animate.js new file mode 100644 index 0000000..59ca0d6 --- /dev/null +++ b/src/main/webapp/subscriber/ui/js/angular/angular-animate.js @@ -0,0 +1,1483 @@ +/** + * @license AngularJS v1.2.9 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +/* jshint maxlen: false */ + +/** + * @ngdoc overview + * @name ngAnimate + * @description + * + * # ngAnimate + * + * The `ngAnimate` module provides support for JavaScript, CSS3 transition and CSS3 keyframe animation hooks within existing core and custom directives. + * + * {@installModule animate} + * + *
      + * + * # Usage + * + * To see animations in action, all that is required is to define the appropriate CSS classes + * or to register a JavaScript animation via the myModule.animation() function. The directives that support animation automatically are: + * `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation + * by using the `$animate` service. + * + * Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives: + * + * | Directive | Supported Animations | + * |---------------------------------------------------------- |----------------------------------------------------| + * | {@link ng.directive:ngRepeat#usage_animations ngRepeat} | enter, leave and move | + * | {@link ngRoute.directive:ngView#usage_animations ngView} | enter and leave | + * | {@link ng.directive:ngInclude#usage_animations ngInclude} | enter and leave | + * | {@link ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave | + * | {@link ng.directive:ngIf#usage_animations ngIf} | enter and leave | + * | {@link ng.directive:ngClass#usage_animations ngClass} | add and remove | + * | {@link ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) | + * + * You can find out more information about animations upon visiting each directive page. + * + * Below is an example of how to apply animations to a directive that supports animation hooks: + * + *
      + * 
      + *
      + * 
      + * 
      + * 
      + * + * Keep in mind that if an animation is running, any child elements cannot be animated until the parent element's + * animation has completed. + * + *

      CSS-defined Animations

      + * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes + * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported + * and can be used to play along with this naming structure. + * + * The following code below demonstrates how to perform animations using **CSS transitions** with Angular: + * + *
      + * 
      + *
      + * 
      + *
      + *
      + *
      + * + * The following code below demonstrates how to perform animations using **CSS animations** with Angular: + * + *
      + * 
      + *
      + * 
      + *
      + *
      + *
      + * + * Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing. + * + * Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add + * the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically + * detect the CSS code to determine when the animation ends. Once the animation is over then both CSS classes will be + * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end + * immediately resulting in a DOM element that is at its final state. This final state is when the DOM element + * has no CSS transition/animation classes applied to it. + * + *

      CSS Staggering Animations

      + * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a + * curtain-like effect. The ngAnimate module, as of 1.2.0, supports staggering animations and the stagger effect can be + * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for + * the animation. The style property expected within the stagger class can either be a **transition-delay** or an + * **animation-delay** property (or both if your animation contains both transitions and keyframe animations). + * + *
      + * .my-animation.ng-enter {
      + *   /* standard transition code */
      + *   -webkit-transition: 1s linear all;
      + *   transition: 1s linear all;
      + *   opacity:0;
      + * }
      + * .my-animation.ng-enter-stagger {
      + *   /* this will have a 100ms delay between each successive leave animation */
      + *   -webkit-transition-delay: 0.1s;
      + *   transition-delay: 0.1s;
      + *
      + *   /* in case the stagger doesn't work then these two values
      + *    must be set to 0 to avoid an accidental CSS inheritance */
      + *   -webkit-transition-duration: 0s;
      + *   transition-duration: 0s;
      + * }
      + * .my-animation.ng-enter.ng-enter-active {
      + *   /* standard transition styles */
      + *   opacity:1;
      + * }
      + * 
      + * + * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations + * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this + * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation + * will also be reset if more than 10ms has passed after the last animation has been fired. + * + * The following code will issue the **ng-leave-stagger** event on the element provided: + * + *
      + * var kids = parent.children();
      + *
      + * $animate.leave(kids[0]); //stagger index=0
      + * $animate.leave(kids[1]); //stagger index=1
      + * $animate.leave(kids[2]); //stagger index=2
      + * $animate.leave(kids[3]); //stagger index=3
      + * $animate.leave(kids[4]); //stagger index=4
      + *
      + * $timeout(function() {
      + *   //stagger has reset itself
      + *   $animate.leave(kids[5]); //stagger index=0
      + *   $animate.leave(kids[6]); //stagger index=1
      + * }, 100, false);
      + * 
      + * + * Stagger animations are currently only supported within CSS-defined animations. + * + *

      JavaScript-defined Animations

      + * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not + * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module. + * + *
      + * //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application.
      + * var ngModule = angular.module('YourApp', ['ngAnimate']);
      + * ngModule.animation('.my-crazy-animation', function() {
      + *   return {
      + *     enter: function(element, done) {
      + *       //run the animation here and call done when the animation is complete
      + *       return function(cancelled) {
      + *         //this (optional) function will be called when the animation
      + *         //completes or when the animation is cancelled (the cancelled
      + *         //flag will be set to true if cancelled).
      + *       };
      + *     },
      + *     leave: function(element, done) { },
      + *     move: function(element, done) { },
      + *
      + *     //animation that can be triggered before the class is added
      + *     beforeAddClass: function(element, className, done) { },
      + *
      + *     //animation that can be triggered after the class is added
      + *     addClass: function(element, className, done) { },
      + *
      + *     //animation that can be triggered before the class is removed
      + *     beforeRemoveClass: function(element, className, done) { },
      + *
      + *     //animation that can be triggered after the class is removed
      + *     removeClass: function(element, className, done) { }
      + *   };
      + * });
      + * 
      + * + * JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run + * a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits + * the element's CSS class attribute value and then run the matching animation event function (if found). + * In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function will + * be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported). + * + * Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned. + * As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run, + * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation + * or transition code that is defined via a stylesheet). + * + */ + +angular.module('ngAnimate', ['ng']) + + /** + * @ngdoc object + * @name ngAnimate.$animateProvider + * @description + * + * The `$animateProvider` allows developers to register JavaScript animation event handlers directly inside of a module. + * When an animation is triggered, the $animate service will query the $animate service to find any animations that match + * the provided name value. + * + * Requires the {@link ngAnimate `ngAnimate`} module to be installed. + * + * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application. + * + */ + .factory('$$animateReflow', ['$window', '$timeout', function($window, $timeout) { + var requestAnimationFrame = $window.requestAnimationFrame || + $window.webkitRequestAnimationFrame || + function(fn) { + return $timeout(fn, 10, false); + }; + + var cancelAnimationFrame = $window.cancelAnimationFrame || + $window.webkitCancelAnimationFrame || + function(timer) { + return $timeout.cancel(timer); + }; + return function(fn) { + var id = requestAnimationFrame(fn); + return function() { + cancelAnimationFrame(id); + }; + }; + }]) + + .config(['$provide', '$animateProvider', function($provide, $animateProvider) { + var noop = angular.noop; + var forEach = angular.forEach; + var selectors = $animateProvider.$$selectors; + + var ELEMENT_NODE = 1; + var NG_ANIMATE_STATE = '$$ngAnimateState'; + var NG_ANIMATE_CLASS_NAME = 'ng-animate'; + var rootAnimateState = {running: true}; + + function extractElementNode(element) { + for(var i = 0; i < element.length; i++) { + var elm = element[i]; + if(elm.nodeType == ELEMENT_NODE) { + return elm; + } + } + } + + function isMatchingElement(elm1, elm2) { + return extractElementNode(elm1) == extractElementNode(elm2); + } + + $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document', + function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope, $document) { + + $rootElement.data(NG_ANIMATE_STATE, rootAnimateState); + + // disable animations during bootstrap, but once we bootstrapped, wait again + // for another digest until enabling animations. The reason why we digest twice + // is because all structural animations (enter, leave and move) all perform a + // post digest operation before animating. If we only wait for a single digest + // to pass then the structural animation would render its animation on page load. + // (which is what we're trying to avoid when the application first boots up.) + $rootScope.$$postDigest(function() { + $rootScope.$$postDigest(function() { + rootAnimateState.running = false; + }); + }); + + var classNameFilter = $animateProvider.classNameFilter(); + var isAnimatableClassName = !classNameFilter + ? function() { return true; } + : function(className) { + return classNameFilter.test(className); + }; + + function async(fn) { + return $timeout(fn, 0, false); + } + + function lookup(name) { + if (name) { + var matches = [], + flagMap = {}, + classes = name.substr(1).split('.'); + + //the empty string value is the default animation + //operation which performs CSS transition and keyframe + //animations sniffing. This is always included for each + //element animation procedure if the browser supports + //transitions and/or keyframe animations + if ($sniffer.transitions || $sniffer.animations) { + classes.push(''); + } + + for(var i=0; i < classes.length; i++) { + var klass = classes[i], + selectorFactoryName = selectors[klass]; + if(selectorFactoryName && !flagMap[klass]) { + matches.push($injector.get(selectorFactoryName)); + flagMap[klass] = true; + } + } + return matches; + } + } + + /** + * @ngdoc object + * @name ngAnimate.$animate + * @function + * + * @description + * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) as well as during addClass and removeClass operations. + * When any of these operations are run, the $animate service + * will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object) + * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run. + * + * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives + * will work out of the box without any extra configuration. + * + * Requires the {@link ngAnimate `ngAnimate`} module to be installed. + * + * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application. + * + */ + return { + /** + * @ngdoc function + * @name ngAnimate.$animate#enter + * @methodOf ngAnimate.$animate + * @function + * + * @description + * Appends the element to the parentElement element that resides in the document and then runs the enter animation. Once + * the animation is started, the following CSS classes will be present on the element for the duration of the animation: + * + * Below is a breakdown of each step that occurs during enter animation: + * + * | Animation Step | What the element class attribute looks like | + * |----------------------------------------------------------------------------------------------|---------------------------------------------| + * | 1. $animate.enter(...) is called | class="my-animation" | + * | 2. element is inserted into the parentElement element or beside the afterElement element | class="my-animation" | + * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" | + * | 4. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" | + * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" | + * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-enter" | + * | 7. the .ng-enter-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" | + * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" | + * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | + * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" | + * + * @param {jQuery/jqLite element} element the element that will be the focus of the enter animation + * @param {jQuery/jqLite element} parentElement the parent element of the element that will be the focus of the enter animation + * @param {jQuery/jqLite element} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation + * @param {function()=} doneCallback the callback function that will be called once the animation is complete + */ + enter : function(element, parentElement, afterElement, doneCallback) { + this.enabled(false, element); + $delegate.enter(element, parentElement, afterElement); + $rootScope.$$postDigest(function() { + performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback); + }); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#leave + * @methodOf ngAnimate.$animate + * @function + * + * @description + * Runs the leave animation operation and, upon completion, removes the element from the DOM. Once + * the animation is started, the following CSS classes will be added for the duration of the animation: + * + * Below is a breakdown of each step that occurs during leave animation: + * + * | Animation Step | What the element class attribute looks like | + * |----------------------------------------------------------------------------------------------|---------------------------------------------| + * | 1. $animate.leave(...) is called | class="my-animation" | + * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" | + * | 3. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" | + * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" | + * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-leave" | + * | 6. the .ng-leave-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" | + * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" | + * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | + * | 9. The element is removed from the DOM | ... | + * | 10. The doneCallback() callback is fired (if provided) | ... | + * + * @param {jQuery/jqLite element} element the element that will be the focus of the leave animation + * @param {function()=} doneCallback the callback function that will be called once the animation is complete + */ + leave : function(element, doneCallback) { + cancelChildAnimations(element); + this.enabled(false, element); + $rootScope.$$postDigest(function() { + performAnimation('leave', 'ng-leave', element, null, null, function() { + $delegate.leave(element); + }, doneCallback); + }); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#move + * @methodOf ngAnimate.$animate + * @function + * + * @description + * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parentElement container or + * add the element directly after the afterElement element if present. Then the move animation will be run. Once + * the animation is started, the following CSS classes will be added for the duration of the animation: + * + * Below is a breakdown of each step that occurs during move animation: + * + * | Animation Step | What the element class attribute looks like | + * |----------------------------------------------------------------------------------------------|---------------------------------------------| + * | 1. $animate.move(...) is called | class="my-animation" | + * | 2. element is moved into the parentElement element or beside the afterElement element | class="my-animation" | + * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" | + * | 4. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" | + * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" | + * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-move" | + * | 7. the .ng-move-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" | + * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" | + * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | + * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" | + * + * @param {jQuery/jqLite element} element the element that will be the focus of the move animation + * @param {jQuery/jqLite element} parentElement the parentElement element of the element that will be the focus of the move animation + * @param {jQuery/jqLite element} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation + * @param {function()=} doneCallback the callback function that will be called once the animation is complete + */ + move : function(element, parentElement, afterElement, doneCallback) { + cancelChildAnimations(element); + this.enabled(false, element); + $delegate.move(element, parentElement, afterElement); + $rootScope.$$postDigest(function() { + performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback); + }); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#addClass + * @methodOf ngAnimate.$animate + * + * @description + * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class. + * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide + * the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions + * or keyframes are defined on the -add or base CSS class). + * + * Below is a breakdown of each step that occurs during addClass animation: + * + * | Animation Step | What the element class attribute looks like | + * |------------------------------------------------------------------------------------------------|---------------------------------------------| + * | 1. $animate.addClass(element, 'super') is called | class="my-animation" | + * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" | + * | 3. the .super-add class are added to the element | class="my-animation ng-animate super-add" | + * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-add" | + * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate super-add" | + * | 6. the .super, .super-add-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super super-add super-add-active" | + * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation super-add super-add-active" | + * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" | + * | 9. The super class is kept on the element | class="my-animation super" | + * | 10. The doneCallback() callback is fired (if provided) | class="my-animation super" | + * + * @param {jQuery/jqLite element} element the element that will be animated + * @param {string} className the CSS class that will be added to the element and then animated + * @param {function()=} doneCallback the callback function that will be called once the animation is complete + */ + addClass : function(element, className, doneCallback) { + performAnimation('addClass', className, element, null, null, function() { + $delegate.addClass(element, className); + }, doneCallback); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#removeClass + * @methodOf ngAnimate.$animate + * + * @description + * Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value + * from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in + * order to provide the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if + * no CSS transitions or keyframes are defined on the -remove or base CSS classes). + * + * Below is a breakdown of each step that occurs during removeClass animation: + * + * | Animation Step | What the element class attribute looks like | + * |-----------------------------------------------------------------------------------------------|---------------------------------------------| + * | 1. $animate.removeClass(element, 'super') is called | class="my-animation super" | + * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation super ng-animate" | + * | 3. the .super-remove class are added to the element | class="my-animation super ng-animate super-remove"| + * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation super ng-animate super-remove" | + * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation super ng-animate super-remove" | + * | 6. the .super-remove-active and .ng-animate-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" | + * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" | + * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | + * | 9. The doneCallback() callback is fired (if provided) | class="my-animation" | + * + * + * @param {jQuery/jqLite element} element the element that will be animated + * @param {string} className the CSS class that will be animated and then removed from the element + * @param {function()=} doneCallback the callback function that will be called once the animation is complete + */ + removeClass : function(element, className, doneCallback) { + performAnimation('removeClass', className, element, null, null, function() { + $delegate.removeClass(element, className); + }, doneCallback); + }, + + /** + * @ngdoc function + * @name ngAnimate.$animate#enabled + * @methodOf ngAnimate.$animate + * @function + * + * @param {boolean=} value If provided then set the animation on or off. + * @param {jQuery/jqLite element=} element If provided then the element will be used to represent the enable/disable operation + * @return {boolean} Current animation state. + * + * @description + * Globally enables/disables animations. + * + */ + enabled : function(value, element) { + switch(arguments.length) { + case 2: + if(value) { + cleanup(element); + } else { + var data = element.data(NG_ANIMATE_STATE) || {}; + data.disabled = true; + element.data(NG_ANIMATE_STATE, data); + } + break; + + case 1: + rootAnimateState.disabled = !value; + break; + + default: + value = !rootAnimateState.disabled; + break; + } + return !!value; + } + }; + + /* + all animations call this shared animation triggering function internally. + The animationEvent variable refers to the JavaScript animation event that will be triggered + and the className value is the name of the animation that will be applied within the + CSS code. Element, parentElement and afterElement are provided DOM elements for the animation + and the onComplete callback will be fired once the animation is fully complete. + */ + function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) { + var currentClassName, classes, node = extractElementNode(element); + if(node) { + currentClassName = node.className; + classes = currentClassName + ' ' + className; + } + + //transcluded directives may sometimes fire an animation using only comment nodes + //best to catch this early on to prevent any animation operations from occurring + if(!node || !isAnimatableClassName(classes)) { + fireDOMOperation(); + fireBeforeCallbackAsync(); + fireAfterCallbackAsync(); + closeAnimation(); + return; + } + + var animationLookup = (' ' + classes).replace(/\s+/g,'.'); + if (!parentElement) { + parentElement = afterElement ? afterElement.parent() : element.parent(); + } + + var matches = lookup(animationLookup); + var isClassBased = animationEvent == 'addClass' || animationEvent == 'removeClass'; + var ngAnimateState = element.data(NG_ANIMATE_STATE) || {}; + + //skip the animation if animations are disabled, a parent is already being animated, + //the element is not currently attached to the document body or then completely close + //the animation if any matching animations are not found at all. + //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found. + if (animationsDisabled(element, parentElement) || matches.length === 0) { + fireDOMOperation(); + fireBeforeCallbackAsync(); + fireAfterCallbackAsync(); + closeAnimation(); + return; + } + + var animations = []; + + //only add animations if the currently running animation is not structural + //or if there is no animation running at all + var allowAnimations = isClassBased ? + !ngAnimateState.disabled && (!ngAnimateState.running || !ngAnimateState.structural) : + true; + + if(allowAnimations) { + forEach(matches, function(animation) { + //add the animation to the queue to if it is allowed to be cancelled + if(!animation.allowCancel || animation.allowCancel(element, animationEvent, className)) { + var beforeFn, afterFn = animation[animationEvent]; + + //Special case for a leave animation since there is no point in performing an + //animation on a element node that has already been removed from the DOM + if(animationEvent == 'leave') { + beforeFn = afterFn; + afterFn = null; //this must be falsy so that the animation is skipped for leave + } else { + beforeFn = animation['before' + animationEvent.charAt(0).toUpperCase() + animationEvent.substr(1)]; + } + animations.push({ + before : beforeFn, + after : afterFn + }); + } + }); + } + + //this would mean that an animation was not allowed so let the existing + //animation do it's thing and close this one early + if(animations.length === 0) { + fireDOMOperation(); + fireBeforeCallbackAsync(); + fireAfterCallbackAsync(); + fireDoneCallbackAsync(); + return; + } + + var ONE_SPACE = ' '; + //this value will be searched for class-based CSS className lookup. Therefore, + //we prefix and suffix the current className value with spaces to avoid substring + //lookups of className tokens + var futureClassName = ONE_SPACE + currentClassName + ONE_SPACE; + if(ngAnimateState.running) { + //if an animation is currently running on the element then lets take the steps + //to cancel that animation and fire any required callbacks + $timeout.cancel(ngAnimateState.closeAnimationTimeout); + cleanup(element); + cancelAnimations(ngAnimateState.animations); + + //in the event that the CSS is class is quickly added and removed back + //then we don't want to wait until after the reflow to add/remove the CSS + //class since both class animations may run into a race condition. + //The code below will check to see if that is occurring and will + //immediately remove the former class before the reflow so that the + //animation can snap back to the original animation smoothly + var isFullyClassBasedAnimation = isClassBased && !ngAnimateState.structural; + var isRevertingClassAnimation = isFullyClassBasedAnimation && + ngAnimateState.className == className && + animationEvent != ngAnimateState.event; + + //if the class is removed during the reflow then it will revert the styles temporarily + //back to the base class CSS styling causing a jump-like effect to occur. This check + //here ensures that the domOperation is only performed after the reflow has commenced + if(ngAnimateState.beforeComplete || isRevertingClassAnimation) { + (ngAnimateState.done || noop)(true); + } else if(isFullyClassBasedAnimation) { + //class-based animations will compare element className values after cancelling the + //previous animation to see if the element properties already contain the final CSS + //class and if so then the animation will be skipped. Since the domOperation will + //be performed only after the reflow is complete then our element's className value + //will be invalid. Therefore the same string manipulation that would occur within the + //DOM operation will be performed below so that the class comparison is valid... + futureClassName = ngAnimateState.event == 'removeClass' ? + futureClassName.replace(ONE_SPACE + ngAnimateState.className + ONE_SPACE, ONE_SPACE) : + futureClassName + ngAnimateState.className + ONE_SPACE; + } + } + + //There is no point in perform a class-based animation if the element already contains + //(on addClass) or doesn't contain (on removeClass) the className being animated. + //The reason why this is being called after the previous animations are cancelled + //is so that the CSS classes present on the element can be properly examined. + var classNameToken = ONE_SPACE + className + ONE_SPACE; + if((animationEvent == 'addClass' && futureClassName.indexOf(classNameToken) >= 0) || + (animationEvent == 'removeClass' && futureClassName.indexOf(classNameToken) == -1)) { + fireDOMOperation(); + fireBeforeCallbackAsync(); + fireAfterCallbackAsync(); + fireDoneCallbackAsync(); + return; + } + + //the ng-animate class does nothing, but it's here to allow for + //parent animations to find and cancel child animations when needed + element.addClass(NG_ANIMATE_CLASS_NAME); + + element.data(NG_ANIMATE_STATE, { + running:true, + event:animationEvent, + className:className, + structural:!isClassBased, + animations:animations, + done:onBeforeAnimationsComplete + }); + + //first we run the before animations and when all of those are complete + //then we perform the DOM operation and run the next set of animations + invokeRegisteredAnimationFns(animations, 'before', onBeforeAnimationsComplete); + + function onBeforeAnimationsComplete(cancelled) { + fireDOMOperation(); + if(cancelled === true) { + closeAnimation(); + return; + } + + //set the done function to the final done function + //so that the DOM event won't be executed twice by accident + //if the after animation is cancelled as well + var data = element.data(NG_ANIMATE_STATE); + if(data) { + data.done = closeAnimation; + element.data(NG_ANIMATE_STATE, data); + } + invokeRegisteredAnimationFns(animations, 'after', closeAnimation); + } + + function invokeRegisteredAnimationFns(animations, phase, allAnimationFnsComplete) { + phase == 'after' ? + fireAfterCallbackAsync() : + fireBeforeCallbackAsync(); + + var endFnName = phase + 'End'; + forEach(animations, function(animation, index) { + var animationPhaseCompleted = function() { + progress(index, phase); + }; + + //there are no before functions for enter + move since the DOM + //operations happen before the performAnimation method fires + if(phase == 'before' && (animationEvent == 'enter' || animationEvent == 'move')) { + animationPhaseCompleted(); + return; + } + + if(animation[phase]) { + animation[endFnName] = isClassBased ? + animation[phase](element, className, animationPhaseCompleted) : + animation[phase](element, animationPhaseCompleted); + } else { + animationPhaseCompleted(); + } + }); + + function progress(index, phase) { + var phaseCompletionFlag = phase + 'Complete'; + var currentAnimation = animations[index]; + currentAnimation[phaseCompletionFlag] = true; + (currentAnimation[endFnName] || noop)(); + + for(var i=0;i 0) { + aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1; + } + + animationDuration = Math.max(aDuration, animationDuration); + } + }); + data = { + total : 0, + transitionPropertyStyle: transitionPropertyStyle, + transitionDurationStyle: transitionDurationStyle, + transitionDelayStyle: transitionDelayStyle, + transitionDelay: transitionDelay, + transitionDuration: transitionDuration, + animationDelayStyle: animationDelayStyle, + animationDelay: animationDelay, + animationDuration: animationDuration + }; + if(cacheKey) { + lookupCache[cacheKey] = data; + } + } + return data; + } + + function parseMaxTime(str) { + var maxValue = 0; + var values = angular.isString(str) ? + str.split(/\s*,\s*/) : + []; + forEach(values, function(value) { + maxValue = Math.max(parseFloat(value) || 0, maxValue); + }); + return maxValue; + } + + function getCacheKey(element) { + var parentElement = element.parent(); + var parentID = parentElement.data(NG_ANIMATE_PARENT_KEY); + if(!parentID) { + parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter); + parentID = parentCounter; + } + return parentID + '-' + extractElementNode(element).className; + } + + function animateSetup(element, className, calculationDecorator) { + var cacheKey = getCacheKey(element); + var eventCacheKey = cacheKey + ' ' + className; + var stagger = {}; + var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0; + + if(itemIndex > 0) { + var staggerClassName = className + '-stagger'; + var staggerCacheKey = cacheKey + ' ' + staggerClassName; + var applyClasses = !lookupCache[staggerCacheKey]; + + applyClasses && element.addClass(staggerClassName); + + stagger = getElementAnimationDetails(element, staggerCacheKey); + + applyClasses && element.removeClass(staggerClassName); + } + + /* the animation itself may need to add/remove special CSS classes + * before calculating the anmation styles */ + calculationDecorator = calculationDecorator || + function(fn) { return fn(); }; + + element.addClass(className); + + var timings = calculationDecorator(function() { + return getElementAnimationDetails(element, eventCacheKey); + }); + + /* there is no point in performing a reflow if the animation + timeout is empty (this would cause a flicker bug normally + in the page. There is also no point in performing an animation + that only has a delay and no duration */ + var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay); + var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration); + if(maxDuration === 0) { + element.removeClass(className); + return false; + } + + //temporarily disable the transition so that the enter styles + //don't animate twice (this is here to avoid a bug in Chrome/FF). + var activeClassName = ''; + timings.transitionDuration > 0 ? + blockTransitions(element) : + blockKeyframeAnimations(element); + + forEach(className.split(' '), function(klass, i) { + activeClassName += (i > 0 ? ' ' : '') + klass + '-active'; + }); + + element.data(NG_ANIMATE_CSS_DATA_KEY, { + className : className, + activeClassName : activeClassName, + maxDuration : maxDuration, + maxDelay : maxDelay, + classes : className + ' ' + activeClassName, + timings : timings, + stagger : stagger, + itemIndex : itemIndex + }); + + return true; + } + + function blockTransitions(element) { + extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none'; + } + + function blockKeyframeAnimations(element) { + extractElementNode(element).style[ANIMATION_PROP] = 'none 0s'; + } + + function unblockTransitions(element) { + var prop = TRANSITION_PROP + PROPERTY_KEY; + var node = extractElementNode(element); + if(node.style[prop] && node.style[prop].length > 0) { + node.style[prop] = ''; + } + } + + function unblockKeyframeAnimations(element) { + var prop = ANIMATION_PROP; + var node = extractElementNode(element); + if(node.style[prop] && node.style[prop].length > 0) { + node.style[prop] = ''; + } + } + + function animateRun(element, className, activeAnimationComplete) { + var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY); + var node = extractElementNode(element); + if(node.className.indexOf(className) == -1 || !elementData) { + activeAnimationComplete(); + return; + } + + var timings = elementData.timings; + var stagger = elementData.stagger; + var maxDuration = elementData.maxDuration; + var activeClassName = elementData.activeClassName; + var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * ONE_SECOND; + var startTime = Date.now(); + var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT; + var itemIndex = elementData.itemIndex; + + var style = '', appliedStyles = []; + if(timings.transitionDuration > 0) { + var propertyStyle = timings.transitionPropertyStyle; + if(propertyStyle.indexOf('all') == -1) { + style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ';'; + style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ';'; + appliedStyles.push(CSS_PREFIX + 'transition-property'); + appliedStyles.push(CSS_PREFIX + 'transition-duration'); + } + } + + if(itemIndex > 0) { + if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) { + var delayStyle = timings.transitionDelayStyle; + style += CSS_PREFIX + 'transition-delay: ' + + prepareStaggerDelay(delayStyle, stagger.transitionDelay, itemIndex) + '; '; + appliedStyles.push(CSS_PREFIX + 'transition-delay'); + } + + if(stagger.animationDelay > 0 && stagger.animationDuration === 0) { + style += CSS_PREFIX + 'animation-delay: ' + + prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, itemIndex) + '; '; + appliedStyles.push(CSS_PREFIX + 'animation-delay'); + } + } + + if(appliedStyles.length > 0) { + //the element being animated may sometimes contain comment nodes in + //the jqLite object, so we're safe to use a single variable to house + //the styles since there is always only one element being animated + var oldStyle = node.getAttribute('style') || ''; + node.setAttribute('style', oldStyle + ' ' + style); + } + + element.on(css3AnimationEvents, onAnimationProgress); + element.addClass(activeClassName); + elementData.closeAnimationFn = function() { + onEnd(); + activeAnimationComplete(); + }; + return onEnd; + + // This will automatically be called by $animate so + // there is no need to attach this internally to the + // timeout done method. + function onEnd(cancelled) { + element.off(css3AnimationEvents, onAnimationProgress); + element.removeClass(activeClassName); + animateClose(element, className); + var node = extractElementNode(element); + for (var i in appliedStyles) { + node.style.removeProperty(appliedStyles[i]); + } + } + + function onAnimationProgress(event) { + event.stopPropagation(); + var ev = event.originalEvent || event; + var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now(); + + /* Firefox (or possibly just Gecko) likes to not round values up + * when a ms measurement is used for the animation */ + var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES)); + + /* $manualTimeStamp is a mocked timeStamp value which is set + * within browserTrigger(). This is only here so that tests can + * mock animations properly. Real events fallback to event.timeStamp, + * or, if they don't, then a timeStamp is automatically created for them. + * We're checking to see if the timeStamp surpasses the expected delay, + * but we're using elapsedTime instead of the timeStamp on the 2nd + * pre-condition since animations sometimes close off early */ + if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) { + activeAnimationComplete(); + } + } + } + + function prepareStaggerDelay(delayStyle, staggerDelay, index) { + var style = ''; + forEach(delayStyle.split(','), function(val, i) { + style += (i > 0 ? ',' : '') + + (index * staggerDelay + parseInt(val, 10)) + 's'; + }); + return style; + } + + function animateBefore(element, className, calculationDecorator) { + if(animateSetup(element, className, calculationDecorator)) { + return function(cancelled) { + cancelled && animateClose(element, className); + }; + } + } + + function animateAfter(element, className, afterAnimationComplete) { + if(element.data(NG_ANIMATE_CSS_DATA_KEY)) { + return animateRun(element, className, afterAnimationComplete); + } else { + animateClose(element, className); + afterAnimationComplete(); + } + } + + function animate(element, className, animationComplete) { + //If the animateSetup function doesn't bother returning a + //cancellation function then it means that there is no animation + //to perform at all + var preReflowCancellation = animateBefore(element, className); + if(!preReflowCancellation) { + animationComplete(); + return; + } + + //There are two cancellation functions: one is before the first + //reflow animation and the second is during the active state + //animation. The first function will take care of removing the + //data from the element which will not make the 2nd animation + //happen in the first place + var cancel = preReflowCancellation; + afterReflow(element, function() { + unblockTransitions(element); + unblockKeyframeAnimations(element); + //once the reflow is complete then we point cancel to + //the new cancellation function which will remove all of the + //animation properties from the active animation + cancel = animateAfter(element, className, animationComplete); + }); + + return function(cancelled) { + (cancel || noop)(cancelled); + }; + } + + function animateClose(element, className) { + element.removeClass(className); + element.removeData(NG_ANIMATE_CSS_DATA_KEY); + } + + return { + allowCancel : function(element, animationEvent, className) { + //always cancel the current animation if it is a + //structural animation + var oldClasses = (element.data(NG_ANIMATE_CSS_DATA_KEY) || {}).classes; + if(!oldClasses || ['enter','leave','move'].indexOf(animationEvent) >= 0) { + return true; + } + + var parentElement = element.parent(); + var clone = angular.element(extractElementNode(element).cloneNode()); + + //make the element super hidden and override any CSS style values + clone.attr('style','position:absolute; top:-9999px; left:-9999px'); + clone.removeAttr('id'); + clone.empty(); + + forEach(oldClasses.split(' '), function(klass) { + clone.removeClass(klass); + }); + + var suffix = animationEvent == 'addClass' ? '-add' : '-remove'; + clone.addClass(suffixClasses(className, suffix)); + parentElement.append(clone); + + var timings = getElementAnimationDetails(clone); + clone.remove(); + + return Math.max(timings.transitionDuration, timings.animationDuration) > 0; + }, + + enter : function(element, animationCompleted) { + return animate(element, 'ng-enter', animationCompleted); + }, + + leave : function(element, animationCompleted) { + return animate(element, 'ng-leave', animationCompleted); + }, + + move : function(element, animationCompleted) { + return animate(element, 'ng-move', animationCompleted); + }, + + beforeAddClass : function(element, className, animationCompleted) { + var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'), function(fn) { + + /* when a CSS class is added to an element then the transition style that + * is applied is the transition defined on the element when the CSS class + * is added at the time of the animation. This is how CSS3 functions + * outside of ngAnimate. */ + element.addClass(className); + var timings = fn(); + element.removeClass(className); + return timings; + }); + + if(cancellationMethod) { + afterReflow(element, function() { + unblockTransitions(element); + unblockKeyframeAnimations(element); + animationCompleted(); + }); + return cancellationMethod; + } + animationCompleted(); + }, + + addClass : function(element, className, animationCompleted) { + return animateAfter(element, suffixClasses(className, '-add'), animationCompleted); + }, + + beforeRemoveClass : function(element, className, animationCompleted) { + var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'), function(fn) { + /* when classes are removed from an element then the transition style + * that is applied is the transition defined on the element without the + * CSS class being there. This is how CSS3 functions outside of ngAnimate. + * http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */ + var klass = element.attr('class'); + element.removeClass(className); + var timings = fn(); + element.attr('class', klass); + return timings; + }); + + if(cancellationMethod) { + afterReflow(element, function() { + unblockTransitions(element); + unblockKeyframeAnimations(element); + animationCompleted(); + }); + return cancellationMethod; + } + animationCompleted(); + }, + + removeClass : function(element, className, animationCompleted) { + return animateAfter(element, suffixClasses(className, '-remove'), animationCompleted); + } + }; + + function suffixClasses(classes, suffix) { + var className = ''; + classes = angular.isArray(classes) ? classes : classes.split(/\s+/); + forEach(classes, function(klass, i) { + if(klass && klass.length > 0) { + className += (i > 0 ? ' ' : '') + klass + suffix; + } + }); + return className; + } + }]); + }]); + + +})(window, window.angular); diff --git a/src/main/webapp/subscriber/ui/js/angular/angular-cookies.js b/src/main/webapp/subscriber/ui/js/angular/angular-cookies.js new file mode 100644 index 0000000..d96dc45 --- /dev/null +++ b/src/main/webapp/subscriber/ui/js/angular/angular-cookies.js @@ -0,0 +1,202 @@ +/** + * @license AngularJS v1.2.9 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +/** + * @ngdoc overview + * @name ngCookies + * @description + * + * # ngCookies + * + * The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies. + * + * {@installModule cookies} + * + *
      + * + * See {@link ngCookies.$cookies `$cookies`} and + * {@link ngCookies.$cookieStore `$cookieStore`} for usage. + */ + + +angular.module('ngCookies', ['ng']). + /** + * @ngdoc object + * @name ngCookies.$cookies + * @requires $browser + * + * @description + * Provides read/write access to browser's cookies. + * + * Only a simple Object is exposed and by adding or removing properties to/from + * this object, new cookies are created/deleted at the end of current $eval. + * + * Requires the {@link ngCookies `ngCookies`} module to be installed. + * + * @example + + + + + + */ + factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) { + var cookies = {}, + lastCookies = {}, + lastBrowserCookies, + runEval = false, + copy = angular.copy, + isUndefined = angular.isUndefined; + + //creates a poller fn that copies all cookies from the $browser to service & inits the service + $browser.addPollFn(function() { + var currentCookies = $browser.cookies(); + if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl + lastBrowserCookies = currentCookies; + copy(currentCookies, lastCookies); + copy(currentCookies, cookies); + if (runEval) $rootScope.$apply(); + } + })(); + + runEval = true; + + //at the end of each eval, push cookies + //TODO: this should happen before the "delayed" watches fire, because if some cookies are not + // strings or browser refuses to store some cookies, we update the model in the push fn. + $rootScope.$watch(push); + + return cookies; + + + /** + * Pushes all the cookies from the service to the browser and verifies if all cookies were + * stored. + */ + function push() { + var name, + value, + browserCookies, + updated; + + //delete any cookies deleted in $cookies + for (name in lastCookies) { + if (isUndefined(cookies[name])) { + $browser.cookies(name, undefined); + } + } + + //update all cookies updated in $cookies + for(name in cookies) { + value = cookies[name]; + if (!angular.isString(value)) { + if (angular.isDefined(lastCookies[name])) { + cookies[name] = lastCookies[name]; + } else { + delete cookies[name]; + } + } else if (value !== lastCookies[name]) { + $browser.cookies(name, value); + updated = true; + } + } + + //verify what was actually stored + if (updated){ + updated = false; + browserCookies = $browser.cookies(); + + for (name in cookies) { + if (cookies[name] !== browserCookies[name]) { + //delete or reset all cookies that the browser dropped from $cookies + if (isUndefined(browserCookies[name])) { + delete cookies[name]; + } else { + cookies[name] = browserCookies[name]; + } + updated = true; + } + } + } + } + }]). + + + /** + * @ngdoc object + * @name ngCookies.$cookieStore + * @requires $cookies + * + * @description + * Provides a key-value (string-object) storage, that is backed by session cookies. + * Objects put or retrieved from this storage are automatically serialized or + * deserialized by angular's toJson/fromJson. + * + * Requires the {@link ngCookies `ngCookies`} module to be installed. + * + * @example + */ + factory('$cookieStore', ['$cookies', function($cookies) { + + return { + /** + * @ngdoc method + * @name ngCookies.$cookieStore#get + * @methodOf ngCookies.$cookieStore + * + * @description + * Returns the value of given cookie key + * + * @param {string} key Id to use for lookup. + * @returns {Object} Deserialized cookie value. + */ + get: function(key) { + var value = $cookies[key]; + return value ? angular.fromJson(value) : value; + }, + + /** + * @ngdoc method + * @name ngCookies.$cookieStore#put + * @methodOf ngCookies.$cookieStore + * + * @description + * Sets a value for given cookie key + * + * @param {string} key Id for the `value`. + * @param {Object} value Value to be stored. + */ + put: function(key, value) { + $cookies[key] = angular.toJson(value); + }, + + /** + * @ngdoc method + * @name ngCookies.$cookieStore#remove + * @methodOf ngCookies.$cookieStore + * + * @description + * Remove given cookie + * + * @param {string} key Id of the key-value pair to delete. + */ + remove: function(key) { + delete $cookies[key]; + } + }; + + }]); + + +})(window, window.angular); diff --git a/src/main/webapp/subscriber/ui/js/angular/angular-resource.js b/src/main/webapp/subscriber/ui/js/angular/angular-resource.js new file mode 100644 index 0000000..6c71276 --- /dev/null +++ b/src/main/webapp/subscriber/ui/js/angular/angular-resource.js @@ -0,0 +1,594 @@ +/** + * @license AngularJS v1.2.9 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +var $resourceMinErr = angular.$$minErr('$resource'); + +// Helper functions and regex to lookup a dotted path on an object +// stopping at undefined/null. The path must be composed of ASCII +// identifiers (just like $parse) +var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/; + +function isValidDottedPath(path) { + return (path != null && path !== '' && path !== 'hasOwnProperty' && + MEMBER_NAME_REGEX.test('.' + path)); +} + +function lookupDottedPath(obj, path) { + if (!isValidDottedPath(path)) { + throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); + } + var keys = path.split('.'); + for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) { + var key = keys[i]; + obj = (obj !== null) ? obj[key] : undefined; + } + return obj; +} + +/** + * Create a shallow copy of an object and clear other fields from the destination + */ +function shallowClearAndCopy(src, dst) { + dst = dst || {}; + + angular.forEach(dst, function(value, key){ + delete dst[key]; + }); + + for (var key in src) { + if (src.hasOwnProperty(key) && key.charAt(0) !== '$' && key.charAt(1) !== '$') { + dst[key] = src[key]; + } + } + + return dst; +} + +/** + * @ngdoc overview + * @name ngResource + * @description + * + * # ngResource + * + * The `ngResource` module provides interaction support with RESTful services + * via the $resource service. + * + * {@installModule resource} + * + *
      + * + * See {@link ngResource.$resource `$resource`} for usage. + */ + +/** + * @ngdoc object + * @name ngResource.$resource + * @requires $http + * + * @description + * A factory which creates a resource object that lets you interact with + * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. + * + * The returned resource object has action methods which provide high-level behaviors without + * the need to interact with the low level {@link ng.$http $http} service. + * + * Requires the {@link ngResource `ngResource`} module to be installed. + * + * @param {string} url A parametrized URL template with parameters prefixed by `:` as in + * `/user/:username`. If you are using a URL with a port number (e.g. + * `http://example.com:8080/api`), it will be respected. + * + * If you are using a url with a suffix, just add the suffix, like this: + * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` + * or even `$resource('http://example.com/resource/:resource_id.:format')` + * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be + * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you + * can escape it with `/\.`. + * + * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in + * `actions` methods. If any of the parameter value is a function, it will be executed every time + * when a param value needs to be obtained for a request (unless the param was overridden). + * + * Each key value in the parameter object is first bound to url template if present and then any + * excess keys are appended to the url search query after the `?`. + * + * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in + * URL `/path/greet?salutation=Hello`. + * + * If the parameter value is prefixed with `@` then the value of that parameter is extracted from + * the data object (useful for non-GET operations). + * + * @param {Object.=} actions Hash with declaration of custom action that should extend the + * default set of resource actions. The declaration should be created in the format of {@link + * ng.$http#usage_parameters $http.config}: + * + * {action1: {method:?, params:?, isArray:?, headers:?, ...}, + * action2: {method:?, params:?, isArray:?, headers:?, ...}, + * ...} + * + * Where: + * + * - **`action`** – {string} – The name of action. This name becomes the name of the method on + * your resource object. + * - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, + * `DELETE`, and `JSONP`. + * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of + * the parameter value is a function, it will be executed every time when a param value needs to + * be obtained for a request (unless the param was overridden). + * - **`url`** – {string} – action specific `url` override. The url templating is supported just + * like for the resource-level urls. + * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, + * see `returns` section. + * - **`transformRequest`** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * request body and headers and returns its transformed (typically serialized) version. + * - **`transformResponse`** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * response body and headers and returns its transformed (typically deserialized) version. + * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the + * GET request, otherwise if a cache instance built with + * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for + * caching. + * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that + * should abort the request when resolved. + * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the + * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 + * requests with credentials} for more information. + * - **`responseType`** - `{string}` - see {@link + * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. + * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - + * `response` and `responseError`. Both `response` and `responseError` interceptors get called + * with `http response` object. See {@link ng.$http $http interceptors}. + * + * @returns {Object} A resource "class" object with methods for the default set of resource actions + * optionally extended with custom `actions`. The default set contains these actions: + * + * { 'get': {method:'GET'}, + * 'save': {method:'POST'}, + * 'query': {method:'GET', isArray:true}, + * 'remove': {method:'DELETE'}, + * 'delete': {method:'DELETE'} }; + * + * Calling these methods invoke an {@link ng.$http} with the specified http method, + * destination and parameters. When the data is returned from the server then the object is an + * instance of the resource class. The actions `save`, `remove` and `delete` are available on it + * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, + * read, update, delete) on server-side data like this: + *
      +        var User = $resource('/user/:userId', {userId:'@id'});
      +        var user = User.get({userId:123}, function() {
      +          user.abc = true;
      +          user.$save();
      +        });
      +     
      + * + * It is important to realize that invoking a $resource object method immediately returns an + * empty reference (object or array depending on `isArray`). Once the data is returned from the + * server the existing reference is populated with the actual data. This is a useful trick since + * usually the resource is assigned to a model which is then rendered by the view. Having an empty + * object results in no rendering, once the data arrives from the server then the object is + * populated with the data and the view automatically re-renders itself showing the new data. This + * means that in most cases one never has to write a callback function for the action methods. + * + * The action methods on the class object or instance object can be invoked with the following + * parameters: + * + * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` + * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` + * - non-GET instance actions: `instance.$action([parameters], [success], [error])` + * + * Success callback is called with (value, responseHeaders) arguments. Error callback is called + * with (httpResponse) argument. + * + * Class actions return empty instance (with additional properties below). + * Instance actions return promise of the action. + * + * The Resource instances and collection have these additional properties: + * + * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this + * instance or collection. + * + * On success, the promise is resolved with the same resource instance or collection object, + * updated with data from server. This makes it easy to use in + * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view + * rendering until the resource(s) are loaded. + * + * On failure, the promise is resolved with the {@link ng.$http http response} object, without + * the `resource` property. + * + * - `$resolved`: `true` after first server interaction is completed (either with success or + * rejection), `false` before that. Knowing if the Resource has been resolved is useful in + * data-binding. + * + * @example + * + * # Credit card resource + * + *
      +     // Define CreditCard class
      +     var CreditCard = $resource('/user/:userId/card/:cardId',
      +      {userId:123, cardId:'@id'}, {
      +       charge: {method:'POST', params:{charge:true}}
      +      });
      +
      +     // We can retrieve a collection from the server
      +     var cards = CreditCard.query(function() {
      +       // GET: /user/123/card
      +       // server returns: [ {id:456, number:'1234', name:'Smith'} ];
      +
      +       var card = cards[0];
      +       // each item is an instance of CreditCard
      +       expect(card instanceof CreditCard).toEqual(true);
      +       card.name = "J. Smith";
      +       // non GET methods are mapped onto the instances
      +       card.$save();
      +       // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
      +       // server returns: {id:456, number:'1234', name: 'J. Smith'};
      +
      +       // our custom method is mapped as well.
      +       card.$charge({amount:9.99});
      +       // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
      +     });
      +
      +     // we can create an instance as well
      +     var newCard = new CreditCard({number:'0123'});
      +     newCard.name = "Mike Smith";
      +     newCard.$save();
      +     // POST: /user/123/card {number:'0123', name:'Mike Smith'}
      +     // server returns: {id:789, number:'0123', name: 'Mike Smith'};
      +     expect(newCard.id).toEqual(789);
      + * 
      + * + * The object returned from this function execution is a resource "class" which has "static" method + * for each action in the definition. + * + * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and + * `headers`. + * When the data is returned from the server then the object is an instance of the resource type and + * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD + * operations (create, read, update, delete) on server-side data. + +
      +     var User = $resource('/user/:userId', {userId:'@id'});
      +     var user = User.get({userId:123}, function() {
      +       user.abc = true;
      +       user.$save();
      +     });
      +   
      + * + * It's worth noting that the success callback for `get`, `query` and other methods gets passed + * in the response that came from the server as well as $http header getter function, so one + * could rewrite the above example and get access to http headers as: + * +
      +     var User = $resource('/user/:userId', {userId:'@id'});
      +     User.get({userId:123}, function(u, getResponseHeaders){
      +       u.abc = true;
      +       u.$save(function(u, putResponseHeaders) {
      +         //u => saved user object
      +         //putResponseHeaders => $http header getter
      +       });
      +     });
      +   
      + + * # Creating a custom 'PUT' request + * In this example we create a custom method on our resource to make a PUT request + *
      + *		var app = angular.module('app', ['ngResource', 'ngRoute']);
      + *
      + *		// Some APIs expect a PUT request in the format URL/object/ID
      + *		// Here we are creating an 'update' method 
      + *		app.factory('Notes', ['$resource', function($resource) {
      + *    return $resource('/notes/:id', null,
      + *        {
      + *            'update': { method:'PUT' }
      + *        });
      + *		}]);
      + *
      + *		// In our controller we get the ID from the URL using ngRoute and $routeParams
      + *		// We pass in $routeParams and our Notes factory along with $scope
      + *		app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
      +                                      function($scope, $routeParams, Notes) {
      + *    // First get a note object from the factory
      + *    var note = Notes.get({ id:$routeParams.id });
      + *    $id = note.id;
      + *
      + *    // Now call update passing in the ID first then the object you are updating
      + *    Notes.update({ id:$id }, note);
      + *
      + *    // This will PUT /notes/ID with the note object in the request payload
      + *		}]);
      + * 
      + */ +angular.module('ngResource', ['ng']). + factory('$resource', ['$http', '$q', function($http, $q) { + + var DEFAULT_ACTIONS = { + 'get': {method:'GET'}, + 'save': {method:'POST'}, + 'query': {method:'GET', isArray:true}, + 'remove': {method:'DELETE'}, + 'delete': {method:'DELETE'} + }; + var noop = angular.noop, + forEach = angular.forEach, + extend = angular.extend, + copy = angular.copy, + isFunction = angular.isFunction; + + /** + * We need our custom method because encodeURIComponent is too aggressive and doesn't follow + * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path + * segments: + * segment = *pchar + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * pct-encoded = "%" HEXDIG HEXDIG + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriSegment(val) { + return encodeUriQuery(val, true). + replace(/%26/gi, '&'). + replace(/%3D/gi, '='). + replace(/%2B/gi, '+'); + } + + + /** + * This method is intended for encoding *key* or *value* parts of query component. We need a + * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't + * have to be encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); + } + + function Route(template, defaults) { + this.template = template; + this.defaults = defaults || {}; + this.urlParams = {}; + } + + Route.prototype = { + setUrlParams: function(config, params, actionUrl) { + var self = this, + url = actionUrl || self.template, + val, + encodedVal; + + var urlParams = self.urlParams = {}; + forEach(url.split(/\W/), function(param){ + if (param === 'hasOwnProperty') { + throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); + } + if (!(new RegExp("^\\d+$").test(param)) && param && + (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { + urlParams[param] = true; + } + }); + url = url.replace(/\\:/g, ':'); + + params = params || {}; + forEach(self.urlParams, function(_, urlParam){ + val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; + if (angular.isDefined(val) && val !== null) { + encodedVal = encodeUriSegment(val); + url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1"); + } else { + url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, + leadingSlashes, tail) { + if (tail.charAt(0) == '/') { + return tail; + } else { + return leadingSlashes + tail; + } + }); + } + }); + + // strip trailing slashes and set the url + url = url.replace(/\/+$/, '') || '/'; + // then replace collapse `/.` if found in the last URL path segment before the query + // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` + url = url.replace(/\/\.(?=\w+($|\?))/, '.'); + // replace escaped `/\.` with `/.` + config.url = url.replace(/\/\\\./, '/.'); + + + // set params - delegate param encoding to $http + forEach(params, function(value, key){ + if (!self.urlParams[key]) { + config.params = config.params || {}; + config.params[key] = value; + } + }); + } + }; + + + function resourceFactory(url, paramDefaults, actions) { + var route = new Route(url); + + actions = extend({}, DEFAULT_ACTIONS, actions); + + function extractParams(data, actionParams){ + var ids = {}; + actionParams = extend({}, paramDefaults, actionParams); + forEach(actionParams, function(value, key){ + if (isFunction(value)) { value = value(); } + ids[key] = value && value.charAt && value.charAt(0) == '@' ? + lookupDottedPath(data, value.substr(1)) : value; + }); + return ids; + } + + function defaultResponseInterceptor(response) { + return response.resource; + } + + function Resource(value){ + shallowClearAndCopy(value || {}, this); + } + + forEach(actions, function(action, name) { + var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); + + Resource[name] = function(a1, a2, a3, a4) { + var params = {}, data, success, error; + + /* jshint -W086 */ /* (purposefully fall through case statements) */ + switch(arguments.length) { + case 4: + error = a4; + success = a3; + //fallthrough + case 3: + case 2: + if (isFunction(a2)) { + if (isFunction(a1)) { + success = a1; + error = a2; + break; + } + + success = a2; + error = a3; + //fallthrough + } else { + params = a1; + data = a2; + success = a3; + break; + } + case 1: + if (isFunction(a1)) success = a1; + else if (hasBody) data = a1; + else params = a1; + break; + case 0: break; + default: + throw $resourceMinErr('badargs', + "Expected up to 4 arguments [params, data, success, error], got {0} arguments", + arguments.length); + } + /* jshint +W086 */ /* (purposefully fall through case statements) */ + + var isInstanceCall = this instanceof Resource; + var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); + var httpConfig = {}; + var responseInterceptor = action.interceptor && action.interceptor.response || + defaultResponseInterceptor; + var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || + undefined; + + forEach(action, function(value, key) { + if (key != 'params' && key != 'isArray' && key != 'interceptor') { + httpConfig[key] = copy(value); + } + }); + + if (hasBody) httpConfig.data = data; + route.setUrlParams(httpConfig, + extend({}, extractParams(data, action.params || {}), params), + action.url); + + var promise = $http(httpConfig).then(function(response) { + var data = response.data, + promise = value.$promise; + + if (data) { + // Need to convert action.isArray to boolean in case it is undefined + // jshint -W018 + if (angular.isArray(data) !== (!!action.isArray)) { + throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' + + 'response to contain an {0} but got an {1}', + action.isArray?'array':'object', angular.isArray(data)?'array':'object'); + } + // jshint +W018 + if (action.isArray) { + value.length = 0; + forEach(data, function(item) { + value.push(new Resource(item)); + }); + } else { + shallowClearAndCopy(data, value); + value.$promise = promise; + } + } + + value.$resolved = true; + + response.resource = value; + + return response; + }, function(response) { + value.$resolved = true; + + (error||noop)(response); + + return $q.reject(response); + }); + + promise = promise.then( + function(response) { + var value = responseInterceptor(response); + (success||noop)(value, response.headers); + return value; + }, + responseErrorInterceptor); + + if (!isInstanceCall) { + // we are creating instance / collection + // - set the initial promise + // - return the instance / collection + value.$promise = promise; + value.$resolved = false; + + return value; + } + + // instance call + return promise; + }; + + + Resource.prototype['$' + name] = function(params, success, error) { + if (isFunction(params)) { + error = success; success = params; params = {}; + } + var result = Resource[name].call(this, params, this, success, error); + return result.$promise || result; + }; + }); + + Resource.bind = function(additionalParamDefaults){ + return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); + }; + + return Resource; + } + + return resourceFactory; + }]); + + +})(window, window.angular); diff --git a/src/main/webapp/subscriber/ui/js/angular/angular-route.js b/src/main/webapp/subscriber/ui/js/angular/angular-route.js new file mode 100644 index 0000000..a7e346d --- /dev/null +++ b/src/main/webapp/subscriber/ui/js/angular/angular-route.js @@ -0,0 +1,920 @@ +/** + * @license AngularJS v1.2.9 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +/** + * @ngdoc overview + * @name ngRoute + * @description + * + * # ngRoute + * + * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. + * + * ## Example + * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. + * + * {@installModule route} + * + *
      + */ + /* global -ngRouteModule */ +var ngRouteModule = angular.module('ngRoute', ['ng']). + provider('$route', $RouteProvider); + +/** + * @ngdoc object + * @name ngRoute.$routeProvider + * @function + * + * @description + * + * Used for configuring routes. + * + * ## Example + * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. + * + * ## Dependencies + * Requires the {@link ngRoute `ngRoute`} module to be installed. + */ +function $RouteProvider(){ + function inherit(parent, extra) { + return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); + } + + var routes = {}; + + /** + * @ngdoc method + * @name ngRoute.$routeProvider#when + * @methodOf ngRoute.$routeProvider + * + * @param {string} path Route path (matched against `$location.path`). If `$location.path` + * contains redundant trailing slash or is missing one, the route will still match and the + * `$location.path` will be updated to add or drop the trailing slash to exactly match the + * route definition. + * + * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up + * to the next slash are matched and stored in `$routeParams` under the given `name` + * when the route matches. + * * `path` can contain named groups starting with a colon and ending with a star: + * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` + * when the route matches. + * * `path` can contain optional named groups with a question mark: e.g.`:name?`. + * + * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match + * `/color/brown/largecode/code/with/slashs/edit` and extract: + * + * * `color: brown` + * * `largecode: code/with/slashs`. + * + * + * @param {Object} route Mapping information to be assigned to `$route.current` on route + * match. + * + * Object properties: + * + * - `controller` – `{(string|function()=}` – Controller fn that should be associated with + * newly created scope or the name of a {@link angular.Module#controller registered + * controller} if passed as a string. + * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be + * published to scope under the `controllerAs` name. + * - `template` – `{string=|function()=}` – html template as a string or a function that + * returns an html template as a string which should be used by {@link + * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. + * This property takes precedence over `templateUrl`. + * + * If `template` is a function, it will be called with the following parameters: + * + * - `{Array.}` - route parameters extracted from the current + * `$location.path()` by applying the current route + * + * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html + * template that should be used by {@link ngRoute.directive:ngView ngView}. + * + * If `templateUrl` is a function, it will be called with the following parameters: + * + * - `{Array.}` - route parameters extracted from the current + * `$location.path()` by applying the current route + * + * - `resolve` - `{Object.=}` - An optional map of dependencies which should + * be injected into the controller. If any of these dependencies are promises, the router + * will wait for them all to be resolved or one to be rejected before the controller is + * instantiated. + * If all the promises are resolved successfully, the values of the resolved promises are + * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is + * fired. If any of the promises are rejected the + * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object + * is: + * + * - `key` – `{string}`: a name of a dependency to be injected into the controller. + * - `factory` - `{string|function}`: If `string` then it is an alias for a service. + * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} + * and the return value is treated as the dependency. If the result is a promise, it is + * resolved before its value is injected into the controller. Be aware that + * `ngRoute.$routeParams` will still refer to the previous route within these resolve + * functions. Use `$route.current.params` to access the new route parameters, instead. + * + * - `redirectTo` – {(string|function())=} – value to update + * {@link ng.$location $location} path with and trigger route redirection. + * + * If `redirectTo` is a function, it will be called with the following parameters: + * + * - `{Object.}` - route parameters extracted from the current + * `$location.path()` by applying the current route templateUrl. + * - `{string}` - current `$location.path()` + * - `{Object}` - current `$location.search()` + * + * The custom `redirectTo` function is expected to return a string which will be used + * to update `$location.path()` and `$location.search()`. + * + * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` + * or `$location.hash()` changes. + * + * If the option is set to `false` and url in the browser changes, then + * `$routeUpdate` event is broadcasted on the root scope. + * + * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive + * + * If the option is set to `true`, then the particular route can be matched without being + * case sensitive + * + * @returns {Object} self + * + * @description + * Adds a new route definition to the `$route` service. + */ + this.when = function(path, route) { + routes[path] = angular.extend( + {reloadOnSearch: true}, + route, + path && pathRegExp(path, route) + ); + + // create redirection for trailing slashes + if (path) { + var redirectPath = (path[path.length-1] == '/') + ? path.substr(0, path.length-1) + : path +'/'; + + routes[redirectPath] = angular.extend( + {redirectTo: path}, + pathRegExp(redirectPath, route) + ); + } + + return this; + }; + + /** + * @param path {string} path + * @param opts {Object} options + * @return {?Object} + * + * @description + * Normalizes the given path, returning a regular expression + * and the original path. + * + * Inspired by pathRexp in visionmedia/express/lib/utils.js. + */ + function pathRegExp(path, opts) { + var insensitive = opts.caseInsensitiveMatch, + ret = { + originalPath: path, + regexp: path + }, + keys = ret.keys = []; + + path = path + .replace(/([().])/g, '\\$1') + .replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){ + var optional = option === '?' ? option : null; + var star = option === '*' ? option : null; + keys.push({ name: key, optional: !!optional }); + slash = slash || ''; + return '' + + (optional ? '' : slash) + + '(?:' + + (optional ? slash : '') + + (star && '(.+?)' || '([^/]+)') + + (optional || '') + + ')' + + (optional || ''); + }) + .replace(/([\/$\*])/g, '\\$1'); + + ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); + return ret; + } + + /** + * @ngdoc method + * @name ngRoute.$routeProvider#otherwise + * @methodOf ngRoute.$routeProvider + * + * @description + * Sets route definition that will be used on route change when no other route definition + * is matched. + * + * @param {Object} params Mapping information to be assigned to `$route.current`. + * @returns {Object} self + */ + this.otherwise = function(params) { + this.when(null, params); + return this; + }; + + + this.$get = ['$rootScope', + '$location', + '$routeParams', + '$q', + '$injector', + '$http', + '$templateCache', + '$sce', + function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { + + /** + * @ngdoc object + * @name ngRoute.$route + * @requires $location + * @requires $routeParams + * + * @property {Object} current Reference to the current route definition. + * The route definition contains: + * + * - `controller`: The controller constructor as define in route definition. + * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for + * controller instantiation. The `locals` contain + * the resolved values of the `resolve` map. Additionally the `locals` also contain: + * + * - `$scope` - The current route scope. + * - `$template` - The current route template HTML. + * + * @property {Array.} routes Array of all configured routes. + * + * @description + * `$route` is used for deep-linking URLs to controllers and views (HTML partials). + * It watches `$location.url()` and tries to map the path to an existing route definition. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. + * + * The `$route` service is typically used in conjunction with the + * {@link ngRoute.directive:ngView `ngView`} directive and the + * {@link ngRoute.$routeParams `$routeParams`} service. + * + * @example + This example shows how changing the URL hash causes the `$route` to match a route against the + URL, and the `ngView` pulls in the partial. + + Note that this example is using {@link ng.directive:script inlined templates} + to get it working on jsfiddle as well. + + + +
      + Choose: + Moby | + Moby: Ch1 | + Gatsby | + Gatsby: Ch4 | + Scarlet Letter
      + +
      +
      + +
      $location.path() = {{$location.path()}}
      +
      $route.current.templateUrl = {{$route.current.templateUrl}}
      +
      $route.current.params = {{$route.current.params}}
      +
      $route.current.scope.name = {{$route.current.scope.name}}
      +
      $routeParams = {{$routeParams}}
      +
      +
      + + + controller: {{name}}
      + Book Id: {{params.bookId}}
      +
      + + + controller: {{name}}
      + Book Id: {{params.bookId}}
      + Chapter Id: {{params.chapterId}} +
      + + + angular.module('ngViewExample', ['ngRoute']) + + .config(function($routeProvider, $locationProvider) { + $routeProvider.when('/Book/:bookId', { + templateUrl: 'book.html', + controller: BookCntl, + resolve: { + // I will cause a 1 second delay + delay: function($q, $timeout) { + var delay = $q.defer(); + $timeout(delay.resolve, 1000); + return delay.promise; + } + } + }); + $routeProvider.when('/Book/:bookId/ch/:chapterId', { + templateUrl: 'chapter.html', + controller: ChapterCntl + }); + + // configure html5 to get links working on jsfiddle + $locationProvider.html5Mode(true); + }); + + function MainCntl($scope, $route, $routeParams, $location) { + $scope.$route = $route; + $scope.$location = $location; + $scope.$routeParams = $routeParams; + } + + function BookCntl($scope, $routeParams) { + $scope.name = "BookCntl"; + $scope.params = $routeParams; + } + + function ChapterCntl($scope, $routeParams) { + $scope.name = "ChapterCntl"; + $scope.params = $routeParams; + } + + + + it('should load and compile correct template', function() { + element('a:contains("Moby: Ch1")').click(); + var content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: ChapterCntl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); + + element('a:contains("Scarlet")').click(); + sleep(2); // promises are not part of scenario waiting + content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: BookCntl/); + expect(content).toMatch(/Book Id\: Scarlet/); + }); + +
      + */ + + /** + * @ngdoc event + * @name ngRoute.$route#$routeChangeStart + * @eventOf ngRoute.$route + * @eventType broadcast on root scope + * @description + * Broadcasted before a route change. At this point the route services starts + * resolving all of the dependencies needed for the route change to occurs. + * Typically this involves fetching the view template as well as any dependencies + * defined in `resolve` route property. Once all of the dependencies are resolved + * `$routeChangeSuccess` is fired. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} next Future route information. + * @param {Route} current Current route information. + */ + + /** + * @ngdoc event + * @name ngRoute.$route#$routeChangeSuccess + * @eventOf ngRoute.$route + * @eventType broadcast on root scope + * @description + * Broadcasted after a route dependencies are resolved. + * {@link ngRoute.directive:ngView ngView} listens for the directive + * to instantiate the controller and render the view. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} current Current route information. + * @param {Route|Undefined} previous Previous route information, or undefined if current is + * first route entered. + */ + + /** + * @ngdoc event + * @name ngRoute.$route#$routeChangeError + * @eventOf ngRoute.$route + * @eventType broadcast on root scope + * @description + * Broadcasted if any of the resolve promises are rejected. + * + * @param {Object} angularEvent Synthetic event object + * @param {Route} current Current route information. + * @param {Route} previous Previous route information. + * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. + */ + + /** + * @ngdoc event + * @name ngRoute.$route#$routeUpdate + * @eventOf ngRoute.$route + * @eventType broadcast on root scope + * @description + * + * The `reloadOnSearch` property has been set to false, and we are reusing the same + * instance of the Controller. + */ + + var forceReload = false, + $route = { + routes: routes, + + /** + * @ngdoc method + * @name ngRoute.$route#reload + * @methodOf ngRoute.$route + * + * @description + * Causes `$route` service to reload the current route even if + * {@link ng.$location $location} hasn't changed. + * + * As a result of that, {@link ngRoute.directive:ngView ngView} + * creates new scope, reinstantiates the controller. + */ + reload: function() { + forceReload = true; + $rootScope.$evalAsync(updateRoute); + } + }; + + $rootScope.$on('$locationChangeSuccess', updateRoute); + + return $route; + + ///////////////////////////////////////////////////// + + /** + * @param on {string} current url + * @param route {Object} route regexp to match the url against + * @return {?Object} + * + * @description + * Check if the route matches the current url. + * + * Inspired by match in + * visionmedia/express/lib/router/router.js. + */ + function switchRouteMatcher(on, route) { + var keys = route.keys, + params = {}; + + if (!route.regexp) return null; + + var m = route.regexp.exec(on); + if (!m) return null; + + for (var i = 1, len = m.length; i < len; ++i) { + var key = keys[i - 1]; + + var val = 'string' == typeof m[i] + ? decodeURIComponent(m[i]) + : m[i]; + + if (key && val) { + params[key.name] = val; + } + } + return params; + } + + function updateRoute() { + var next = parseRoute(), + last = $route.current; + + if (next && last && next.$$route === last.$$route + && angular.equals(next.pathParams, last.pathParams) + && !next.reloadOnSearch && !forceReload) { + last.params = next.params; + angular.copy(last.params, $routeParams); + $rootScope.$broadcast('$routeUpdate', last); + } else if (next || last) { + forceReload = false; + $rootScope.$broadcast('$routeChangeStart', next, last); + $route.current = next; + if (next) { + if (next.redirectTo) { + if (angular.isString(next.redirectTo)) { + $location.path(interpolate(next.redirectTo, next.params)).search(next.params) + .replace(); + } else { + $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) + .replace(); + } + } + } + + $q.when(next). + then(function() { + if (next) { + var locals = angular.extend({}, next.resolve), + template, templateUrl; + + angular.forEach(locals, function(value, key) { + locals[key] = angular.isString(value) ? + $injector.get(value) : $injector.invoke(value); + }); + + if (angular.isDefined(template = next.template)) { + if (angular.isFunction(template)) { + template = template(next.params); + } + } else if (angular.isDefined(templateUrl = next.templateUrl)) { + if (angular.isFunction(templateUrl)) { + templateUrl = templateUrl(next.params); + } + templateUrl = $sce.getTrustedResourceUrl(templateUrl); + if (angular.isDefined(templateUrl)) { + next.loadedTemplateUrl = templateUrl; + template = $http.get(templateUrl, {cache: $templateCache}). + then(function(response) { return response.data; }); + } + } + if (angular.isDefined(template)) { + locals['$template'] = template; + } + return $q.all(locals); + } + }). + // after route change + then(function(locals) { + if (next == $route.current) { + if (next) { + next.locals = locals; + angular.copy(next.params, $routeParams); + } + $rootScope.$broadcast('$routeChangeSuccess', next, last); + } + }, function(error) { + if (next == $route.current) { + $rootScope.$broadcast('$routeChangeError', next, last, error); + } + }); + } + } + + + /** + * @returns the current active route, by matching it against the URL + */ + function parseRoute() { + // Match a route + var params, match; + angular.forEach(routes, function(route, path) { + if (!match && (params = switchRouteMatcher($location.path(), route))) { + match = inherit(route, { + params: angular.extend({}, $location.search(), params), + pathParams: params}); + match.$$route = route; + } + }); + // No route matched; fallback to "otherwise" route + return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); + } + + /** + * @returns interpolation of the redirect path with the parameters + */ + function interpolate(string, params) { + var result = []; + angular.forEach((string||'').split(':'), function(segment, i) { + if (i === 0) { + result.push(segment); + } else { + var segmentMatch = segment.match(/(\w+)(.*)/); + var key = segmentMatch[1]; + result.push(params[key]); + result.push(segmentMatch[2] || ''); + delete params[key]; + } + }); + return result.join(''); + } + }]; +} + +ngRouteModule.provider('$routeParams', $RouteParamsProvider); + + +/** + * @ngdoc object + * @name ngRoute.$routeParams + * @requires $route + * + * @description + * The `$routeParams` service allows you to retrieve the current set of route parameters. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * The route parameters are a combination of {@link ng.$location `$location`}'s + * {@link ng.$location#methods_search `search()`} and {@link ng.$location#methods_path `path()`}. + * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. + * + * In case of parameter name collision, `path` params take precedence over `search` params. + * + * The service guarantees that the identity of the `$routeParams` object will remain unchanged + * (but its properties will likely change) even when a route change occurs. + * + * Note that the `$routeParams` are only updated *after* a route change completes successfully. + * This means that you cannot rely on `$routeParams` being correct in route resolve functions. + * Instead you can use `$route.current.params` to access the new route's parameters. + * + * @example + *
      + *  // Given:
      + *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
      + *  // Route: /Chapter/:chapterId/Section/:sectionId
      + *  //
      + *  // Then
      + *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
      + * 
      + */ +function $RouteParamsProvider() { + this.$get = function() { return {}; }; +} + +ngRouteModule.directive('ngView', ngViewFactory); +ngRouteModule.directive('ngView', ngViewFillContentFactory); + + +/** + * @ngdoc directive + * @name ngRoute.directive:ngView + * @restrict ECA + * + * @description + * # Overview + * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by + * including the rendered template of the current route into the main layout (`index.html`) file. + * Every time the current route changes, the included view changes with it according to the + * configuration of the `$route` service. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * @animations + * enter - animation is used to bring new content into the browser. + * leave - animation is used to animate existing content away. + * + * The enter and leave animation occur concurrently. + * + * @scope + * @priority 400 + * @param {string=} onload Expression to evaluate whenever the view updates. + * + * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll + * $anchorScroll} to scroll the viewport after the view is updated. + * + * - If the attribute is not set, disable scrolling. + * - If the attribute is set without value, enable scrolling. + * - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated + * as an expression yields a truthy value. + * @example + + +
      + Choose: + Moby | + Moby: Ch1 | + Gatsby | + Gatsby: Ch4 | + Scarlet Letter
      + +
      +
      +
      +
      + +
      $location.path() = {{main.$location.path()}}
      +
      $route.current.templateUrl = {{main.$route.current.templateUrl}}
      +
      $route.current.params = {{main.$route.current.params}}
      +
      $route.current.scope.name = {{main.$route.current.scope.name}}
      +
      $routeParams = {{main.$routeParams}}
      +
      +
      + + +
      + controller: {{book.name}}
      + Book Id: {{book.params.bookId}}
      +
      +
      + + +
      + controller: {{chapter.name}}
      + Book Id: {{chapter.params.bookId}}
      + Chapter Id: {{chapter.params.chapterId}} +
      +
      + + + .view-animate-container { + position:relative; + height:100px!important; + position:relative; + background:white; + border:1px solid black; + height:40px; + overflow:hidden; + } + + .view-animate { + padding:10px; + } + + .view-animate.ng-enter, .view-animate.ng-leave { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; + + display:block; + width:100%; + border-left:1px solid black; + + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + padding:10px; + } + + .view-animate.ng-enter { + left:100%; + } + .view-animate.ng-enter.ng-enter-active { + left:0; + } + .view-animate.ng-leave.ng-leave-active { + left:-100%; + } + + + + angular.module('ngViewExample', ['ngRoute', 'ngAnimate'], + function($routeProvider, $locationProvider) { + $routeProvider.when('/Book/:bookId', { + templateUrl: 'book.html', + controller: BookCntl, + controllerAs: 'book' + }); + $routeProvider.when('/Book/:bookId/ch/:chapterId', { + templateUrl: 'chapter.html', + controller: ChapterCntl, + controllerAs: 'chapter' + }); + + // configure html5 to get links working on jsfiddle + $locationProvider.html5Mode(true); + }); + + function MainCntl($route, $routeParams, $location) { + this.$route = $route; + this.$location = $location; + this.$routeParams = $routeParams; + } + + function BookCntl($routeParams) { + this.name = "BookCntl"; + this.params = $routeParams; + } + + function ChapterCntl($routeParams) { + this.name = "ChapterCntl"; + this.params = $routeParams; + } + + + + it('should load and compile correct template', function() { + element('a:contains("Moby: Ch1")').click(); + var content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: ChapterCntl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); + + element('a:contains("Scarlet")').click(); + content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: BookCntl/); + expect(content).toMatch(/Book Id\: Scarlet/); + }); + +
      + */ + + +/** + * @ngdoc event + * @name ngRoute.directive:ngView#$viewContentLoaded + * @eventOf ngRoute.directive:ngView + * @eventType emit on the current ngView scope + * @description + * Emitted every time the ngView content is reloaded. + */ +ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; +function ngViewFactory( $route, $anchorScroll, $animate) { + return { + restrict: 'ECA', + terminal: true, + priority: 400, + transclude: 'element', + link: function(scope, $element, attr, ctrl, $transclude) { + var currentScope, + currentElement, + autoScrollExp = attr.autoscroll, + onloadExp = attr.onload || ''; + + scope.$on('$routeChangeSuccess', update); + update(); + + function cleanupLastView() { + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + if(currentElement) { + $animate.leave(currentElement); + currentElement = null; + } + } + + function update() { + var locals = $route.current && $route.current.locals, + template = locals && locals.$template; + + if (angular.isDefined(template)) { + var newScope = scope.$new(); + var current = $route.current; + + // Note: This will also link all children of ng-view that were contained in the original + // html. If that content contains controllers, ... they could pollute/change the scope. + // However, using ng-view on an element with additional content does not make sense... + // Note: We can't remove them in the cloneAttchFn of $transclude as that + // function is called before linking the content, which would apply child + // directives to non existing elements. + var clone = $transclude(newScope, function(clone) { + $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { + if (angular.isDefined(autoScrollExp) + && (!autoScrollExp || scope.$eval(autoScrollExp))) { + $anchorScroll(); + } + }); + cleanupLastView(); + }); + + currentElement = clone; + currentScope = current.scope = newScope; + currentScope.$emit('$viewContentLoaded'); + currentScope.$eval(onloadExp); + } else { + cleanupLastView(); + } + } + } + }; +} + +// This directive is called during the $transclude call of the first `ngView` directive. +// It will replace and compile the content of the element with the loaded template. +// We need this directive so that the element content is already filled when +// the link function of another directive on the same element as ngView +// is called. +ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; +function ngViewFillContentFactory($compile, $controller, $route) { + return { + restrict: 'ECA', + priority: -400, + link: function(scope, $element) { + var current = $route.current, + locals = current.locals; + + $element.html(locals.$template); + + var link = $compile($element.contents()); + + if (current.controller) { + locals.$scope = scope; + var controller = $controller(current.controller, locals); + if (current.controllerAs) { + scope[current.controllerAs] = controller; + } + $element.data('$ngControllerController', controller); + $element.children().data('$ngControllerController', controller); + } + + link(scope); + } + }; +} + + +})(window, window.angular); diff --git a/src/main/webapp/subscriber/ui/js/angular/angular.js b/src/main/webapp/subscriber/ui/js/angular/angular.js new file mode 100644 index 0000000..a6a46cd --- /dev/null +++ b/src/main/webapp/subscriber/ui/js/angular/angular.js @@ -0,0 +1,20560 @@ +/** + * @license AngularJS v1.2.9 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, document, undefined) {'use strict'; + +/** + * @description + * + * This object provides a utility for producing rich Error messages within + * Angular. It can be called as follows: + * + * var exampleMinErr = minErr('example'); + * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); + * + * The above creates an instance of minErr in the example namespace. The + * resulting error will have a namespaced error code of example.one. The + * resulting error will replace {0} with the value of foo, and {1} with the + * value of bar. The object is not restricted in the number of arguments it can + * take. + * + * If fewer arguments are specified than necessary for interpolation, the extra + * interpolation markers will be preserved in the final string. + * + * Since data will be parsed statically during a build step, some restrictions + * are applied with respect to how minErr instances are created and called. + * Instances should have names of the form namespaceMinErr for a minErr created + * using minErr('namespace') . Error codes, namespaces and template strings + * should all be static strings, not variables or general expressions. + * + * @param {string} module The namespace to use for the new minErr instance. + * @returns {function(string, string, ...): Error} instance + */ + +function minErr(module) { + return function () { + var code = arguments[0], + prefix = '[' + (module ? module + ':' : '') + code + '] ', + template = arguments[1], + templateArgs = arguments, + stringify = function (obj) { + if (typeof obj === 'function') { + return obj.toString().replace(/ \{[\s\S]*$/, ''); + } else if (typeof obj === 'undefined') { + return 'undefined'; + } else if (typeof obj !== 'string') { + return JSON.stringify(obj); + } + return obj; + }, + message, i; + + message = prefix + template.replace(/\{\d+\}/g, function (match) { + var index = +match.slice(1, -1), arg; + + if (index + 2 < templateArgs.length) { + arg = templateArgs[index + 2]; + if (typeof arg === 'function') { + return arg.toString().replace(/ ?\{[\s\S]*$/, ''); + } else if (typeof arg === 'undefined') { + return 'undefined'; + } else if (typeof arg !== 'string') { + return toJson(arg); + } + return arg; + } + return match; + }); + + message = message + '\nhttp://errors.angularjs.org/1.2.9/' + + (module ? module + '/' : '') + code; + for (i = 2; i < arguments.length; i++) { + message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + + encodeURIComponent(stringify(arguments[i])); + } + + return new Error(message); + }; +} + +/* We need to tell jshint what variables are being exported */ +/* global + -angular, + -msie, + -jqLite, + -jQuery, + -slice, + -push, + -toString, + -ngMinErr, + -_angular, + -angularModule, + -nodeName_, + -uid, + + -lowercase, + -uppercase, + -manualLowercase, + -manualUppercase, + -nodeName_, + -isArrayLike, + -forEach, + -sortedKeys, + -forEachSorted, + -reverseParams, + -nextUid, + -setHashKey, + -extend, + -int, + -inherit, + -noop, + -identity, + -valueFn, + -isUndefined, + -isDefined, + -isObject, + -isString, + -isNumber, + -isDate, + -isArray, + -isFunction, + -isRegExp, + -isWindow, + -isScope, + -isFile, + -isBoolean, + -trim, + -isElement, + -makeMap, + -map, + -size, + -includes, + -indexOf, + -arrayRemove, + -isLeafNode, + -copy, + -shallowCopy, + -equals, + -csp, + -concat, + -sliceArgs, + -bind, + -toJsonReplacer, + -toJson, + -fromJson, + -toBoolean, + -startingTag, + -tryDecodeURIComponent, + -parseKeyValue, + -toKeyValue, + -encodeUriSegment, + -encodeUriQuery, + -angularInit, + -bootstrap, + -snake_case, + -bindJQuery, + -assertArg, + -assertArgFn, + -assertNotHasOwnProperty, + -getter, + -getBlockElements, + +*/ + +//////////////////////////////////// + +/** + * @ngdoc function + * @name angular.lowercase + * @function + * + * @description Converts the specified string to lowercase. + * @param {string} string String to be converted to lowercase. + * @returns {string} Lowercased string. + */ +var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; + + +/** + * @ngdoc function + * @name angular.uppercase + * @function + * + * @description Converts the specified string to uppercase. + * @param {string} string String to be converted to uppercase. + * @returns {string} Uppercased string. + */ +var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; + + +var manualLowercase = function(s) { + /* jshint bitwise: false */ + return isString(s) + ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) + : s; +}; +var manualUppercase = function(s) { + /* jshint bitwise: false */ + return isString(s) + ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) + : s; +}; + + +// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish +// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods +// with correct but slower alternatives. +if ('i' !== 'I'.toLowerCase()) { + lowercase = manualLowercase; + uppercase = manualUppercase; +} + + +var /** holds major version number for IE or NaN for real browsers */ + msie, + jqLite, // delay binding since jQuery could be loaded after us. + jQuery, // delay binding + slice = [].slice, + push = [].push, + toString = Object.prototype.toString, + ngMinErr = minErr('ng'), + + + _angular = window.angular, + /** @name angular */ + angular = window.angular || (window.angular = {}), + angularModule, + nodeName_, + uid = ['0', '0', '0']; + +/** + * IE 11 changed the format of the UserAgent string. + * See http://msdn.microsoft.com/en-us/library/ms537503.aspx + */ +msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); +if (isNaN(msie)) { + msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); +} + + +/** + * @private + * @param {*} obj + * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, + * String ...) + */ +function isArrayLike(obj) { + if (obj == null || isWindow(obj)) { + return false; + } + + var length = obj.length; + + if (obj.nodeType === 1 && length) { + return true; + } + + return isString(obj) || isArray(obj) || length === 0 || + typeof length === 'number' && length > 0 && (length - 1) in obj; +} + +/** + * @ngdoc function + * @name angular.forEach + * @function + * + * @description + * Invokes the `iterator` function once for each item in `obj` collection, which can be either an + * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` + * is the value of an object property or an array element and `key` is the object property key or + * array element index. Specifying a `context` for the function is optional. + * + * It is worth nothing that `.forEach` does not iterate over inherited properties because it filters + * using the `hasOwnProperty` method. + * +
      +     var values = {name: 'misko', gender: 'male'};
      +     var log = [];
      +     angular.forEach(values, function(value, key){
      +       this.push(key + ': ' + value);
      +     }, log);
      +     expect(log).toEqual(['name: misko', 'gender:male']);
      +   
      + * + * @param {Object|Array} obj Object to iterate over. + * @param {Function} iterator Iterator function. + * @param {Object=} context Object to become context (`this`) for the iterator function. + * @returns {Object|Array} Reference to `obj`. + */ +function forEach(obj, iterator, context) { + var key; + if (obj) { + if (isFunction(obj)){ + for (key in obj) { + // Need to check if hasOwnProperty exists, + // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function + if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { + iterator.call(context, obj[key], key); + } + } + } else if (obj.forEach && obj.forEach !== forEach) { + obj.forEach(iterator, context); + } else if (isArrayLike(obj)) { + for (key = 0; key < obj.length; key++) + iterator.call(context, obj[key], key); + } else { + for (key in obj) { + if (obj.hasOwnProperty(key)) { + iterator.call(context, obj[key], key); + } + } + } + } + return obj; +} + +function sortedKeys(obj) { + var keys = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + keys.push(key); + } + } + return keys.sort(); +} + +function forEachSorted(obj, iterator, context) { + var keys = sortedKeys(obj); + for ( var i = 0; i < keys.length; i++) { + iterator.call(context, obj[keys[i]], keys[i]); + } + return keys; +} + + +/** + * when using forEach the params are value, key, but it is often useful to have key, value. + * @param {function(string, *)} iteratorFn + * @returns {function(*, string)} + */ +function reverseParams(iteratorFn) { + return function(value, key) { iteratorFn(key, value); }; +} + +/** + * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric + * characters such as '012ABC'. The reason why we are not using simply a number counter is that + * the number string gets longer over time, and it can also overflow, where as the nextId + * will grow much slower, it is a string, and it will never overflow. + * + * @returns an unique alpha-numeric string + */ +function nextUid() { + var index = uid.length; + var digit; + + while(index) { + index--; + digit = uid[index].charCodeAt(0); + if (digit == 57 /*'9'*/) { + uid[index] = 'A'; + return uid.join(''); + } + if (digit == 90 /*'Z'*/) { + uid[index] = '0'; + } else { + uid[index] = String.fromCharCode(digit + 1); + return uid.join(''); + } + } + uid.unshift('0'); + return uid.join(''); +} + + +/** + * Set or clear the hashkey for an object. + * @param obj object + * @param h the hashkey (!truthy to delete the hashkey) + */ +function setHashKey(obj, h) { + if (h) { + obj.$$hashKey = h; + } + else { + delete obj.$$hashKey; + } +} + +/** + * @ngdoc function + * @name angular.extend + * @function + * + * @description + * Extends the destination object `dst` by copying all of the properties from the `src` object(s) + * to `dst`. You can specify multiple `src` objects. + * + * @param {Object} dst Destination object. + * @param {...Object} src Source object(s). + * @returns {Object} Reference to `dst`. + */ +function extend(dst) { + var h = dst.$$hashKey; + forEach(arguments, function(obj){ + if (obj !== dst) { + forEach(obj, function(value, key){ + dst[key] = value; + }); + } + }); + + setHashKey(dst,h); + return dst; +} + +function int(str) { + return parseInt(str, 10); +} + + +function inherit(parent, extra) { + return extend(new (extend(function() {}, {prototype:parent}))(), extra); +} + +/** + * @ngdoc function + * @name angular.noop + * @function + * + * @description + * A function that performs no operations. This function can be useful when writing code in the + * functional style. +
      +     function foo(callback) {
      +       var result = calculateResult();
      +       (callback || angular.noop)(result);
      +     }
      +   
      + */ +function noop() {} +noop.$inject = []; + + +/** + * @ngdoc function + * @name angular.identity + * @function + * + * @description + * A function that returns its first argument. This function is useful when writing code in the + * functional style. + * +
      +     function transformer(transformationFn, value) {
      +       return (transformationFn || angular.identity)(value);
      +     };
      +   
      + */ +function identity($) {return $;} +identity.$inject = []; + + +function valueFn(value) {return function() {return value;};} + +/** + * @ngdoc function + * @name angular.isUndefined + * @function + * + * @description + * Determines if a reference is undefined. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is undefined. + */ +function isUndefined(value){return typeof value === 'undefined';} + + +/** + * @ngdoc function + * @name angular.isDefined + * @function + * + * @description + * Determines if a reference is defined. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is defined. + */ +function isDefined(value){return typeof value !== 'undefined';} + + +/** + * @ngdoc function + * @name angular.isObject + * @function + * + * @description + * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not + * considered to be objects. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is an `Object` but not `null`. + */ +function isObject(value){return value != null && typeof value === 'object';} + + +/** + * @ngdoc function + * @name angular.isString + * @function + * + * @description + * Determines if a reference is a `String`. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `String`. + */ +function isString(value){return typeof value === 'string';} + + +/** + * @ngdoc function + * @name angular.isNumber + * @function + * + * @description + * Determines if a reference is a `Number`. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `Number`. + */ +function isNumber(value){return typeof value === 'number';} + + +/** + * @ngdoc function + * @name angular.isDate + * @function + * + * @description + * Determines if a value is a date. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `Date`. + */ +function isDate(value){ + return toString.call(value) === '[object Date]'; +} + + +/** + * @ngdoc function + * @name angular.isArray + * @function + * + * @description + * Determines if a reference is an `Array`. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is an `Array`. + */ +function isArray(value) { + return toString.call(value) === '[object Array]'; +} + + +/** + * @ngdoc function + * @name angular.isFunction + * @function + * + * @description + * Determines if a reference is a `Function`. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `Function`. + */ +function isFunction(value){return typeof value === 'function';} + + +/** + * Determines if a value is a regular expression object. + * + * @private + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `RegExp`. + */ +function isRegExp(value) { + return toString.call(value) === '[object RegExp]'; +} + + +/** + * Checks if `obj` is a window object. + * + * @private + * @param {*} obj Object to check + * @returns {boolean} True if `obj` is a window obj. + */ +function isWindow(obj) { + return obj && obj.document && obj.location && obj.alert && obj.setInterval; +} + + +function isScope(obj) { + return obj && obj.$evalAsync && obj.$watch; +} + + +function isFile(obj) { + return toString.call(obj) === '[object File]'; +} + + +function isBoolean(value) { + return typeof value === 'boolean'; +} + + +var trim = (function() { + // native trim is way faster: http://jsperf.com/angular-trim-test + // but IE doesn't have it... :-( + // TODO: we should move this into IE/ES5 polyfill + if (!String.prototype.trim) { + return function(value) { + return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value; + }; + } + return function(value) { + return isString(value) ? value.trim() : value; + }; +})(); + + +/** + * @ngdoc function + * @name angular.isElement + * @function + * + * @description + * Determines if a reference is a DOM element (or wrapped jQuery element). + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). + */ +function isElement(node) { + return !!(node && + (node.nodeName // we are a direct element + || (node.on && node.find))); // we have an on and find method part of jQuery API +} + +/** + * @param str 'key1,key2,...' + * @returns {object} in the form of {key1:true, key2:true, ...} + */ +function makeMap(str){ + var obj = {}, items = str.split(","), i; + for ( i = 0; i < items.length; i++ ) + obj[ items[i] ] = true; + return obj; +} + + +if (msie < 9) { + nodeName_ = function(element) { + element = element.nodeName ? element : element[0]; + return (element.scopeName && element.scopeName != 'HTML') + ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; + }; +} else { + nodeName_ = function(element) { + return element.nodeName ? element.nodeName : element[0].nodeName; + }; +} + + +function map(obj, iterator, context) { + var results = []; + forEach(obj, function(value, index, list) { + results.push(iterator.call(context, value, index, list)); + }); + return results; +} + + +/** + * @description + * Determines the number of elements in an array, the number of properties an object has, or + * the length of a string. + * + * Note: This function is used to augment the Object type in Angular expressions. See + * {@link angular.Object} for more information about Angular arrays. + * + * @param {Object|Array|string} obj Object, array, or string to inspect. + * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object + * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. + */ +function size(obj, ownPropsOnly) { + var count = 0, key; + + if (isArray(obj) || isString(obj)) { + return obj.length; + } else if (isObject(obj)){ + for (key in obj) + if (!ownPropsOnly || obj.hasOwnProperty(key)) + count++; + } + + return count; +} + + +function includes(array, obj) { + return indexOf(array, obj) != -1; +} + +function indexOf(array, obj) { + if (array.indexOf) return array.indexOf(obj); + + for (var i = 0; i < array.length; i++) { + if (obj === array[i]) return i; + } + return -1; +} + +function arrayRemove(array, value) { + var index = indexOf(array, value); + if (index >=0) + array.splice(index, 1); + return value; +} + +function isLeafNode (node) { + if (node) { + switch (node.nodeName) { + case "OPTION": + case "PRE": + case "TITLE": + return true; + } + } + return false; +} + +/** + * @ngdoc function + * @name angular.copy + * @function + * + * @description + * Creates a deep copy of `source`, which should be an object or an array. + * + * * If no destination is supplied, a copy of the object or array is created. + * * If a destination is provided, all of its elements (for array) or properties (for objects) + * are deleted and then all elements/properties from the source are copied to it. + * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. + * * If `source` is identical to 'destination' an exception will be thrown. + * + * @param {*} source The source that will be used to make a copy. + * Can be any type, including primitives, `null`, and `undefined`. + * @param {(Object|Array)=} destination Destination into which the source is copied. If + * provided, must be of the same type as `source`. + * @returns {*} The copy or updated `destination`, if `destination` was specified. + * + * @example + + +
      +
      + Name:
      + E-mail:
      + Gender: male + female
      + + +
      +
      form = {{user | json}}
      +
      master = {{master | json}}
      +
      + + +
      +
      + */ +function copy(source, destination){ + if (isWindow(source) || isScope(source)) { + throw ngMinErr('cpws', + "Can't copy! Making copies of Window or Scope instances is not supported."); + } + + if (!destination) { + destination = source; + if (source) { + if (isArray(source)) { + destination = copy(source, []); + } else if (isDate(source)) { + destination = new Date(source.getTime()); + } else if (isRegExp(source)) { + destination = new RegExp(source.source); + } else if (isObject(source)) { + destination = copy(source, {}); + } + } + } else { + if (source === destination) throw ngMinErr('cpi', + "Can't copy! Source and destination are identical."); + if (isArray(source)) { + destination.length = 0; + for ( var i = 0; i < source.length; i++) { + destination.push(copy(source[i])); + } + } else { + var h = destination.$$hashKey; + forEach(destination, function(value, key){ + delete destination[key]; + }); + for ( var key in source) { + destination[key] = copy(source[key]); + } + setHashKey(destination,h); + } + } + return destination; +} + +/** + * Create a shallow copy of an object + */ +function shallowCopy(src, dst) { + dst = dst || {}; + + for(var key in src) { + // shallowCopy is only ever called by $compile nodeLinkFn, which has control over src + // so we don't need to worry about using our custom hasOwnProperty here + if (src.hasOwnProperty(key) && key.charAt(0) !== '$' && key.charAt(1) !== '$') { + dst[key] = src[key]; + } + } + + return dst; +} + + +/** + * @ngdoc function + * @name angular.equals + * @function + * + * @description + * Determines if two objects or two values are equivalent. Supports value types, regular + * expressions, arrays and objects. + * + * Two objects or values are considered equivalent if at least one of the following is true: + * + * * Both objects or values pass `===` comparison. + * * Both objects or values are of the same type and all of their properties are equal by + * comparing them with `angular.equals`. + * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) + * * Both values represent the same regular expression (In JavasScript, + * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual + * representation matches). + * + * During a property comparison, properties of `function` type and properties with names + * that begin with `$` are ignored. + * + * Scope and DOMWindow objects are being compared only by identify (`===`). + * + * @param {*} o1 Object or value to compare. + * @param {*} o2 Object or value to compare. + * @returns {boolean} True if arguments are equal. + */ +function equals(o1, o2) { + if (o1 === o2) return true; + if (o1 === null || o2 === null) return false; + if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN + var t1 = typeof o1, t2 = typeof o2, length, key, keySet; + if (t1 == t2) { + if (t1 == 'object') { + if (isArray(o1)) { + if (!isArray(o2)) return false; + if ((length = o1.length) == o2.length) { + for(key=0; key 2 ? sliceArgs(arguments, 2) : []; + if (isFunction(fn) && !(fn instanceof RegExp)) { + return curryArgs.length + ? function() { + return arguments.length + ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) + : fn.apply(self, curryArgs); + } + : function() { + return arguments.length + ? fn.apply(self, arguments) + : fn.call(self); + }; + } else { + // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) + return fn; + } +} + + +function toJsonReplacer(key, value) { + var val = value; + + if (typeof key === 'string' && key.charAt(0) === '$') { + val = undefined; + } else if (isWindow(value)) { + val = '$WINDOW'; + } else if (value && document === value) { + val = '$DOCUMENT'; + } else if (isScope(value)) { + val = '$SCOPE'; + } + + return val; +} + + +/** + * @ngdoc function + * @name angular.toJson + * @function + * + * @description + * Serializes input into a JSON-formatted string. Properties with leading $ characters will be + * stripped since angular uses this notation internally. + * + * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. + * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace. + * @returns {string|undefined} JSON-ified string representing `obj`. + */ +function toJson(obj, pretty) { + if (typeof obj === 'undefined') return undefined; + return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); +} + + +/** + * @ngdoc function + * @name angular.fromJson + * @function + * + * @description + * Deserializes a JSON string. + * + * @param {string} json JSON string to deserialize. + * @returns {Object|Array|Date|string|number} Deserialized thingy. + */ +function fromJson(json) { + return isString(json) + ? JSON.parse(json) + : json; +} + + +function toBoolean(value) { + if (typeof value === 'function') { + value = true; + } else if (value && value.length !== 0) { + var v = lowercase("" + value); + value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); + } else { + value = false; + } + return value; +} + +/** + * @returns {string} Returns the string representation of the element. + */ +function startingTag(element) { + element = jqLite(element).clone(); + try { + // turns out IE does not let you set .html() on elements which + // are not allowed to have children. So we just ignore it. + element.empty(); + } catch(e) {} + // As Per DOM Standards + var TEXT_NODE = 3; + var elemHtml = jqLite('
      ').append(element).html(); + try { + return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : + elemHtml. + match(/^(<[^>]+>)/)[1]. + replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); + } catch(e) { + return lowercase(elemHtml); + } + +} + + +///////////////////////////////////////////////// + +/** + * Tries to decode the URI component without throwing an exception. + * + * @private + * @param str value potential URI component to check. + * @returns {boolean} True if `value` can be decoded + * with the decodeURIComponent function. + */ +function tryDecodeURIComponent(value) { + try { + return decodeURIComponent(value); + } catch(e) { + // Ignore any invalid uri component + } +} + + +/** + * Parses an escaped url query string into key-value pairs. + * @returns Object.<(string|boolean)> + */ +function parseKeyValue(/**string*/keyValue) { + var obj = {}, key_value, key; + forEach((keyValue || "").split('&'), function(keyValue){ + if ( keyValue ) { + key_value = keyValue.split('='); + key = tryDecodeURIComponent(key_value[0]); + if ( isDefined(key) ) { + var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; + if (!obj[key]) { + obj[key] = val; + } else if(isArray(obj[key])) { + obj[key].push(val); + } else { + obj[key] = [obj[key],val]; + } + } + } + }); + return obj; +} + +function toKeyValue(obj) { + var parts = []; + forEach(obj, function(value, key) { + if (isArray(value)) { + forEach(value, function(arrayValue) { + parts.push(encodeUriQuery(key, true) + + (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); + }); + } else { + parts.push(encodeUriQuery(key, true) + + (value === true ? '' : '=' + encodeUriQuery(value, true))); + } + }); + return parts.length ? parts.join('&') : ''; +} + + +/** + * We need our custom method because encodeURIComponent is too aggressive and doesn't follow + * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path + * segments: + * segment = *pchar + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * pct-encoded = "%" HEXDIG HEXDIG + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ +function encodeUriSegment(val) { + return encodeUriQuery(val, true). + replace(/%26/gi, '&'). + replace(/%3D/gi, '='). + replace(/%2B/gi, '+'); +} + + +/** + * This method is intended for encoding *key* or *value* parts of query component. We need a custom + * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be + * encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ +function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); +} + + +/** + * @ngdoc directive + * @name ng.directive:ngApp + * + * @element ANY + * @param {angular.Module} ngApp an optional application + * {@link angular.module module} name to load. + * + * @description + * + * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive + * designates the **root element** of the application and is typically placed near the root element + * of the page - e.g. on the `` or `` tags. + * + * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` + * found in the document will be used to define the root element to auto-bootstrap as an + * application. To run multiple applications in an HTML document you must manually bootstrap them using + * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. + * + * You can specify an **AngularJS module** to be used as the root module for the application. This + * module will be loaded into the {@link AUTO.$injector} when the application is bootstrapped and + * should contain the application code needed or have dependencies on other modules that will + * contain the code. See {@link angular.module} for more information. + * + * In the example below if the `ngApp` directive were not placed on the `html` element then the + * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` + * would not be resolved to `3`. + * + * `ngApp` is the easiest, and most common, way to bootstrap an application. + * + + +
      + I can add: {{a}} + {{b}} = {{ a+b }} + + + angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { + $scope.a = 1; + $scope.b = 2; + }); + + + * + */ +function angularInit(element, bootstrap) { + var elements = [element], + appElement, + module, + names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], + NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; + + function append(element) { + element && elements.push(element); + } + + forEach(names, function(name) { + names[name] = true; + append(document.getElementById(name)); + name = name.replace(':', '\\:'); + if (element.querySelectorAll) { + forEach(element.querySelectorAll('.' + name), append); + forEach(element.querySelectorAll('.' + name + '\\:'), append); + forEach(element.querySelectorAll('[' + name + ']'), append); + } + }); + + forEach(elements, function(element) { + if (!appElement) { + var className = ' ' + element.className + ' '; + var match = NG_APP_CLASS_REGEXP.exec(className); + if (match) { + appElement = element; + module = (match[2] || '').replace(/\s+/g, ','); + } else { + forEach(element.attributes, function(attr) { + if (!appElement && names[attr.name]) { + appElement = element; + module = attr.value; + } + }); + } + } + }); + if (appElement) { + bootstrap(appElement, module ? [module] : []); + } +} + +/** + * @ngdoc function + * @name angular.bootstrap + * @description + * Use this function to manually start up angular application. + * + * See: {@link guide/bootstrap Bootstrap} + * + * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually. + * They must use {@link api/ng.directive:ngApp ngApp}. + * + * @param {Element} element DOM element which is the root of angular application. + * @param {Array=} modules an array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * See: {@link angular.module modules} + * @returns {AUTO.$injector} Returns the newly created injector for this app. + */ +function bootstrap(element, modules) { + var doBootstrap = function() { + element = jqLite(element); + + if (element.injector()) { + var tag = (element[0] === document) ? 'document' : startingTag(element); + throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag); + } + + modules = modules || []; + modules.unshift(['$provide', function($provide) { + $provide.value('$rootElement', element); + }]); + modules.unshift('ng'); + var injector = createInjector(modules); + injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', + function(scope, element, compile, injector, animate) { + scope.$apply(function() { + element.data('$injector', injector); + compile(element)(scope); + }); + }] + ); + return injector; + }; + + var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; + + if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { + return doBootstrap(); + } + + window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); + angular.resumeBootstrap = function(extraModules) { + forEach(extraModules, function(module) { + modules.push(module); + }); + doBootstrap(); + }; +} + +var SNAKE_CASE_REGEXP = /[A-Z]/g; +function snake_case(name, separator){ + separator = separator || '_'; + return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); +} + +function bindJQuery() { + // bind to jQuery if present; + jQuery = window.jQuery; + // reset to jQuery or default to us. + if (jQuery) { + jqLite = jQuery; + extend(jQuery.fn, { + scope: JQLitePrototype.scope, + isolateScope: JQLitePrototype.isolateScope, + controller: JQLitePrototype.controller, + injector: JQLitePrototype.injector, + inheritedData: JQLitePrototype.inheritedData + }); + // Method signature: + // jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) + jqLitePatchJQueryRemove('remove', true, true, false); + jqLitePatchJQueryRemove('empty', false, false, false); + jqLitePatchJQueryRemove('html', false, false, true); + } else { + jqLite = JQLite; + } + angular.element = jqLite; +} + +/** + * throw error if the argument is falsy. + */ +function assertArg(arg, name, reason) { + if (!arg) { + throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); + } + return arg; +} + +function assertArgFn(arg, name, acceptArrayAnnotation) { + if (acceptArrayAnnotation && isArray(arg)) { + arg = arg[arg.length - 1]; + } + + assertArg(isFunction(arg), name, 'not a function, got ' + + (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg)); + return arg; +} + +/** + * throw error if the name given is hasOwnProperty + * @param {String} name the name to test + * @param {String} context the context in which the name is used, such as module or directive + */ +function assertNotHasOwnProperty(name, context) { + if (name === 'hasOwnProperty') { + throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); + } +} + +/** + * Return the value accessible from the object by path. Any undefined traversals are ignored + * @param {Object} obj starting object + * @param {string} path path to traverse + * @param {boolean=true} bindFnToScope + * @returns value as accessible by path + */ +//TODO(misko): this function needs to be removed +function getter(obj, path, bindFnToScope) { + if (!path) return obj; + var keys = path.split('.'); + var key; + var lastInstance = obj; + var len = keys.length; + + for (var i = 0; i < len; i++) { + key = keys[i]; + if (obj) { + obj = (lastInstance = obj)[key]; + } + } + if (!bindFnToScope && isFunction(obj)) { + return bind(lastInstance, obj); + } + return obj; +} + +/** + * Return the DOM siblings between the first and last node in the given array. + * @param {Array} array like object + * @returns jQlite object containing the elements + */ +function getBlockElements(nodes) { + var startNode = nodes[0], + endNode = nodes[nodes.length - 1]; + if (startNode === endNode) { + return jqLite(startNode); + } + + var element = startNode; + var elements = [element]; + + do { + element = element.nextSibling; + if (!element) break; + elements.push(element); + } while (element !== endNode); + + return jqLite(elements); +} + +/** + * @ngdoc interface + * @name angular.Module + * @description + * + * Interface for configuring angular {@link angular.module modules}. + */ + +function setupModuleLoader(window) { + + var $injectorMinErr = minErr('$injector'); + var ngMinErr = minErr('ng'); + + function ensure(obj, name, factory) { + return obj[name] || (obj[name] = factory()); + } + + var angular = ensure(window, 'angular', Object); + + // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap + angular.$$minErr = angular.$$minErr || minErr; + + return ensure(angular, 'module', function() { + /** @type {Object.} */ + var modules = {}; + + /** + * @ngdoc function + * @name angular.module + * @description + * + * The `angular.module` is a global place for creating, registering and retrieving Angular + * modules. + * All modules (angular core or 3rd party) that should be available to an application must be + * registered using this mechanism. + * + * When passed two or more arguments, a new module is created. If passed only one argument, an + * existing module (the name passed as the first argument to `module`) is retrieved. + * + * + * # Module + * + * A module is a collection of services, directives, filters, and configuration information. + * `angular.module` is used to configure the {@link AUTO.$injector $injector}. + * + *
      +     * // Create a new module
      +     * var myModule = angular.module('myModule', []);
      +     *
      +     * // register a new service
      +     * myModule.value('appName', 'MyCoolApp');
      +     *
      +     * // configure existing services inside initialization blocks.
      +     * myModule.config(function($locationProvider) {
      +     *   // Configure existing providers
      +     *   $locationProvider.hashPrefix('!');
      +     * });
      +     * 
      + * + * Then you can create an injector and load your modules like this: + * + *
      +     * var injector = angular.injector(['ng', 'MyModule'])
      +     * 
      + * + * However it's more likely that you'll just use + * {@link ng.directive:ngApp ngApp} or + * {@link angular.bootstrap} to simplify this process for you. + * + * @param {!string} name The name of the module to create or retrieve. + * @param {Array.=} requires If specified then new module is being created. If + * unspecified then the the module is being retrieved for further configuration. + * @param {Function} configFn Optional configuration function for the module. Same as + * {@link angular.Module#methods_config Module#config()}. + * @returns {module} new module with the {@link angular.Module} api. + */ + return function module(name, requires, configFn) { + var assertNotHasOwnProperty = function(name, context) { + if (name === 'hasOwnProperty') { + throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); + } + }; + + assertNotHasOwnProperty(name, 'module'); + if (requires && modules.hasOwnProperty(name)) { + modules[name] = null; + } + return ensure(modules, name, function() { + if (!requires) { + throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + + "the module name or forgot to load it. If registering a module ensure that you " + + "specify the dependencies as the second argument.", name); + } + + /** @type {!Array.>} */ + var invokeQueue = []; + + /** @type {!Array.} */ + var runBlocks = []; + + var config = invokeLater('$injector', 'invoke'); + + /** @type {angular.Module} */ + var moduleInstance = { + // Private state + _invokeQueue: invokeQueue, + _runBlocks: runBlocks, + + /** + * @ngdoc property + * @name angular.Module#requires + * @propertyOf angular.Module + * @returns {Array.} List of module names which must be loaded before this module. + * @description + * Holds the list of modules which the injector will load before the current module is + * loaded. + */ + requires: requires, + + /** + * @ngdoc property + * @name angular.Module#name + * @propertyOf angular.Module + * @returns {string} Name of the module. + * @description + */ + name: name, + + + /** + * @ngdoc method + * @name angular.Module#provider + * @methodOf angular.Module + * @param {string} name service name + * @param {Function} providerType Construction function for creating new instance of the + * service. + * @description + * See {@link AUTO.$provide#provider $provide.provider()}. + */ + provider: invokeLater('$provide', 'provider'), + + /** + * @ngdoc method + * @name angular.Module#factory + * @methodOf angular.Module + * @param {string} name service name + * @param {Function} providerFunction Function for creating new instance of the service. + * @description + * See {@link AUTO.$provide#factory $provide.factory()}. + */ + factory: invokeLater('$provide', 'factory'), + + /** + * @ngdoc method + * @name angular.Module#service + * @methodOf angular.Module + * @param {string} name service name + * @param {Function} constructor A constructor function that will be instantiated. + * @description + * See {@link AUTO.$provide#service $provide.service()}. + */ + service: invokeLater('$provide', 'service'), + + /** + * @ngdoc method + * @name angular.Module#value + * @methodOf angular.Module + * @param {string} name service name + * @param {*} object Service instance object. + * @description + * See {@link AUTO.$provide#value $provide.value()}. + */ + value: invokeLater('$provide', 'value'), + + /** + * @ngdoc method + * @name angular.Module#constant + * @methodOf angular.Module + * @param {string} name constant name + * @param {*} object Constant value. + * @description + * Because the constant are fixed, they get applied before other provide methods. + * See {@link AUTO.$provide#constant $provide.constant()}. + */ + constant: invokeLater('$provide', 'constant', 'unshift'), + + /** + * @ngdoc method + * @name angular.Module#animation + * @methodOf angular.Module + * @param {string} name animation name + * @param {Function} animationFactory Factory function for creating new instance of an + * animation. + * @description + * + * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. + * + * + * Defines an animation hook that can be later used with + * {@link ngAnimate.$animate $animate} service and directives that use this service. + * + *
      +           * module.animation('.animation-name', function($inject1, $inject2) {
      +           *   return {
      +           *     eventName : function(element, done) {
      +           *       //code to run the animation
      +           *       //once complete, then run done()
      +           *       return function cancellationFunction(element) {
      +           *         //code to cancel the animation
      +           *       }
      +           *     }
      +           *   }
      +           * })
      +           * 
      + * + * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and + * {@link ngAnimate ngAnimate module} for more information. + */ + animation: invokeLater('$animateProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#filter + * @methodOf angular.Module + * @param {string} name Filter name. + * @param {Function} filterFactory Factory function for creating new instance of filter. + * @description + * See {@link ng.$filterProvider#register $filterProvider.register()}. + */ + filter: invokeLater('$filterProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#controller + * @methodOf angular.Module + * @param {string|Object} name Controller name, or an object map of controllers where the + * keys are the names and the values are the constructors. + * @param {Function} constructor Controller constructor function. + * @description + * See {@link ng.$controllerProvider#register $controllerProvider.register()}. + */ + controller: invokeLater('$controllerProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#directive + * @methodOf angular.Module + * @param {string|Object} name Directive name, or an object map of directives where the + * keys are the names and the values are the factories. + * @param {Function} directiveFactory Factory function for creating new instance of + * directives. + * @description + * See {@link ng.$compileProvider#methods_directive $compileProvider.directive()}. + */ + directive: invokeLater('$compileProvider', 'directive'), + + /** + * @ngdoc method + * @name angular.Module#config + * @methodOf angular.Module + * @param {Function} configFn Execute this function on module load. Useful for service + * configuration. + * @description + * Use this method to register work which needs to be performed on module loading. + */ + config: config, + + /** + * @ngdoc method + * @name angular.Module#run + * @methodOf angular.Module + * @param {Function} initializationFn Execute this function after injector creation. + * Useful for application initialization. + * @description + * Use this method to register work which should be performed when the injector is done + * loading all modules. + */ + run: function(block) { + runBlocks.push(block); + return this; + } + }; + + if (configFn) { + config(configFn); + } + + return moduleInstance; + + /** + * @param {string} provider + * @param {string} method + * @param {String=} insertMethod + * @returns {angular.Module} + */ + function invokeLater(provider, method, insertMethod) { + return function() { + invokeQueue[insertMethod || 'push']([provider, method, arguments]); + return moduleInstance; + }; + } + }); + }; + }); + +} + +/* global + angularModule: true, + version: true, + + $LocaleProvider, + $CompileProvider, + + htmlAnchorDirective, + inputDirective, + inputDirective, + formDirective, + scriptDirective, + selectDirective, + styleDirective, + optionDirective, + ngBindDirective, + ngBindHtmlDirective, + ngBindTemplateDirective, + ngClassDirective, + ngClassEvenDirective, + ngClassOddDirective, + ngCspDirective, + ngCloakDirective, + ngControllerDirective, + ngFormDirective, + ngHideDirective, + ngIfDirective, + ngIncludeDirective, + ngIncludeFillContentDirective, + ngInitDirective, + ngNonBindableDirective, + ngPluralizeDirective, + ngRepeatDirective, + ngShowDirective, + ngStyleDirective, + ngSwitchDirective, + ngSwitchWhenDirective, + ngSwitchDefaultDirective, + ngOptionsDirective, + ngTranscludeDirective, + ngModelDirective, + ngListDirective, + ngChangeDirective, + requiredDirective, + requiredDirective, + ngValueDirective, + ngAttributeAliasDirectives, + ngEventDirectives, + + $AnchorScrollProvider, + $AnimateProvider, + $BrowserProvider, + $CacheFactoryProvider, + $ControllerProvider, + $DocumentProvider, + $ExceptionHandlerProvider, + $FilterProvider, + $InterpolateProvider, + $IntervalProvider, + $HttpProvider, + $HttpBackendProvider, + $LocationProvider, + $LogProvider, + $ParseProvider, + $RootScopeProvider, + $QProvider, + $$SanitizeUriProvider, + $SceProvider, + $SceDelegateProvider, + $SnifferProvider, + $TemplateCacheProvider, + $TimeoutProvider, + $WindowProvider +*/ + + +/** + * @ngdoc property + * @name angular.version + * @description + * An object that contains information about the current AngularJS version. This object has the + * following properties: + * + * - `full` – `{string}` – Full version string, such as "0.9.18". + * - `major` – `{number}` – Major version number, such as "0". + * - `minor` – `{number}` – Minor version number, such as "9". + * - `dot` – `{number}` – Dot version number, such as "18". + * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". + */ +var version = { + full: '1.2.9', // all of these placeholder strings will be replaced by grunt's + major: 1, // package task + minor: 2, + dot: 9, + codeName: 'enchanted-articulacy' +}; + + +function publishExternalAPI(angular){ + extend(angular, { + 'bootstrap': bootstrap, + 'copy': copy, + 'extend': extend, + 'equals': equals, + 'element': jqLite, + 'forEach': forEach, + 'injector': createInjector, + 'noop':noop, + 'bind':bind, + 'toJson': toJson, + 'fromJson': fromJson, + 'identity':identity, + 'isUndefined': isUndefined, + 'isDefined': isDefined, + 'isString': isString, + 'isFunction': isFunction, + 'isObject': isObject, + 'isNumber': isNumber, + 'isElement': isElement, + 'isArray': isArray, + 'version': version, + 'isDate': isDate, + 'lowercase': lowercase, + 'uppercase': uppercase, + 'callbacks': {counter: 0}, + '$$minErr': minErr, + '$$csp': csp + }); + + angularModule = setupModuleLoader(window); + try { + angularModule('ngLocale'); + } catch (e) { + angularModule('ngLocale', []).provider('$locale', $LocaleProvider); + } + + angularModule('ng', ['ngLocale'], ['$provide', + function ngModule($provide) { + // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. + $provide.provider({ + $$sanitizeUri: $$SanitizeUriProvider + }); + $provide.provider('$compile', $CompileProvider). + directive({ + a: htmlAnchorDirective, + input: inputDirective, + textarea: inputDirective, + form: formDirective, + script: scriptDirective, + select: selectDirective, + style: styleDirective, + option: optionDirective, + ngBind: ngBindDirective, + ngBindHtml: ngBindHtmlDirective, + ngBindTemplate: ngBindTemplateDirective, + ngClass: ngClassDirective, + ngClassEven: ngClassEvenDirective, + ngClassOdd: ngClassOddDirective, + ngCloak: ngCloakDirective, + ngController: ngControllerDirective, + ngForm: ngFormDirective, + ngHide: ngHideDirective, + ngIf: ngIfDirective, + ngInclude: ngIncludeDirective, + ngInit: ngInitDirective, + ngNonBindable: ngNonBindableDirective, + ngPluralize: ngPluralizeDirective, + ngRepeat: ngRepeatDirective, + ngShow: ngShowDirective, + ngStyle: ngStyleDirective, + ngSwitch: ngSwitchDirective, + ngSwitchWhen: ngSwitchWhenDirective, + ngSwitchDefault: ngSwitchDefaultDirective, + ngOptions: ngOptionsDirective, + ngTransclude: ngTranscludeDirective, + ngModel: ngModelDirective, + ngList: ngListDirective, + ngChange: ngChangeDirective, + required: requiredDirective, + ngRequired: requiredDirective, + ngValue: ngValueDirective + }). + directive({ + ngInclude: ngIncludeFillContentDirective + }). + directive(ngAttributeAliasDirectives). + directive(ngEventDirectives); + $provide.provider({ + $anchorScroll: $AnchorScrollProvider, + $animate: $AnimateProvider, + $browser: $BrowserProvider, + $cacheFactory: $CacheFactoryProvider, + $controller: $ControllerProvider, + $document: $DocumentProvider, + $exceptionHandler: $ExceptionHandlerProvider, + $filter: $FilterProvider, + $interpolate: $InterpolateProvider, + $interval: $IntervalProvider, + $http: $HttpProvider, + $httpBackend: $HttpBackendProvider, + $location: $LocationProvider, + $log: $LogProvider, + $parse: $ParseProvider, + $rootScope: $RootScopeProvider, + $q: $QProvider, + $sce: $SceProvider, + $sceDelegate: $SceDelegateProvider, + $sniffer: $SnifferProvider, + $templateCache: $TemplateCacheProvider, + $timeout: $TimeoutProvider, + $window: $WindowProvider + }); + } + ]); +} + +/* global + + -JQLitePrototype, + -addEventListenerFn, + -removeEventListenerFn, + -BOOLEAN_ATTR +*/ + +////////////////////////////////// +//JQLite +////////////////////////////////// + +/** + * @ngdoc function + * @name angular.element + * @function + * + * @description + * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. + * + * If jQuery is available, `angular.element` is an alias for the + * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` + * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." + * + *
      jqLite is a tiny, API-compatible subset of jQuery that allows + * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most + * commonly needed functionality with the goal of having a very small footprint.
      + * + * To use jQuery, simply load it before `DOMContentLoaded` event fired. + * + *
      **Note:** all element references in Angular are always wrapped with jQuery or + * jqLite; they are never raw DOM references.
      + * + * ## Angular's jqLite + * jqLite provides only the following jQuery methods: + * + * - [`addClass()`](http://api.jquery.com/addClass/) + * - [`after()`](http://api.jquery.com/after/) + * - [`append()`](http://api.jquery.com/append/) + * - [`attr()`](http://api.jquery.com/attr/) + * - [`bind()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData + * - [`children()`](http://api.jquery.com/children/) - Does not support selectors + * - [`clone()`](http://api.jquery.com/clone/) + * - [`contents()`](http://api.jquery.com/contents/) + * - [`css()`](http://api.jquery.com/css/) + * - [`data()`](http://api.jquery.com/data/) + * - [`empty()`](http://api.jquery.com/empty/) + * - [`eq()`](http://api.jquery.com/eq/) + * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name + * - [`hasClass()`](http://api.jquery.com/hasClass/) + * - [`html()`](http://api.jquery.com/html/) + * - [`next()`](http://api.jquery.com/next/) - Does not support selectors + * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData + * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors + * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors + * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors + * - [`prepend()`](http://api.jquery.com/prepend/) + * - [`prop()`](http://api.jquery.com/prop/) + * - [`ready()`](http://api.jquery.com/ready/) + * - [`remove()`](http://api.jquery.com/remove/) + * - [`removeAttr()`](http://api.jquery.com/removeAttr/) + * - [`removeClass()`](http://api.jquery.com/removeClass/) + * - [`removeData()`](http://api.jquery.com/removeData/) + * - [`replaceWith()`](http://api.jquery.com/replaceWith/) + * - [`text()`](http://api.jquery.com/text/) + * - [`toggleClass()`](http://api.jquery.com/toggleClass/) + * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. + * - [`unbind()`](http://api.jquery.com/off/) - Does not support namespaces + * - [`val()`](http://api.jquery.com/val/) + * - [`wrap()`](http://api.jquery.com/wrap/) + * + * ## jQuery/jqLite Extras + * Angular also provides the following additional methods and events to both jQuery and jqLite: + * + * ### Events + * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event + * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM + * element before it is removed. + * + * ### Methods + * - `controller(name)` - retrieves the controller of the current element or its parent. By default + * retrieves controller associated with the `ngController` directive. If `name` is provided as + * camelCase directive name, then the controller for this directive will be retrieved (e.g. + * `'ngModel'`). + * - `injector()` - retrieves the injector of the current element or its parent. + * - `scope()` - retrieves the {@link api/ng.$rootScope.Scope scope} of the current + * element or its parent. + * - `isolateScope()` - retrieves an isolate {@link api/ng.$rootScope.Scope scope} if one is attached directly to the + * current element. This getter should be used only on elements that contain a directive which starts a new isolate + * scope. Calling `scope()` on this element always returns the original non-isolate scope. + * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top + * parent element is reached. + * + * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. + * @returns {Object} jQuery object. + */ + +var jqCache = JQLite.cache = {}, + jqName = JQLite.expando = 'ng-' + new Date().getTime(), + jqId = 1, + addEventListenerFn = (window.document.addEventListener + ? function(element, type, fn) {element.addEventListener(type, fn, false);} + : function(element, type, fn) {element.attachEvent('on' + type, fn);}), + removeEventListenerFn = (window.document.removeEventListener + ? function(element, type, fn) {element.removeEventListener(type, fn, false); } + : function(element, type, fn) {element.detachEvent('on' + type, fn); }); + +function jqNextId() { return ++jqId; } + + +var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; +var MOZ_HACK_REGEXP = /^moz([A-Z])/; +var jqLiteMinErr = minErr('jqLite'); + +/** + * Converts snake_case to camelCase. + * Also there is special case for Moz prefix starting with upper case letter. + * @param name Name to normalize + */ +function camelCase(name) { + return name. + replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { + return offset ? letter.toUpperCase() : letter; + }). + replace(MOZ_HACK_REGEXP, 'Moz$1'); +} + +///////////////////////////////////////////// +// jQuery mutation patch +// +// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a +// $destroy event on all DOM nodes being removed. +// +///////////////////////////////////////////// + +function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { + var originalJqFn = jQuery.fn[name]; + originalJqFn = originalJqFn.$original || originalJqFn; + removePatch.$original = originalJqFn; + jQuery.fn[name] = removePatch; + + function removePatch(param) { + // jshint -W040 + var list = filterElems && param ? [this.filter(param)] : [this], + fireEvent = dispatchThis, + set, setIndex, setLength, + element, childIndex, childLength, children; + + if (!getterIfNoArguments || param != null) { + while(list.length) { + set = list.shift(); + for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { + element = jqLite(set[setIndex]); + if (fireEvent) { + element.triggerHandler('$destroy'); + } else { + fireEvent = !fireEvent; + } + for(childIndex = 0, childLength = (children = element.children()).length; + childIndex < childLength; + childIndex++) { + list.push(jQuery(children[childIndex])); + } + } + } + } + return originalJqFn.apply(this, arguments); + } +} + +///////////////////////////////////////////// +function JQLite(element) { + if (element instanceof JQLite) { + return element; + } + if (!(this instanceof JQLite)) { + if (isString(element) && element.charAt(0) != '<') { + throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); + } + return new JQLite(element); + } + + if (isString(element)) { + var div = document.createElement('div'); + // Read about the NoScope elements here: + // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx + div.innerHTML = '
       
      ' + element; // IE insanity to make NoScope elements work! + div.removeChild(div.firstChild); // remove the superfluous div + jqLiteAddNodes(this, div.childNodes); + var fragment = jqLite(document.createDocumentFragment()); + fragment.append(this); // detach the elements from the temporary DOM div. + } else { + jqLiteAddNodes(this, element); + } +} + +function jqLiteClone(element) { + return element.cloneNode(true); +} + +function jqLiteDealoc(element){ + jqLiteRemoveData(element); + for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { + jqLiteDealoc(children[i]); + } +} + +function jqLiteOff(element, type, fn, unsupported) { + if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); + + var events = jqLiteExpandoStore(element, 'events'), + handle = jqLiteExpandoStore(element, 'handle'); + + if (!handle) return; //no listeners registered + + if (isUndefined(type)) { + forEach(events, function(eventHandler, type) { + removeEventListenerFn(element, type, eventHandler); + delete events[type]; + }); + } else { + forEach(type.split(' '), function(type) { + if (isUndefined(fn)) { + removeEventListenerFn(element, type, events[type]); + delete events[type]; + } else { + arrayRemove(events[type] || [], fn); + } + }); + } +} + +function jqLiteRemoveData(element, name) { + var expandoId = element[jqName], + expandoStore = jqCache[expandoId]; + + if (expandoStore) { + if (name) { + delete jqCache[expandoId].data[name]; + return; + } + + if (expandoStore.handle) { + expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); + jqLiteOff(element); + } + delete jqCache[expandoId]; + element[jqName] = undefined; // ie does not allow deletion of attributes on elements. + } +} + +function jqLiteExpandoStore(element, key, value) { + var expandoId = element[jqName], + expandoStore = jqCache[expandoId || -1]; + + if (isDefined(value)) { + if (!expandoStore) { + element[jqName] = expandoId = jqNextId(); + expandoStore = jqCache[expandoId] = {}; + } + expandoStore[key] = value; + } else { + return expandoStore && expandoStore[key]; + } +} + +function jqLiteData(element, key, value) { + var data = jqLiteExpandoStore(element, 'data'), + isSetter = isDefined(value), + keyDefined = !isSetter && isDefined(key), + isSimpleGetter = keyDefined && !isObject(key); + + if (!data && !isSimpleGetter) { + jqLiteExpandoStore(element, 'data', data = {}); + } + + if (isSetter) { + data[key] = value; + } else { + if (keyDefined) { + if (isSimpleGetter) { + // don't create data in this case. + return data && data[key]; + } else { + extend(data, key); + } + } else { + return data; + } + } +} + +function jqLiteHasClass(element, selector) { + if (!element.getAttribute) return false; + return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). + indexOf( " " + selector + " " ) > -1); +} + +function jqLiteRemoveClass(element, cssClasses) { + if (cssClasses && element.setAttribute) { + forEach(cssClasses.split(' '), function(cssClass) { + element.setAttribute('class', trim( + (" " + (element.getAttribute('class') || '') + " ") + .replace(/[\n\t]/g, " ") + .replace(" " + trim(cssClass) + " ", " ")) + ); + }); + } +} + +function jqLiteAddClass(element, cssClasses) { + if (cssClasses && element.setAttribute) { + var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') + .replace(/[\n\t]/g, " "); + + forEach(cssClasses.split(' '), function(cssClass) { + cssClass = trim(cssClass); + if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { + existingClasses += cssClass + ' '; + } + }); + + element.setAttribute('class', trim(existingClasses)); + } +} + +function jqLiteAddNodes(root, elements) { + if (elements) { + elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) + ? elements + : [ elements ]; + for(var i=0; i < elements.length; i++) { + root.push(elements[i]); + } + } +} + +function jqLiteController(element, name) { + return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); +} + +function jqLiteInheritedData(element, name, value) { + element = jqLite(element); + + // if element is the document object work with the html element instead + // this makes $(document).scope() possible + if(element[0].nodeType == 9) { + element = element.find('html'); + } + var names = isArray(name) ? name : [name]; + + while (element.length) { + + for (var i = 0, ii = names.length; i < ii; i++) { + if ((value = element.data(names[i])) !== undefined) return value; + } + element = element.parent(); + } +} + +function jqLiteEmpty(element) { + for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { + jqLiteDealoc(childNodes[i]); + } + while (element.firstChild) { + element.removeChild(element.firstChild); + } +} + +////////////////////////////////////////// +// Functions which are declared directly. +////////////////////////////////////////// +var JQLitePrototype = JQLite.prototype = { + ready: function(fn) { + var fired = false; + + function trigger() { + if (fired) return; + fired = true; + fn(); + } + + // check if document already is loaded + if (document.readyState === 'complete'){ + setTimeout(trigger); + } else { + this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 + // we can not use jqLite since we are not done loading and jQuery could be loaded later. + // jshint -W064 + JQLite(window).on('load', trigger); // fallback to window.onload for others + // jshint +W064 + } + }, + toString: function() { + var value = []; + forEach(this, function(e){ value.push('' + e);}); + return '[' + value.join(', ') + ']'; + }, + + eq: function(index) { + return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); + }, + + length: 0, + push: push, + sort: [].sort, + splice: [].splice +}; + +////////////////////////////////////////// +// Functions iterating getter/setters. +// these functions return self on setter and +// value on get. +////////////////////////////////////////// +var BOOLEAN_ATTR = {}; +forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { + BOOLEAN_ATTR[lowercase(value)] = value; +}); +var BOOLEAN_ELEMENTS = {}; +forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { + BOOLEAN_ELEMENTS[uppercase(value)] = true; +}); + +function getBooleanAttrName(element, name) { + // check dom last since we will most likely fail on name + var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; + + // booleanAttr is here twice to minimize DOM access + return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; +} + +forEach({ + data: jqLiteData, + inheritedData: jqLiteInheritedData, + + scope: function(element) { + // Can't use jqLiteData here directly so we stay compatible with jQuery! + return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); + }, + + isolateScope: function(element) { + // Can't use jqLiteData here directly so we stay compatible with jQuery! + return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate'); + }, + + controller: jqLiteController , + + injector: function(element) { + return jqLiteInheritedData(element, '$injector'); + }, + + removeAttr: function(element,name) { + element.removeAttribute(name); + }, + + hasClass: jqLiteHasClass, + + css: function(element, name, value) { + name = camelCase(name); + + if (isDefined(value)) { + element.style[name] = value; + } else { + var val; + + if (msie <= 8) { + // this is some IE specific weirdness that jQuery 1.6.4 does not sure why + val = element.currentStyle && element.currentStyle[name]; + if (val === '') val = 'auto'; + } + + val = val || element.style[name]; + + if (msie <= 8) { + // jquery weirdness :-/ + val = (val === '') ? undefined : val; + } + + return val; + } + }, + + attr: function(element, name, value){ + var lowercasedName = lowercase(name); + if (BOOLEAN_ATTR[lowercasedName]) { + if (isDefined(value)) { + if (!!value) { + element[name] = true; + element.setAttribute(name, lowercasedName); + } else { + element[name] = false; + element.removeAttribute(lowercasedName); + } + } else { + return (element[name] || + (element.attributes.getNamedItem(name)|| noop).specified) + ? lowercasedName + : undefined; + } + } else if (isDefined(value)) { + element.setAttribute(name, value); + } else if (element.getAttribute) { + // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code + // some elements (e.g. Document) don't have get attribute, so return undefined + var ret = element.getAttribute(name, 2); + // normalize non-existing attributes to undefined (as jQuery) + return ret === null ? undefined : ret; + } + }, + + prop: function(element, name, value) { + if (isDefined(value)) { + element[name] = value; + } else { + return element[name]; + } + }, + + text: (function() { + var NODE_TYPE_TEXT_PROPERTY = []; + if (msie < 9) { + NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/ + NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/ + } else { + NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ + NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/ + } + getText.$dv = ''; + return getText; + + function getText(element, value) { + var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; + if (isUndefined(value)) { + return textProp ? element[textProp] : ''; + } + element[textProp] = value; + } + })(), + + val: function(element, value) { + if (isUndefined(value)) { + if (nodeName_(element) === 'SELECT' && element.multiple) { + var result = []; + forEach(element.options, function (option) { + if (option.selected) { + result.push(option.value || option.text); + } + }); + return result.length === 0 ? null : result; + } + return element.value; + } + element.value = value; + }, + + html: function(element, value) { + if (isUndefined(value)) { + return element.innerHTML; + } + for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { + jqLiteDealoc(childNodes[i]); + } + element.innerHTML = value; + }, + + empty: jqLiteEmpty +}, function(fn, name){ + /** + * Properties: writes return selection, reads return first value + */ + JQLite.prototype[name] = function(arg1, arg2) { + var i, key; + + // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it + // in a way that survives minification. + // jqLiteEmpty takes no arguments but is a setter. + if (fn !== jqLiteEmpty && + (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { + if (isObject(arg1)) { + + // we are a write, but the object properties are the key/values + for (i = 0; i < this.length; i++) { + if (fn === jqLiteData) { + // data() takes the whole object in jQuery + fn(this[i], arg1); + } else { + for (key in arg1) { + fn(this[i], key, arg1[key]); + } + } + } + // return self for chaining + return this; + } else { + // we are a read, so read the first child. + var value = fn.$dv; + // Only if we have $dv do we iterate over all, otherwise it is just the first element. + var jj = (value === undefined) ? Math.min(this.length, 1) : this.length; + for (var j = 0; j < jj; j++) { + var nodeValue = fn(this[j], arg1, arg2); + value = value ? value + nodeValue : nodeValue; + } + return value; + } + } else { + // we are a write, so apply to all children + for (i = 0; i < this.length; i++) { + fn(this[i], arg1, arg2); + } + // return self for chaining + return this; + } + }; +}); + +function createEventHandler(element, events) { + var eventHandler = function (event, type) { + if (!event.preventDefault) { + event.preventDefault = function() { + event.returnValue = false; //ie + }; + } + + if (!event.stopPropagation) { + event.stopPropagation = function() { + event.cancelBubble = true; //ie + }; + } + + if (!event.target) { + event.target = event.srcElement || document; + } + + if (isUndefined(event.defaultPrevented)) { + var prevent = event.preventDefault; + event.preventDefault = function() { + event.defaultPrevented = true; + prevent.call(event); + }; + event.defaultPrevented = false; + } + + event.isDefaultPrevented = function() { + return event.defaultPrevented || event.returnValue === false; + }; + + // Copy event handlers in case event handlers array is modified during execution. + var eventHandlersCopy = shallowCopy(events[type || event.type] || []); + + forEach(eventHandlersCopy, function(fn) { + fn.call(element, event); + }); + + // Remove monkey-patched methods (IE), + // as they would cause memory leaks in IE8. + if (msie <= 8) { + // IE7/8 does not allow to delete property on native object + event.preventDefault = null; + event.stopPropagation = null; + event.isDefaultPrevented = null; + } else { + // It shouldn't affect normal browsers (native methods are defined on prototype). + delete event.preventDefault; + delete event.stopPropagation; + delete event.isDefaultPrevented; + } + }; + eventHandler.elem = element; + return eventHandler; +} + +////////////////////////////////////////// +// Functions iterating traversal. +// These functions chain results into a single +// selector. +////////////////////////////////////////// +forEach({ + removeData: jqLiteRemoveData, + + dealoc: jqLiteDealoc, + + on: function onFn(element, type, fn, unsupported){ + if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); + + var events = jqLiteExpandoStore(element, 'events'), + handle = jqLiteExpandoStore(element, 'handle'); + + if (!events) jqLiteExpandoStore(element, 'events', events = {}); + if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); + + forEach(type.split(' '), function(type){ + var eventFns = events[type]; + + if (!eventFns) { + if (type == 'mouseenter' || type == 'mouseleave') { + var contains = document.body.contains || document.body.compareDocumentPosition ? + function( a, b ) { + // jshint bitwise: false + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + events[type] = []; + + // Refer to jQuery's implementation of mouseenter & mouseleave + // Read about mouseenter and mouseleave: + // http://www.quirksmode.org/js/events_mouse.html#link8 + var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; + + onFn(element, eventmap[type], function(event) { + var target = this, related = event.relatedTarget; + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !contains(target, related)) ){ + handle(event, type); + } + }); + + } else { + addEventListenerFn(element, type, handle); + events[type] = []; + } + eventFns = events[type]; + } + eventFns.push(fn); + }); + }, + + off: jqLiteOff, + + one: function(element, type, fn) { + element = jqLite(element); + + //add the listener twice so that when it is called + //you can remove the original function and still be + //able to call element.off(ev, fn) normally + element.on(type, function onFn() { + element.off(type, fn); + element.off(type, onFn); + }); + element.on(type, fn); + }, + + replaceWith: function(element, replaceNode) { + var index, parent = element.parentNode; + jqLiteDealoc(element); + forEach(new JQLite(replaceNode), function(node){ + if (index) { + parent.insertBefore(node, index.nextSibling); + } else { + parent.replaceChild(node, element); + } + index = node; + }); + }, + + children: function(element) { + var children = []; + forEach(element.childNodes, function(element){ + if (element.nodeType === 1) + children.push(element); + }); + return children; + }, + + contents: function(element) { + return element.childNodes || []; + }, + + append: function(element, node) { + forEach(new JQLite(node), function(child){ + if (element.nodeType === 1 || element.nodeType === 11) { + element.appendChild(child); + } + }); + }, + + prepend: function(element, node) { + if (element.nodeType === 1) { + var index = element.firstChild; + forEach(new JQLite(node), function(child){ + element.insertBefore(child, index); + }); + } + }, + + wrap: function(element, wrapNode) { + wrapNode = jqLite(wrapNode)[0]; + var parent = element.parentNode; + if (parent) { + parent.replaceChild(wrapNode, element); + } + wrapNode.appendChild(element); + }, + + remove: function(element) { + jqLiteDealoc(element); + var parent = element.parentNode; + if (parent) parent.removeChild(element); + }, + + after: function(element, newElement) { + var index = element, parent = element.parentNode; + forEach(new JQLite(newElement), function(node){ + parent.insertBefore(node, index.nextSibling); + index = node; + }); + }, + + addClass: jqLiteAddClass, + removeClass: jqLiteRemoveClass, + + toggleClass: function(element, selector, condition) { + if (isUndefined(condition)) { + condition = !jqLiteHasClass(element, selector); + } + (condition ? jqLiteAddClass : jqLiteRemoveClass)(element, selector); + }, + + parent: function(element) { + var parent = element.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + + next: function(element) { + if (element.nextElementSibling) { + return element.nextElementSibling; + } + + // IE8 doesn't have nextElementSibling + var elm = element.nextSibling; + while (elm != null && elm.nodeType !== 1) { + elm = elm.nextSibling; + } + return elm; + }, + + find: function(element, selector) { + if (element.getElementsByTagName) { + return element.getElementsByTagName(selector); + } else { + return []; + } + }, + + clone: jqLiteClone, + + triggerHandler: function(element, eventName, eventData) { + var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; + + eventData = eventData || []; + + var event = [{ + preventDefault: noop, + stopPropagation: noop + }]; + + forEach(eventFns, function(fn) { + fn.apply(element, event.concat(eventData)); + }); + } +}, function(fn, name){ + /** + * chaining functions + */ + JQLite.prototype[name] = function(arg1, arg2, arg3) { + var value; + for(var i=0; i < this.length; i++) { + if (isUndefined(value)) { + value = fn(this[i], arg1, arg2, arg3); + if (isDefined(value)) { + // any function which returns a value needs to be wrapped + value = jqLite(value); + } + } else { + jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); + } + } + return isDefined(value) ? value : this; + }; + + // bind legacy bind/unbind to on/off + JQLite.prototype.bind = JQLite.prototype.on; + JQLite.prototype.unbind = JQLite.prototype.off; +}); + +/** + * Computes a hash of an 'obj'. + * Hash of a: + * string is string + * number is number as string + * object is either result of calling $$hashKey function on the object or uniquely generated id, + * that is also assigned to the $$hashKey property of the object. + * + * @param obj + * @returns {string} hash string such that the same input will have the same hash string. + * The resulting string key is in 'type:hashKey' format. + */ +function hashKey(obj) { + var objType = typeof obj, + key; + + if (objType == 'object' && obj !== null) { + if (typeof (key = obj.$$hashKey) == 'function') { + // must invoke on object to keep the right this + key = obj.$$hashKey(); + } else if (key === undefined) { + key = obj.$$hashKey = nextUid(); + } + } else { + key = obj; + } + + return objType + ':' + key; +} + +/** + * HashMap which can use objects as keys + */ +function HashMap(array){ + forEach(array, this.put, this); +} +HashMap.prototype = { + /** + * Store key value pair + * @param key key to store can be any type + * @param value value to store can be any type + */ + put: function(key, value) { + this[hashKey(key)] = value; + }, + + /** + * @param key + * @returns the value for the key + */ + get: function(key) { + return this[hashKey(key)]; + }, + + /** + * Remove the key/value pair + * @param key + */ + remove: function(key) { + var value = this[key = hashKey(key)]; + delete this[key]; + return value; + } +}; + +/** + * @ngdoc function + * @name angular.injector + * @function + * + * @description + * Creates an injector function that can be used for retrieving services as well as for + * dependency injection (see {@link guide/di dependency injection}). + * + + * @param {Array.} modules A list of module functions or their aliases. See + * {@link angular.module}. The `ng` module must be explicitly added. + * @returns {function()} Injector function. See {@link AUTO.$injector $injector}. + * + * @example + * Typical usage + *
      + *   // create an injector
      + *   var $injector = angular.injector(['ng']);
      + *
      + *   // use the injector to kick off your application
      + *   // use the type inference to auto inject arguments, or use implicit injection
      + *   $injector.invoke(function($rootScope, $compile, $document){
      + *     $compile($document)($rootScope);
      + *     $rootScope.$digest();
      + *   });
      + * 
      + * + * Sometimes you want to get access to the injector of a currently running Angular app + * from outside Angular. Perhaps, you want to inject and compile some markup after the + * application has been bootstrapped. You can do this using extra `injector()` added + * to JQuery/jqLite elements. See {@link angular.element}. + * + * *This is fairly rare but could be the case if a third party library is injecting the + * markup.* + * + * In the following example a new block of HTML containing a `ng-controller` + * directive is added to the end of the document body by JQuery. We then compile and link + * it into the current AngularJS scope. + * + *
      + * var $div = $('
      {{content.label}}
      '); + * $(document.body).append($div); + * + * angular.element(document).injector().invoke(function($compile) { + * var scope = angular.element($div).scope(); + * $compile($div)(scope); + * }); + *
      + */ + + +/** + * @ngdoc overview + * @name AUTO + * @description + * + * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}. + */ + +var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; +var FN_ARG_SPLIT = /,/; +var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; +var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; +var $injectorMinErr = minErr('$injector'); +function annotate(fn) { + var $inject, + fnText, + argDecl, + last; + + if (typeof fn == 'function') { + if (!($inject = fn.$inject)) { + $inject = []; + if (fn.length) { + fnText = fn.toString().replace(STRIP_COMMENTS, ''); + argDecl = fnText.match(FN_ARGS); + forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ + arg.replace(FN_ARG, function(all, underscore, name){ + $inject.push(name); + }); + }); + } + fn.$inject = $inject; + } + } else if (isArray(fn)) { + last = fn.length - 1; + assertArgFn(fn[last], 'fn'); + $inject = fn.slice(0, last); + } else { + assertArgFn(fn, 'fn', true); + } + return $inject; +} + +/////////////////////////////////////// + +/** + * @ngdoc object + * @name AUTO.$injector + * @function + * + * @description + * + * `$injector` is used to retrieve object instances as defined by + * {@link AUTO.$provide provider}, instantiate types, invoke methods, + * and load modules. + * + * The following always holds true: + * + *
      + *   var $injector = angular.injector();
      + *   expect($injector.get('$injector')).toBe($injector);
      + *   expect($injector.invoke(function($injector){
      + *     return $injector;
      + *   }).toBe($injector);
      + * 
      + * + * # Injection Function Annotation + * + * JavaScript does not have annotations, and annotations are needed for dependency injection. The + * following are all valid ways of annotating function with injection arguments and are equivalent. + * + *
      + *   // inferred (only works if code not minified/obfuscated)
      + *   $injector.invoke(function(serviceA){});
      + *
      + *   // annotated
      + *   function explicit(serviceA) {};
      + *   explicit.$inject = ['serviceA'];
      + *   $injector.invoke(explicit);
      + *
      + *   // inline
      + *   $injector.invoke(['serviceA', function(serviceA){}]);
      + * 
      + * + * ## Inference + * + * In JavaScript calling `toString()` on a function returns the function definition. The definition + * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with + * minification, and obfuscation tools since these tools change the argument names. + * + * ## `$inject` Annotation + * By adding a `$inject` property onto a function the injection parameters can be specified. + * + * ## Inline + * As an array of injection names, where the last item in the array is the function to call. + */ + +/** + * @ngdoc method + * @name AUTO.$injector#get + * @methodOf AUTO.$injector + * + * @description + * Return an instance of the service. + * + * @param {string} name The name of the instance to retrieve. + * @return {*} The instance. + */ + +/** + * @ngdoc method + * @name AUTO.$injector#invoke + * @methodOf AUTO.$injector + * + * @description + * Invoke the method and supply the method arguments from the `$injector`. + * + * @param {!function} fn The function to invoke. Function parameters are injected according to the + * {@link guide/di $inject Annotation} rules. + * @param {Object=} self The `this` for the invoked method. + * @param {Object=} locals Optional object. If preset then any argument names are read from this + * object first, before the `$injector` is consulted. + * @returns {*} the value returned by the invoked `fn` function. + */ + +/** + * @ngdoc method + * @name AUTO.$injector#has + * @methodOf AUTO.$injector + * + * @description + * Allows the user to query if the particular service exist. + * + * @param {string} Name of the service to query. + * @returns {boolean} returns true if injector has given service. + */ + +/** + * @ngdoc method + * @name AUTO.$injector#instantiate + * @methodOf AUTO.$injector + * @description + * Create a new instance of JS type. The method takes a constructor function invokes the new + * operator and supplies all of the arguments to the constructor function as specified by the + * constructor annotation. + * + * @param {function} Type Annotated constructor function. + * @param {Object=} locals Optional object. If preset then any argument names are read from this + * object first, before the `$injector` is consulted. + * @returns {Object} new instance of `Type`. + */ + +/** + * @ngdoc method + * @name AUTO.$injector#annotate + * @methodOf AUTO.$injector + * + * @description + * Returns an array of service names which the function is requesting for injection. This API is + * used by the injector to determine which services need to be injected into the function when the + * function is invoked. There are three ways in which the function can be annotated with the needed + * dependencies. + * + * # Argument names + * + * The simplest form is to extract the dependencies from the arguments of the function. This is done + * by converting the function into a string using `toString()` method and extracting the argument + * names. + *
      + *   // Given
      + *   function MyController($scope, $route) {
      + *     // ...
      + *   }
      + *
      + *   // Then
      + *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
      + * 
      + * + * This method does not work with code minification / obfuscation. For this reason the following + * annotation strategies are supported. + * + * # The `$inject` property + * + * If a function has an `$inject` property and its value is an array of strings, then the strings + * represent names of services to be injected into the function. + *
      + *   // Given
      + *   var MyController = function(obfuscatedScope, obfuscatedRoute) {
      + *     // ...
      + *   }
      + *   // Define function dependencies
      + *   MyController['$inject'] = ['$scope', '$route'];
      + *
      + *   // Then
      + *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
      + * 
      + * + * # The array notation + * + * It is often desirable to inline Injected functions and that's when setting the `$inject` property + * is very inconvenient. In these situations using the array notation to specify the dependencies in + * a way that survives minification is a better choice: + * + *
      + *   // We wish to write this (not minification / obfuscation safe)
      + *   injector.invoke(function($compile, $rootScope) {
      + *     // ...
      + *   });
      + *
      + *   // We are forced to write break inlining
      + *   var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
      + *     // ...
      + *   };
      + *   tmpFn.$inject = ['$compile', '$rootScope'];
      + *   injector.invoke(tmpFn);
      + *
      + *   // To better support inline function the inline annotation is supported
      + *   injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
      + *     // ...
      + *   }]);
      + *
      + *   // Therefore
      + *   expect(injector.annotate(
      + *      ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
      + *    ).toEqual(['$compile', '$rootScope']);
      + * 
      + * + * @param {function|Array.} fn Function for which dependent service names need to + * be retrieved as described above. + * + * @returns {Array.} The names of the services which the function requires. + */ + + + + +/** + * @ngdoc object + * @name AUTO.$provide + * + * @description + * + * The {@link AUTO.$provide $provide} service has a number of methods for registering components + * with the {@link AUTO.$injector $injector}. Many of these functions are also exposed on + * {@link angular.Module}. + * + * An Angular **service** is a singleton object created by a **service factory**. These **service + * factories** are functions which, in turn, are created by a **service provider**. + * The **service providers** are constructor functions. When instantiated they must contain a + * property called `$get`, which holds the **service factory** function. + * + * When you request a service, the {@link AUTO.$injector $injector} is responsible for finding the + * correct **service provider**, instantiating it and then calling its `$get` **service factory** + * function to get the instance of the **service**. + * + * Often services have no configuration options and there is no need to add methods to the service + * provider. The provider will be no more than a constructor function with a `$get` property. For + * these cases the {@link AUTO.$provide $provide} service has additional helper methods to register + * services without specifying a provider. + * + * * {@link AUTO.$provide#methods_provider provider(provider)} - registers a **service provider** with the + * {@link AUTO.$injector $injector} + * * {@link AUTO.$provide#methods_constant constant(obj)} - registers a value/object that can be accessed by + * providers and services. + * * {@link AUTO.$provide#methods_value value(obj)} - registers a value/object that can only be accessed by + * services, not providers. + * * {@link AUTO.$provide#methods_factory factory(fn)} - registers a service **factory function**, `fn`, + * that will be wrapped in a **service provider** object, whose `$get` property will contain the + * given factory function. + * * {@link AUTO.$provide#methods_service service(class)} - registers a **constructor function**, `class` that + * that will be wrapped in a **service provider** object, whose `$get` property will instantiate + * a new object using the given constructor function. + * + * See the individual methods for more information and examples. + */ + +/** + * @ngdoc method + * @name AUTO.$provide#provider + * @methodOf AUTO.$provide + * @description + * + * Register a **provider function** with the {@link AUTO.$injector $injector}. Provider functions + * are constructor functions, whose instances are responsible for "providing" a factory for a + * service. + * + * Service provider names start with the name of the service they provide followed by `Provider`. + * For example, the {@link ng.$log $log} service has a provider called + * {@link ng.$logProvider $logProvider}. + * + * Service provider objects can have additional methods which allow configuration of the provider + * and its service. Importantly, you can configure what kind of service is created by the `$get` + * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a + * method {@link ng.$logProvider#debugEnabled debugEnabled} + * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the + * console or not. + * + * @param {string} name The name of the instance. NOTE: the provider will be available under `name + + 'Provider'` key. + * @param {(Object|function())} provider If the provider is: + * + * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using + * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be + * created. + * - `Constructor`: a new instance of the provider will be created using + * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as + * `object`. + * + * @returns {Object} registered provider instance + + * @example + * + * The following example shows how to create a simple event tracking service and register it using + * {@link AUTO.$provide#methods_provider $provide.provider()}. + * + *
      + *  // Define the eventTracker provider
      + *  function EventTrackerProvider() {
      + *    var trackingUrl = '/track';
      + *
      + *    // A provider method for configuring where the tracked events should been saved
      + *    this.setTrackingUrl = function(url) {
      + *      trackingUrl = url;
      + *    };
      + *
      + *    // The service factory function
      + *    this.$get = ['$http', function($http) {
      + *      var trackedEvents = {};
      + *      return {
      + *        // Call this to track an event
      + *        event: function(event) {
      + *          var count = trackedEvents[event] || 0;
      + *          count += 1;
      + *          trackedEvents[event] = count;
      + *          return count;
      + *        },
      + *        // Call this to save the tracked events to the trackingUrl
      + *        save: function() {
      + *          $http.post(trackingUrl, trackedEvents);
      + *        }
      + *      };
      + *    }];
      + *  }
      + *
      + *  describe('eventTracker', function() {
      + *    var postSpy;
      + *
      + *    beforeEach(module(function($provide) {
      + *      // Register the eventTracker provider
      + *      $provide.provider('eventTracker', EventTrackerProvider);
      + *    }));
      + *
      + *    beforeEach(module(function(eventTrackerProvider) {
      + *      // Configure eventTracker provider
      + *      eventTrackerProvider.setTrackingUrl('/custom-track');
      + *    }));
      + *
      + *    it('tracks events', inject(function(eventTracker) {
      + *      expect(eventTracker.event('login')).toEqual(1);
      + *      expect(eventTracker.event('login')).toEqual(2);
      + *    }));
      + *
      + *    it('saves to the tracking url', inject(function(eventTracker, $http) {
      + *      postSpy = spyOn($http, 'post');
      + *      eventTracker.event('login');
      + *      eventTracker.save();
      + *      expect(postSpy).toHaveBeenCalled();
      + *      expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
      + *      expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
      + *      expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
      + *    }));
      + *  });
      + * 
      + */ + +/** + * @ngdoc method + * @name AUTO.$provide#factory + * @methodOf AUTO.$provide + * @description + * + * Register a **service factory**, which will be called to return the service instance. + * This is short for registering a service where its provider consists of only a `$get` property, + * which is the given service factory function. + * You should use {@link AUTO.$provide#factory $provide.factory(getFn)} if you do not need to + * configure your service in a provider. + * + * @param {string} name The name of the instance. + * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand + * for `$provide.provider(name, {$get: $getFn})`. + * @returns {Object} registered provider instance + * + * @example + * Here is an example of registering a service + *
      + *   $provide.factory('ping', ['$http', function($http) {
      + *     return function ping() {
      + *       return $http.send('/ping');
      + *     };
      + *   }]);
      + * 
      + * You would then inject and use this service like this: + *
      + *   someModule.controller('Ctrl', ['ping', function(ping) {
      + *     ping();
      + *   }]);
      + * 
      + */ + + +/** + * @ngdoc method + * @name AUTO.$provide#service + * @methodOf AUTO.$provide + * @description + * + * Register a **service constructor**, which will be invoked with `new` to create the service + * instance. + * This is short for registering a service where its provider's `$get` property is the service + * constructor function that will be used to instantiate the service instance. + * + * You should use {@link AUTO.$provide#methods_service $provide.service(class)} if you define your service + * as a type/class. + * + * @param {string} name The name of the instance. + * @param {Function} constructor A class (constructor function) that will be instantiated. + * @returns {Object} registered provider instance + * + * @example + * Here is an example of registering a service using + * {@link AUTO.$provide#methods_service $provide.service(class)}. + *
      + *   $provide.service('ping', ['$http', function($http) {
      + *     var Ping = function() {
      + *       this.$http = $http;
      + *     };
      + *   
      + *     Ping.prototype.send = function() {
      + *       return this.$http.get('/ping');
      + *     }; 
      + *   
      + *     return Ping;
      + *   }]);
      + * 
      + * You would then inject and use this service like this: + *
      + *   someModule.controller('Ctrl', ['ping', function(ping) {
      + *     ping.send();
      + *   }]);
      + * 
      + */ + + +/** + * @ngdoc method + * @name AUTO.$provide#value + * @methodOf AUTO.$provide + * @description + * + * Register a **value service** with the {@link AUTO.$injector $injector}, such as a string, a + * number, an array, an object or a function. This is short for registering a service where its + * provider's `$get` property is a factory function that takes no arguments and returns the **value + * service**. + * + * Value services are similar to constant services, except that they cannot be injected into a + * module configuration function (see {@link angular.Module#config}) but they can be overridden by + * an Angular + * {@link AUTO.$provide#decorator decorator}. + * + * @param {string} name The name of the instance. + * @param {*} value The value. + * @returns {Object} registered provider instance + * + * @example + * Here are some examples of creating value services. + *
      + *   $provide.value('ADMIN_USER', 'admin');
      + *
      + *   $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
      + *
      + *   $provide.value('halfOf', function(value) {
      + *     return value / 2;
      + *   });
      + * 
      + */ + + +/** + * @ngdoc method + * @name AUTO.$provide#constant + * @methodOf AUTO.$provide + * @description + * + * Register a **constant service**, such as a string, a number, an array, an object or a function, + * with the {@link AUTO.$injector $injector}. Unlike {@link AUTO.$provide#value value} it can be + * injected into a module configuration function (see {@link angular.Module#config}) and it cannot + * be overridden by an Angular {@link AUTO.$provide#decorator decorator}. + * + * @param {string} name The name of the constant. + * @param {*} value The constant value. + * @returns {Object} registered instance + * + * @example + * Here a some examples of creating constants: + *
      + *   $provide.constant('SHARD_HEIGHT', 306);
      + *
      + *   $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
      + *
      + *   $provide.constant('double', function(value) {
      + *     return value * 2;
      + *   });
      + * 
      + */ + + +/** + * @ngdoc method + * @name AUTO.$provide#decorator + * @methodOf AUTO.$provide + * @description + * + * Register a **service decorator** with the {@link AUTO.$injector $injector}. A service decorator + * intercepts the creation of a service, allowing it to override or modify the behaviour of the + * service. The object returned by the decorator may be the original service, or a new service + * object which replaces or wraps and delegates to the original service. + * + * @param {string} name The name of the service to decorate. + * @param {function()} decorator This function will be invoked when the service needs to be + * instantiated and should return the decorated service instance. The function is called using + * the {@link AUTO.$injector#invoke injector.invoke} method and is therefore fully injectable. + * Local injection arguments: + * + * * `$delegate` - The original service instance, which can be monkey patched, configured, + * decorated or delegated to. + * + * @example + * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting + * calls to {@link ng.$log#error $log.warn()}. + *
      + *   $provider.decorator('$log', ['$delegate', function($delegate) {
      + *     $delegate.warn = $delegate.error;
      + *     return $delegate;
      + *   }]);
      + * 
      + */ + + +function createInjector(modulesToLoad) { + var INSTANTIATING = {}, + providerSuffix = 'Provider', + path = [], + loadedModules = new HashMap(), + providerCache = { + $provide: { + provider: supportObject(provider), + factory: supportObject(factory), + service: supportObject(service), + value: supportObject(value), + constant: supportObject(constant), + decorator: decorator + } + }, + providerInjector = (providerCache.$injector = + createInternalInjector(providerCache, function() { + throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); + })), + instanceCache = {}, + instanceInjector = (instanceCache.$injector = + createInternalInjector(instanceCache, function(servicename) { + var provider = providerInjector.get(servicename + providerSuffix); + return instanceInjector.invoke(provider.$get, provider); + })); + + + forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); + + return instanceInjector; + + //////////////////////////////////// + // $provider + //////////////////////////////////// + + function supportObject(delegate) { + return function(key, value) { + if (isObject(key)) { + forEach(key, reverseParams(delegate)); + } else { + return delegate(key, value); + } + }; + } + + function provider(name, provider_) { + assertNotHasOwnProperty(name, 'service'); + if (isFunction(provider_) || isArray(provider_)) { + provider_ = providerInjector.instantiate(provider_); + } + if (!provider_.$get) { + throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); + } + return providerCache[name + providerSuffix] = provider_; + } + + function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } + + function service(name, constructor) { + return factory(name, ['$injector', function($injector) { + return $injector.instantiate(constructor); + }]); + } + + function value(name, val) { return factory(name, valueFn(val)); } + + function constant(name, value) { + assertNotHasOwnProperty(name, 'constant'); + providerCache[name] = value; + instanceCache[name] = value; + } + + function decorator(serviceName, decorFn) { + var origProvider = providerInjector.get(serviceName + providerSuffix), + orig$get = origProvider.$get; + + origProvider.$get = function() { + var origInstance = instanceInjector.invoke(orig$get, origProvider); + return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); + }; + } + + //////////////////////////////////// + // Module Loading + //////////////////////////////////// + function loadModules(modulesToLoad){ + var runBlocks = [], moduleFn, invokeQueue, i, ii; + forEach(modulesToLoad, function(module) { + if (loadedModules.get(module)) return; + loadedModules.put(module, true); + + try { + if (isString(module)) { + moduleFn = angularModule(module); + runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); + + for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { + var invokeArgs = invokeQueue[i], + provider = providerInjector.get(invokeArgs[0]); + + provider[invokeArgs[1]].apply(provider, invokeArgs[2]); + } + } else if (isFunction(module)) { + runBlocks.push(providerInjector.invoke(module)); + } else if (isArray(module)) { + runBlocks.push(providerInjector.invoke(module)); + } else { + assertArgFn(module, 'module'); + } + } catch (e) { + if (isArray(module)) { + module = module[module.length - 1]; + } + if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { + // Safari & FF's stack traces don't contain error.message content + // unlike those of Chrome and IE + // So if stack doesn't contain message, we create a new string that contains both. + // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. + /* jshint -W022 */ + e = e.message + '\n' + e.stack; + } + throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", + module, e.stack || e.message || e); + } + }); + return runBlocks; + } + + //////////////////////////////////// + // internal Injector + //////////////////////////////////// + + function createInternalInjector(cache, factory) { + + function getService(serviceName) { + if (cache.hasOwnProperty(serviceName)) { + if (cache[serviceName] === INSTANTIATING) { + throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- ')); + } + return cache[serviceName]; + } else { + try { + path.unshift(serviceName); + cache[serviceName] = INSTANTIATING; + return cache[serviceName] = factory(serviceName); + } catch (err) { + if (cache[serviceName] === INSTANTIATING) { + delete cache[serviceName]; + } + throw err; + } finally { + path.shift(); + } + } + } + + function invoke(fn, self, locals){ + var args = [], + $inject = annotate(fn), + length, i, + key; + + for(i = 0, length = $inject.length; i < length; i++) { + key = $inject[i]; + if (typeof key !== 'string') { + throw $injectorMinErr('itkn', + 'Incorrect injection token! Expected service name as string, got {0}', key); + } + args.push( + locals && locals.hasOwnProperty(key) + ? locals[key] + : getService(key) + ); + } + if (!fn.$inject) { + // this means that we must be an array. + fn = fn[length]; + } + + // http://jsperf.com/angularjs-invoke-apply-vs-switch + // #5388 + return fn.apply(self, args); + } + + function instantiate(Type, locals) { + var Constructor = function() {}, + instance, returnedValue; + + // Check if Type is annotated and use just the given function at n-1 as parameter + // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); + Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; + instance = new Constructor(); + returnedValue = invoke(Type, instance, locals); + + return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; + } + + return { + invoke: invoke, + instantiate: instantiate, + get: getService, + annotate: annotate, + has: function(name) { + return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); + } + }; + } +} + +/** + * @ngdoc function + * @name ng.$anchorScroll + * @requires $window + * @requires $location + * @requires $rootScope + * + * @description + * When called, it checks current value of `$location.hash()` and scroll to related element, + * according to rules specified in + * {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}. + * + * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. + * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. + * + * @example + + +
      + Go to bottom + You're at the bottom! +
      +
      + + function ScrollCtrl($scope, $location, $anchorScroll) { + $scope.gotoBottom = function (){ + // set the location.hash to the id of + // the element you wish to scroll to. + $location.hash('bottom'); + + // call $anchorScroll() + $anchorScroll(); + } + } + + + #scrollArea { + height: 350px; + overflow: auto; + } + + #bottom { + display: block; + margin-top: 2000px; + } + +
      + */ +function $AnchorScrollProvider() { + + var autoScrollingEnabled = true; + + this.disableAutoScrolling = function() { + autoScrollingEnabled = false; + }; + + this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { + var document = $window.document; + + // helper function to get first anchor from a NodeList + // can't use filter.filter, as it accepts only instances of Array + // and IE can't convert NodeList to an array using [].slice + // TODO(vojta): use filter if we change it to accept lists as well + function getFirstAnchor(list) { + var result = null; + forEach(list, function(element) { + if (!result && lowercase(element.nodeName) === 'a') result = element; + }); + return result; + } + + function scroll() { + var hash = $location.hash(), elm; + + // empty hash, scroll to the top of the page + if (!hash) $window.scrollTo(0, 0); + + // element with given id + else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); + + // first anchor with given name :-D + else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); + + // no element and hash == 'top', scroll to the top of the page + else if (hash === 'top') $window.scrollTo(0, 0); + } + + // does not scroll when user clicks on anchor link that is currently on + // (no url change, no $location.hash() change), browser native does scroll + if (autoScrollingEnabled) { + $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, + function autoScrollWatchAction() { + $rootScope.$evalAsync(scroll); + }); + } + + return scroll; + }]; +} + +var $animateMinErr = minErr('$animate'); + +/** + * @ngdoc object + * @name ng.$animateProvider + * + * @description + * Default implementation of $animate that doesn't perform any animations, instead just + * synchronously performs DOM + * updates and calls done() callbacks. + * + * In order to enable animations the ngAnimate module has to be loaded. + * + * To see the functional implementation check out src/ngAnimate/animate.js + */ +var $AnimateProvider = ['$provide', function($provide) { + + + this.$$selectors = {}; + + + /** + * @ngdoc function + * @name ng.$animateProvider#register + * @methodOf ng.$animateProvider + * + * @description + * Registers a new injectable animation factory function. The factory function produces the + * animation object which contains callback functions for each event that is expected to be + * animated. + * + * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` + * must be called once the element animation is complete. If a function is returned then the + * animation service will use this function to cancel the animation whenever a cancel event is + * triggered. + * + * + *
      +   *   return {
      +     *     eventFn : function(element, done) {
      +     *       //code to run the animation
      +     *       //once complete, then run done()
      +     *       return function cancellationFunction() {
      +     *         //code to cancel the animation
      +     *       }
      +     *     }
      +     *   }
      +   *
      + * + * @param {string} name The name of the animation. + * @param {function} factory The factory function that will be executed to return the animation + * object. + */ + this.register = function(name, factory) { + var key = name + '-animation'; + if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', + "Expecting class selector starting with '.' got '{0}'.", name); + this.$$selectors[name.substr(1)] = key; + $provide.factory(key, factory); + }; + + /** + * @ngdoc function + * @name ng.$animateProvider#classNameFilter + * @methodOf ng.$animateProvider + * + * @description + * Sets and/or returns the CSS class regular expression that is checked when performing + * an animation. Upon bootstrap the classNameFilter value is not set at all and will + * therefore enable $animate to attempt to perform an animation on any element. + * When setting the classNameFilter value, animations will only be performed on elements + * that successfully match the filter expression. This in turn can boost performance + * for low-powered devices as well as applications containing a lot of structural operations. + * @param {RegExp=} expression The className expression which will be checked against all animations + * @return {RegExp} The current CSS className expression value. If null then there is no expression value + */ + this.classNameFilter = function(expression) { + if(arguments.length === 1) { + this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; + } + return this.$$classNameFilter; + }; + + this.$get = ['$timeout', function($timeout) { + + /** + * + * @ngdoc object + * @name ng.$animate + * @description The $animate service provides rudimentary DOM manipulation functions to + * insert, remove and move elements within the DOM, as well as adding and removing classes. + * This service is the core service used by the ngAnimate $animator service which provides + * high-level animation hooks for CSS and JavaScript. + * + * $animate is available in the AngularJS core, however, the ngAnimate module must be included + * to enable full out animation support. Otherwise, $animate will only perform simple DOM + * manipulation operations. + * + * To learn more about enabling animation support, click here to visit the {@link ngAnimate + * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service + * page}. + */ + return { + + /** + * + * @ngdoc function + * @name ng.$animate#enter + * @methodOf ng.$animate + * @function + * @description Inserts the element into the DOM either after the `after` element or within + * the `parent` element. Once complete, the done() callback will be fired (if provided). + * @param {jQuery/jqLite element} element the element which will be inserted into the DOM + * @param {jQuery/jqLite element} parent the parent element which will append the element as + * a child (if the after element is not present) + * @param {jQuery/jqLite element} after the sibling element which will append the element + * after itself + * @param {function=} done callback function that will be called after the element has been + * inserted into the DOM + */ + enter : function(element, parent, after, done) { + if (after) { + after.after(element); + } else { + if (!parent || !parent[0]) { + parent = after.parent(); + } + parent.append(element); + } + done && $timeout(done, 0, false); + }, + + /** + * + * @ngdoc function + * @name ng.$animate#leave + * @methodOf ng.$animate + * @function + * @description Removes the element from the DOM. Once complete, the done() callback will be + * fired (if provided). + * @param {jQuery/jqLite element} element the element which will be removed from the DOM + * @param {function=} done callback function that will be called after the element has been + * removed from the DOM + */ + leave : function(element, done) { + element.remove(); + done && $timeout(done, 0, false); + }, + + /** + * + * @ngdoc function + * @name ng.$animate#move + * @methodOf ng.$animate + * @function + * @description Moves the position of the provided element within the DOM to be placed + * either after the `after` element or inside of the `parent` element. Once complete, the + * done() callback will be fired (if provided). + * + * @param {jQuery/jqLite element} element the element which will be moved around within the + * DOM + * @param {jQuery/jqLite element} parent the parent element where the element will be + * inserted into (if the after element is not present) + * @param {jQuery/jqLite element} after the sibling element where the element will be + * positioned next to + * @param {function=} done the callback function (if provided) that will be fired after the + * element has been moved to its new position + */ + move : function(element, parent, after, done) { + // Do not remove element before insert. Removing will cause data associated with the + // element to be dropped. Insert will implicitly do the remove. + this.enter(element, parent, after, done); + }, + + /** + * + * @ngdoc function + * @name ng.$animate#addClass + * @methodOf ng.$animate + * @function + * @description Adds the provided className CSS class value to the provided element. Once + * complete, the done() callback will be fired (if provided). + * @param {jQuery/jqLite element} element the element which will have the className value + * added to it + * @param {string} className the CSS class which will be added to the element + * @param {function=} done the callback function (if provided) that will be fired after the + * className value has been added to the element + */ + addClass : function(element, className, done) { + className = isString(className) ? + className : + isArray(className) ? className.join(' ') : ''; + forEach(element, function (element) { + jqLiteAddClass(element, className); + }); + done && $timeout(done, 0, false); + }, + + /** + * + * @ngdoc function + * @name ng.$animate#removeClass + * @methodOf ng.$animate + * @function + * @description Removes the provided className CSS class value from the provided element. + * Once complete, the done() callback will be fired (if provided). + * @param {jQuery/jqLite element} element the element which will have the className value + * removed from it + * @param {string} className the CSS class which will be removed from the element + * @param {function=} done the callback function (if provided) that will be fired after the + * className value has been removed from the element + */ + removeClass : function(element, className, done) { + className = isString(className) ? + className : + isArray(className) ? className.join(' ') : ''; + forEach(element, function (element) { + jqLiteRemoveClass(element, className); + }); + done && $timeout(done, 0, false); + }, + + enabled : noop + }; + }]; +}]; + +/** + * ! This is a private undocumented service ! + * + * @name ng.$browser + * @requires $log + * @description + * This object has two goals: + * + * - hide all the global state in the browser caused by the window object + * - abstract away all the browser specific features and inconsistencies + * + * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` + * service, which can be used for convenient testing of the application without the interaction with + * the real browser apis. + */ +/** + * @param {object} window The global window object. + * @param {object} document jQuery wrapped document. + * @param {function()} XHR XMLHttpRequest constructor. + * @param {object} $log console.log or an object with the same interface. + * @param {object} $sniffer $sniffer service + */ +function Browser(window, document, $log, $sniffer) { + var self = this, + rawDocument = document[0], + location = window.location, + history = window.history, + setTimeout = window.setTimeout, + clearTimeout = window.clearTimeout, + pendingDeferIds = {}; + + self.isMock = false; + + var outstandingRequestCount = 0; + var outstandingRequestCallbacks = []; + + // TODO(vojta): remove this temporary api + self.$$completeOutstandingRequest = completeOutstandingRequest; + self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; + + /** + * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` + * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. + */ + function completeOutstandingRequest(fn) { + try { + fn.apply(null, sliceArgs(arguments, 1)); + } finally { + outstandingRequestCount--; + if (outstandingRequestCount === 0) { + while(outstandingRequestCallbacks.length) { + try { + outstandingRequestCallbacks.pop()(); + } catch (e) { + $log.error(e); + } + } + } + } + } + + /** + * @private + * Note: this method is used only by scenario runner + * TODO(vojta): prefix this method with $$ ? + * @param {function()} callback Function that will be called when no outstanding request + */ + self.notifyWhenNoOutstandingRequests = function(callback) { + // force browser to execute all pollFns - this is needed so that cookies and other pollers fire + // at some deterministic time in respect to the test runner's actions. Leaving things up to the + // regular poller would result in flaky tests. + forEach(pollFns, function(pollFn){ pollFn(); }); + + if (outstandingRequestCount === 0) { + callback(); + } else { + outstandingRequestCallbacks.push(callback); + } + }; + + ////////////////////////////////////////////////////////////// + // Poll Watcher API + ////////////////////////////////////////////////////////////// + var pollFns = [], + pollTimeout; + + /** + * @name ng.$browser#addPollFn + * @methodOf ng.$browser + * + * @param {function()} fn Poll function to add + * + * @description + * Adds a function to the list of functions that poller periodically executes, + * and starts polling if not started yet. + * + * @returns {function()} the added function + */ + self.addPollFn = function(fn) { + if (isUndefined(pollTimeout)) startPoller(100, setTimeout); + pollFns.push(fn); + return fn; + }; + + /** + * @param {number} interval How often should browser call poll functions (ms) + * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. + * + * @description + * Configures the poller to run in the specified intervals, using the specified + * setTimeout fn and kicks it off. + */ + function startPoller(interval, setTimeout) { + (function check() { + forEach(pollFns, function(pollFn){ pollFn(); }); + pollTimeout = setTimeout(check, interval); + })(); + } + + ////////////////////////////////////////////////////////////// + // URL API + ////////////////////////////////////////////////////////////// + + var lastBrowserUrl = location.href, + baseElement = document.find('base'), + newLocation = null; + + /** + * @name ng.$browser#url + * @methodOf ng.$browser + * + * @description + * GETTER: + * Without any argument, this method just returns current value of location.href. + * + * SETTER: + * With at least one argument, this method sets url to new value. + * If html5 history api supported, pushState/replaceState is used, otherwise + * location.href/location.replace is used. + * Returns its own instance to allow chaining + * + * NOTE: this api is intended for use only by the $location service. Please use the + * {@link ng.$location $location service} to change url. + * + * @param {string} url New url (when used as setter) + * @param {boolean=} replace Should new url replace current history record ? + */ + self.url = function(url, replace) { + // Android Browser BFCache causes location, history reference to become stale. + if (location !== window.location) location = window.location; + if (history !== window.history) history = window.history; + + // setter + if (url) { + if (lastBrowserUrl == url) return; + lastBrowserUrl = url; + if ($sniffer.history) { + if (replace) history.replaceState(null, '', url); + else { + history.pushState(null, '', url); + // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 + baseElement.attr('href', baseElement.attr('href')); + } + } else { + newLocation = url; + if (replace) { + location.replace(url); + } else { + location.href = url; + } + } + return self; + // getter + } else { + // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href + // methods not updating location.href synchronously. + // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 + return newLocation || location.href.replace(/%27/g,"'"); + } + }; + + var urlChangeListeners = [], + urlChangeInit = false; + + function fireUrlChange() { + newLocation = null; + if (lastBrowserUrl == self.url()) return; + + lastBrowserUrl = self.url(); + forEach(urlChangeListeners, function(listener) { + listener(self.url()); + }); + } + + /** + * @name ng.$browser#onUrlChange + * @methodOf ng.$browser + * @TODO(vojta): refactor to use node's syntax for events + * + * @description + * Register callback function that will be called, when url changes. + * + * It's only called when the url is changed from outside of angular: + * - user types different url into address bar + * - user clicks on history (forward/back) button + * - user clicks on a link + * + * It's not called when url is changed by $browser.url() method + * + * The listener gets called with new url as parameter. + * + * NOTE: this api is intended for use only by the $location service. Please use the + * {@link ng.$location $location service} to monitor url changes in angular apps. + * + * @param {function(string)} listener Listener function to be called when url changes. + * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. + */ + self.onUrlChange = function(callback) { + if (!urlChangeInit) { + // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) + // don't fire popstate when user change the address bar and don't fire hashchange when url + // changed by push/replaceState + + // html5 history api - popstate event + if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); + // hashchange event + if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); + // polling + else self.addPollFn(fireUrlChange); + + urlChangeInit = true; + } + + urlChangeListeners.push(callback); + return callback; + }; + + ////////////////////////////////////////////////////////////// + // Misc API + ////////////////////////////////////////////////////////////// + + /** + * @name ng.$browser#baseHref + * @methodOf ng.$browser + * + * @description + * Returns current + * (always relative - without domain) + * + * @returns {string=} current + */ + self.baseHref = function() { + var href = baseElement.attr('href'); + return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; + }; + + ////////////////////////////////////////////////////////////// + // Cookies API + ////////////////////////////////////////////////////////////// + var lastCookies = {}; + var lastCookieString = ''; + var cookiePath = self.baseHref(); + + /** + * @name ng.$browser#cookies + * @methodOf ng.$browser + * + * @param {string=} name Cookie name + * @param {string=} value Cookie value + * + * @description + * The cookies method provides a 'private' low level access to browser cookies. + * It is not meant to be used directly, use the $cookie service instead. + * + * The return values vary depending on the arguments that the method was called with as follows: + * + * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify + * it + * - cookies(name, value) -> set name to value, if value is undefined delete the cookie + * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that + * way) + * + * @returns {Object} Hash of all cookies (if called without any parameter) + */ + self.cookies = function(name, value) { + /* global escape: false, unescape: false */ + var cookieLength, cookieArray, cookie, i, index; + + if (name) { + if (value === undefined) { + rawDocument.cookie = escape(name) + "=;path=" + cookiePath + + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; + } else { + if (isString(value)) { + cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + + ';path=' + cookiePath).length + 1; + + // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: + // - 300 cookies + // - 20 cookies per unique domain + // - 4096 bytes per cookie + if (cookieLength > 4096) { + $log.warn("Cookie '"+ name + + "' possibly not set or overflowed because it was too large ("+ + cookieLength + " > 4096 bytes)!"); + } + } + } + } else { + if (rawDocument.cookie !== lastCookieString) { + lastCookieString = rawDocument.cookie; + cookieArray = lastCookieString.split("; "); + lastCookies = {}; + + for (i = 0; i < cookieArray.length; i++) { + cookie = cookieArray[i]; + index = cookie.indexOf('='); + if (index > 0) { //ignore nameless cookies + name = unescape(cookie.substring(0, index)); + // the first value that is seen for a cookie is the most + // specific one. values for the same cookie name that + // follow are for less specific paths. + if (lastCookies[name] === undefined) { + lastCookies[name] = unescape(cookie.substring(index + 1)); + } + } + } + } + return lastCookies; + } + }; + + + /** + * @name ng.$browser#defer + * @methodOf ng.$browser + * @param {function()} fn A function, who's execution should be deferred. + * @param {number=} [delay=0] of milliseconds to defer the function execution. + * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. + * + * @description + * Executes a fn asynchronously via `setTimeout(fn, delay)`. + * + * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using + * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed + * via `$browser.defer.flush()`. + * + */ + self.defer = function(fn, delay) { + var timeoutId; + outstandingRequestCount++; + timeoutId = setTimeout(function() { + delete pendingDeferIds[timeoutId]; + completeOutstandingRequest(fn); + }, delay || 0); + pendingDeferIds[timeoutId] = true; + return timeoutId; + }; + + + /** + * @name ng.$browser#defer.cancel + * @methodOf ng.$browser.defer + * + * @description + * Cancels a deferred task identified with `deferId`. + * + * @param {*} deferId Token returned by the `$browser.defer` function. + * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully + * canceled. + */ + self.defer.cancel = function(deferId) { + if (pendingDeferIds[deferId]) { + delete pendingDeferIds[deferId]; + clearTimeout(deferId); + completeOutstandingRequest(noop); + return true; + } + return false; + }; + +} + +function $BrowserProvider(){ + this.$get = ['$window', '$log', '$sniffer', '$document', + function( $window, $log, $sniffer, $document){ + return new Browser($window, $document, $log, $sniffer); + }]; +} + +/** + * @ngdoc object + * @name ng.$cacheFactory + * + * @description + * Factory that constructs cache objects and gives access to them. + * + *
      + * 
      + *  var cache = $cacheFactory('cacheId');
      + *  expect($cacheFactory.get('cacheId')).toBe(cache);
      + *  expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
      + *
      + *  cache.put("key", "value");
      + *  cache.put("another key", "another value");
      + *
      + *  // We've specified no options on creation
      + *  expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 
      + * 
      + * 
      + * + * + * @param {string} cacheId Name or id of the newly created cache. + * @param {object=} options Options object that specifies the cache behavior. Properties: + * + * - `{number=}` `capacity` — turns the cache into LRU cache. + * + * @returns {object} Newly created cache object with the following set of methods: + * + * - `{object}` `info()` — Returns id, size, and options of cache. + * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns + * it. + * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss. + * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache. + * - `{void}` `removeAll()` — Removes all cached values. + * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory. + * + */ +function $CacheFactoryProvider() { + + this.$get = function() { + var caches = {}; + + function cacheFactory(cacheId, options) { + if (cacheId in caches) { + throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); + } + + var size = 0, + stats = extend({}, options, {id: cacheId}), + data = {}, + capacity = (options && options.capacity) || Number.MAX_VALUE, + lruHash = {}, + freshEnd = null, + staleEnd = null; + + return caches[cacheId] = { + + put: function(key, value) { + var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); + + refresh(lruEntry); + + if (isUndefined(value)) return; + if (!(key in data)) size++; + data[key] = value; + + if (size > capacity) { + this.remove(staleEnd.key); + } + + return value; + }, + + + get: function(key) { + var lruEntry = lruHash[key]; + + if (!lruEntry) return; + + refresh(lruEntry); + + return data[key]; + }, + + + remove: function(key) { + var lruEntry = lruHash[key]; + + if (!lruEntry) return; + + if (lruEntry == freshEnd) freshEnd = lruEntry.p; + if (lruEntry == staleEnd) staleEnd = lruEntry.n; + link(lruEntry.n,lruEntry.p); + + delete lruHash[key]; + delete data[key]; + size--; + }, + + + removeAll: function() { + data = {}; + size = 0; + lruHash = {}; + freshEnd = staleEnd = null; + }, + + + destroy: function() { + data = null; + stats = null; + lruHash = null; + delete caches[cacheId]; + }, + + + info: function() { + return extend({}, stats, {size: size}); + } + }; + + + /** + * makes the `entry` the freshEnd of the LRU linked list + */ + function refresh(entry) { + if (entry != freshEnd) { + if (!staleEnd) { + staleEnd = entry; + } else if (staleEnd == entry) { + staleEnd = entry.n; + } + + link(entry.n, entry.p); + link(entry, freshEnd); + freshEnd = entry; + freshEnd.n = null; + } + } + + + /** + * bidirectionally links two entries of the LRU linked list + */ + function link(nextEntry, prevEntry) { + if (nextEntry != prevEntry) { + if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify + if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify + } + } + } + + + /** + * @ngdoc method + * @name ng.$cacheFactory#info + * @methodOf ng.$cacheFactory + * + * @description + * Get information about all the of the caches that have been created + * + * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` + */ + cacheFactory.info = function() { + var info = {}; + forEach(caches, function(cache, cacheId) { + info[cacheId] = cache.info(); + }); + return info; + }; + + + /** + * @ngdoc method + * @name ng.$cacheFactory#get + * @methodOf ng.$cacheFactory + * + * @description + * Get access to a cache object by the `cacheId` used when it was created. + * + * @param {string} cacheId Name or id of a cache to access. + * @returns {object} Cache object identified by the cacheId or undefined if no such cache. + */ + cacheFactory.get = function(cacheId) { + return caches[cacheId]; + }; + + + return cacheFactory; + }; +} + +/** + * @ngdoc object + * @name ng.$templateCache + * + * @description + * The first time a template is used, it is loaded in the template cache for quick retrieval. You + * can load templates directly into the cache in a `script` tag, or by consuming the + * `$templateCache` service directly. + * + * Adding via the `script` tag: + *
      + * 
      + * 
      + * 
      + * 
      + *   ...
      + * 
      + * 
      + * + * **Note:** the `script` tag containing the template does not need to be included in the `head` of + * the document, but it must be below the `ng-app` definition. + * + * Adding via the $templateCache service: + * + *
      + * var myApp = angular.module('myApp', []);
      + * myApp.run(function($templateCache) {
      + *   $templateCache.put('templateId.html', 'This is the content of the template');
      + * });
      + * 
      + * + * To retrieve the template later, simply use it in your HTML: + *
      + * 
      + *
      + * + * or get it via Javascript: + *
      + * $templateCache.get('templateId.html')
      + * 
      + * + * See {@link ng.$cacheFactory $cacheFactory}. + * + */ +function $TemplateCacheProvider() { + this.$get = ['$cacheFactory', function($cacheFactory) { + return $cacheFactory('templates'); + }]; +} + +/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! + * + * DOM-related variables: + * + * - "node" - DOM Node + * - "element" - DOM Element or Node + * - "$node" or "$element" - jqLite-wrapped node or element + * + * + * Compiler related stuff: + * + * - "linkFn" - linking fn of a single directive + * - "nodeLinkFn" - function that aggregates all linking fns for a particular node + * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node + * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) + */ + + +/** + * @ngdoc function + * @name ng.$compile + * @function + * + * @description + * Compiles an HTML string or DOM into a template and produces a template function, which + * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. + * + * The compilation is a process of walking the DOM tree and matching DOM elements to + * {@link ng.$compileProvider#methods_directive directives}. + * + *
      + * **Note:** This document is an in-depth reference of all directive options. + * For a gentle introduction to directives with examples of common use cases, + * see the {@link guide/directive directive guide}. + *
      + * + * ## Comprehensive Directive API + * + * There are many different options for a directive. + * + * The difference resides in the return value of the factory function. + * You can either return a "Directive Definition Object" (see below) that defines the directive properties, + * or just the `postLink` function (all other properties will have the default values). + * + *
      + * **Best Practice:** It's recommended to use the "directive definition object" form. + *
      + * + * Here's an example directive declared with a Directive Definition Object: + * + *
      + *   var myModule = angular.module(...);
      + *
      + *   myModule.directive('directiveName', function factory(injectables) {
      + *     var directiveDefinitionObject = {
      + *       priority: 0,
      + *       template: '
      ', // or // function(tElement, tAttrs) { ... }, + * // or + * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, + * replace: false, + * transclude: false, + * restrict: 'A', + * scope: false, + * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, + * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], + * compile: function compile(tElement, tAttrs, transclude) { + * return { + * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, + * post: function postLink(scope, iElement, iAttrs, controller) { ... } + * } + * // or + * // return function postLink( ... ) { ... } + * }, + * // or + * // link: { + * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, + * // post: function postLink(scope, iElement, iAttrs, controller) { ... } + * // } + * // or + * // link: function postLink( ... ) { ... } + * }; + * return directiveDefinitionObject; + * }); + *
      + * + *
      + * **Note:** Any unspecified options will use the default value. You can see the default values below. + *
      + * + * Therefore the above can be simplified as: + * + *
      + *   var myModule = angular.module(...);
      + *
      + *   myModule.directive('directiveName', function factory(injectables) {
      + *     var directiveDefinitionObject = {
      + *       link: function postLink(scope, iElement, iAttrs) { ... }
      + *     };
      + *     return directiveDefinitionObject;
      + *     // or
      + *     // return function postLink(scope, iElement, iAttrs) { ... }
      + *   });
      + * 
      + * + * + * + * ### Directive Definition Object + * + * The directive definition object provides instructions to the {@link api/ng.$compile + * compiler}. The attributes are: + * + * #### `priority` + * When there are multiple directives defined on a single DOM element, sometimes it + * is necessary to specify the order in which the directives are applied. The `priority` is used + * to sort the directives before their `compile` functions get called. Priority is defined as a + * number. Directives with greater numerical `priority` are compiled first. Pre-link functions + * are also run in priority order, but post-link functions are run in reverse order. The order + * of directives with the same priority is undefined. The default priority is `0`. + * + * #### `terminal` + * If set to true then the current `priority` will be the last set of directives + * which will execute (any directives at the current priority will still execute + * as the order of execution on same `priority` is undefined). + * + * #### `scope` + * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the + * same element request a new scope, only one new scope is created. The new scope rule does not + * apply for the root of the template since the root of the template always gets a new scope. + * + * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from + * normal scope in that it does not prototypically inherit from the parent scope. This is useful + * when creating reusable components, which should not accidentally read or modify data in the + * parent scope. + * + * The 'isolate' scope takes an object hash which defines a set of local scope properties + * derived from the parent scope. These local properties are useful for aliasing values for + * templates. Locals definition is a hash of local scope property to its source: + * + * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is + * always a string since DOM attributes are strings. If no `attr` name is specified then the + * attribute name is assumed to be the same as the local name. + * Given `` and widget definition + * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect + * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the + * `localName` property on the widget scope. The `name` is read from the parent scope (not + * component scope). + * + * * `=` or `=attr` - set up bi-directional binding between a local scope property and the + * parent scope property of name defined via the value of the `attr` attribute. If no `attr` + * name is specified then the attribute name is assumed to be the same as the local name. + * Given `` and widget definition of + * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the + * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected + * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent + * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You + * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. + * + * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. + * If no `attr` name is specified then the attribute name is assumed to be the same as the + * local name. Given `` and widget definition of + * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to + * a function wrapper for the `count = count + value` expression. Often it's desirable to + * pass data from the isolated scope via an expression and to the parent scope, this can be + * done by passing a map of local variable names and values into the expression wrapper fn. + * For example, if the expression is `increment(amount)` then we can specify the amount value + * by calling the `localFn` as `localFn({amount: 22})`. + * + * + * + * #### `controller` + * Controller constructor function. The controller is instantiated before the + * pre-linking phase and it is shared with other directives (see + * `require` attribute). This allows the directives to communicate with each other and augment + * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: + * + * * `$scope` - Current scope associated with the element + * * `$element` - Current element + * * `$attrs` - Current attributes object for the element + * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope. + * The scope can be overridden by an optional first argument. + * `function([scope], cloneLinkingFn)`. + * + * + * #### `require` + * Require another directive and inject its controller as the fourth argument to the linking function. The + * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the + * injected argument will be an array in corresponding order. If no such directive can be + * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: + * + * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. + * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. + * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found. + * * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the + * `link` fn if not found. + * + * + * #### `controllerAs` + * Controller alias at the directive scope. An alias for the controller so it + * can be referenced at the directive template. The directive needs to define a scope for this + * configuration to be used. Useful in the case when directive is used as component. + * + * + * #### `restrict` + * String of subset of `EACM` which restricts the directive to a specific directive + * declaration style. If omitted, the default (attributes only) is used. + * + * * `E` - Element name: `` + * * `A` - Attribute (default): `
      ` + * * `C` - Class: `
      ` + * * `M` - Comment: `` + * + * + * #### `template` + * replace the current element with the contents of the HTML. The replacement process + * migrates all of the attributes / classes from the old element to the new one. See the + * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive + * Directives Guide} for an example. + * + * You can specify `template` as a string representing the template or as a function which takes + * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and + * returns a string value representing the template. + * + * + * #### `templateUrl` + * Same as `template` but the template is loaded from the specified URL. Because + * the template loading is asynchronous the compilation/linking is suspended until the template + * is loaded. + * + * You can specify `templateUrl` as a string representing the URL or as a function which takes two + * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns + * a string value representing the url. In either case, the template URL is passed through {@link + * api/ng.$sce#methods_getTrustedResourceUrl $sce.getTrustedResourceUrl}. + * + * + * #### `replace` + * specify where the template should be inserted. Defaults to `false`. + * + * * `true` - the template will replace the current element. + * * `false` - the template will replace the contents of the current element. + * + * + * #### `transclude` + * compile the content of the element and make it available to the directive. + * Typically used with {@link api/ng.directive:ngTransclude + * ngTransclude}. The advantage of transclusion is that the linking function receives a + * transclusion function which is pre-bound to the correct scope. In a typical setup the widget + * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` + * scope. This makes it possible for the widget to have private state, and the transclusion to + * be bound to the parent (pre-`isolate`) scope. + * + * * `true` - transclude the content of the directive. + * * `'element'` - transclude the whole element including any directives defined at lower priority. + * + * + * #### `compile` + * + *
      + *   function compile(tElement, tAttrs, transclude) { ... }
      + * 
      + * + * The compile function deals with transforming the template DOM. Since most directives do not do + * template transformation, it is not used often. Examples that require compile functions are + * directives that transform template DOM, such as {@link + * api/ng.directive:ngRepeat ngRepeat}, or load the contents + * asynchronously, such as {@link api/ngRoute.directive:ngView ngView}. The + * compile function takes the following arguments. + * + * * `tElement` - template element - The element where the directive has been declared. It is + * safe to do template transformation on the element and child elements only. + * + * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared + * between all directive compile functions. + * + * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` + * + *
      + * **Note:** The template instance and the link instance may be different objects if the template has + * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that + * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration + * should be done in a linking function rather than in a compile function. + *
      + * + *
      + * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it + * e.g. does not know about the right outer scope. Please use the transclude function that is passed + * to the link function instead. + *
      + + * A compile function can have a return value which can be either a function or an object. + * + * * returning a (post-link) function - is equivalent to registering the linking function via the + * `link` property of the config object when the compile function is empty. + * + * * returning an object with function(s) registered via `pre` and `post` properties - allows you to + * control when a linking function should be called during the linking phase. See info about + * pre-linking and post-linking functions below. + * + * + * #### `link` + * This property is used only if the `compile` property is not defined. + * + *
      + *   function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
      + * 
      + * + * The link function is responsible for registering DOM listeners as well as updating the DOM. It is + * executed after the template has been cloned. This is where most of the directive logic will be + * put. + * + * * `scope` - {@link api/ng.$rootScope.Scope Scope} - The scope to be used by the + * directive for registering {@link api/ng.$rootScope.Scope#methods_$watch watches}. + * + * * `iElement` - instance element - The element where the directive is to be used. It is safe to + * manipulate the children of the element only in `postLink` function since the children have + * already been linked. + * + * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared + * between all directive linking functions. + * + * * `controller` - a controller instance - A controller instance if at least one directive on the + * element defines a controller. The controller is shared among all the directives, which allows + * the directives to use the controllers as a communication channel. + * + * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. + * The scope can be overridden by an optional first argument. This is the same as the `$transclude` + * parameter of directive controllers. + * `function([scope], cloneLinkingFn)`. + * + * + * #### Pre-linking function + * + * Executed before the child elements are linked. Not safe to do DOM transformation since the + * compiler linking function will fail to locate the correct elements for linking. + * + * #### Post-linking function + * + * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. + * + * + * ### Attributes + * + * The {@link api/ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the + * `link()` or `compile()` functions. It has a variety of uses. + * + * accessing *Normalized attribute names:* + * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. + * the attributes object allows for normalized access to + * the attributes. + * + * * *Directive inter-communication:* All directives share the same instance of the attributes + * object which allows the directives to use the attributes object as inter directive + * communication. + * + * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object + * allowing other directives to read the interpolated value. + * + * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes + * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also + * the only way to easily get the actual value because during the linking phase the interpolation + * hasn't been evaluated yet and so the value is at this time set to `undefined`. + * + *
      + * function linkingFn(scope, elm, attrs, ctrl) {
      + *   // get the attribute value
      + *   console.log(attrs.ngModel);
      + *
      + *   // change the attribute
      + *   attrs.$set('ngModel', 'new value');
      + *
      + *   // observe changes to interpolated attribute
      + *   attrs.$observe('ngModel', function(value) {
      + *     console.log('ngModel has changed value to ' + value);
      + *   });
      + * }
      + * 
      + * + * Below is an example using `$compileProvider`. + * + *
      + * **Note**: Typically directives are registered with `module.directive`. The example below is + * to illustrate how `$compile` works. + *
      + * + + + +
      +
      +
      +
      +
      +
      + + it('should auto compile', function() { + expect(element('div[compile]').text()).toBe('Hello Angular'); + input('html').enter('{{name}}!'); + expect(element('div[compile]').text()).toBe('Angular!'); + }); + +
      + + * + * + * @param {string|DOMElement} element Element or HTML string to compile into a template function. + * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives. + * @param {number} maxPriority only apply directives lower then given priority (Only effects the + * root element(s), not their children) + * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template + * (a DOM element/tree) to a scope. Where: + * + * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. + * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the + * `template` and call the `cloneAttachFn` function allowing the caller to attach the + * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is + * called as:
      `cloneAttachFn(clonedElement, scope)` where: + * + * * `clonedElement` - is a clone of the original `element` passed into the compiler. + * * `scope` - is the current scope with which the linking function is working with. + * + * Calling the linking function returns the element of the template. It is either the original + * element passed in, or the clone of the element if the `cloneAttachFn` is provided. + * + * After linking the view is not updated until after a call to $digest which typically is done by + * Angular automatically. + * + * If you need access to the bound view, there are two ways to do it: + * + * - If you are not asking the linking function to clone the template, create the DOM element(s) + * before you send them to the compiler and keep this reference around. + *
      + *     var element = $compile('

      {{total}}

      ')(scope); + *
      + * + * - if on the other hand, you need the element to be cloned, the view reference from the original + * example would not point to the clone, but rather to the original template that was cloned. In + * this case, you can access the clone via the cloneAttachFn: + *
      + *     var templateElement = angular.element('

      {{total}}

      '), + * scope = ....; + * + * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { + * //attach the clone to DOM document at the right place + * }); + * + * //now we have reference to the cloned DOM via `clonedElement` + *
      + * + * + * For information on how the compiler works, see the + * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. + */ + +var $compileMinErr = minErr('$compile'); + +/** + * @ngdoc service + * @name ng.$compileProvider + * @function + * + * @description + */ +$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; +function $CompileProvider($provide, $$sanitizeUriProvider) { + var hasDirectives = {}, + Suffix = 'Directive', + COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, + CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/; + + // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes + // The assumption is that future DOM event attribute names will begin with + // 'on' and be composed of only English letters. + var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; + + /** + * @ngdoc function + * @name ng.$compileProvider#directive + * @methodOf ng.$compileProvider + * @function + * + * @description + * Register a new directive with the compiler. + * + * @param {string|Object} name Name of the directive in camel-case (i.e. ngBind which + * will match as ng-bind), or an object map of directives where the keys are the + * names and the values are the factories. + * @param {function|Array} directiveFactory An injectable directive factory function. See + * {@link guide/directive} for more info. + * @returns {ng.$compileProvider} Self for chaining. + */ + this.directive = function registerDirective(name, directiveFactory) { + assertNotHasOwnProperty(name, 'directive'); + if (isString(name)) { + assertArg(directiveFactory, 'directiveFactory'); + if (!hasDirectives.hasOwnProperty(name)) { + hasDirectives[name] = []; + $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', + function($injector, $exceptionHandler) { + var directives = []; + forEach(hasDirectives[name], function(directiveFactory, index) { + try { + var directive = $injector.invoke(directiveFactory); + if (isFunction(directive)) { + directive = { compile: valueFn(directive) }; + } else if (!directive.compile && directive.link) { + directive.compile = valueFn(directive.link); + } + directive.priority = directive.priority || 0; + directive.index = index; + directive.name = directive.name || name; + directive.require = directive.require || (directive.controller && directive.name); + directive.restrict = directive.restrict || 'A'; + directives.push(directive); + } catch (e) { + $exceptionHandler(e); + } + }); + return directives; + }]); + } + hasDirectives[name].push(directiveFactory); + } else { + forEach(name, reverseParams(registerDirective)); + } + return this; + }; + + + /** + * @ngdoc function + * @name ng.$compileProvider#aHrefSanitizationWhitelist + * @methodOf ng.$compileProvider + * @function + * + * @description + * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * urls during a[href] sanitization. + * + * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * + * Any url about to be assigned to a[href] via data-binding is first normalized and turned into + * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` + * regular expression. If a match is found, the original url is written into the dom. Otherwise, + * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. + * + * @param {RegExp=} regexp New regexp to whitelist urls with. + * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for + * chaining otherwise. + */ + this.aHrefSanitizationWhitelist = function(regexp) { + if (isDefined(regexp)) { + $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); + return this; + } else { + return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); + } + }; + + + /** + * @ngdoc function + * @name ng.$compileProvider#imgSrcSanitizationWhitelist + * @methodOf ng.$compileProvider + * @function + * + * @description + * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * urls during img[src] sanitization. + * + * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * + * Any url about to be assigned to img[src] via data-binding is first normalized and turned into + * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` + * regular expression. If a match is found, the original url is written into the dom. Otherwise, + * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. + * + * @param {RegExp=} regexp New regexp to whitelist urls with. + * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for + * chaining otherwise. + */ + this.imgSrcSanitizationWhitelist = function(regexp) { + if (isDefined(regexp)) { + $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); + return this; + } else { + return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); + } + }; + + this.$get = [ + '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', + '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', + function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, + $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { + + var Attributes = function(element, attr) { + this.$$element = element; + this.$attr = attr || {}; + }; + + Attributes.prototype = { + $normalize: directiveNormalize, + + + /** + * @ngdoc function + * @name ng.$compile.directive.Attributes#$addClass + * @methodOf ng.$compile.directive.Attributes + * @function + * + * @description + * Adds the CSS class value specified by the classVal parameter to the element. If animations + * are enabled then an animation will be triggered for the class addition. + * + * @param {string} classVal The className value that will be added to the element + */ + $addClass : function(classVal) { + if(classVal && classVal.length > 0) { + $animate.addClass(this.$$element, classVal); + } + }, + + /** + * @ngdoc function + * @name ng.$compile.directive.Attributes#$removeClass + * @methodOf ng.$compile.directive.Attributes + * @function + * + * @description + * Removes the CSS class value specified by the classVal parameter from the element. If + * animations are enabled then an animation will be triggered for the class removal. + * + * @param {string} classVal The className value that will be removed from the element + */ + $removeClass : function(classVal) { + if(classVal && classVal.length > 0) { + $animate.removeClass(this.$$element, classVal); + } + }, + + /** + * @ngdoc function + * @name ng.$compile.directive.Attributes#$updateClass + * @methodOf ng.$compile.directive.Attributes + * @function + * + * @description + * Adds and removes the appropriate CSS class values to the element based on the difference + * between the new and old CSS class values (specified as newClasses and oldClasses). + * + * @param {string} newClasses The current CSS className value + * @param {string} oldClasses The former CSS className value + */ + $updateClass : function(newClasses, oldClasses) { + this.$removeClass(tokenDifference(oldClasses, newClasses)); + this.$addClass(tokenDifference(newClasses, oldClasses)); + }, + + /** + * Set a normalized attribute on the element in a way such that all directives + * can share the attribute. This function properly handles boolean attributes. + * @param {string} key Normalized key. (ie ngAttribute) + * @param {string|boolean} value The value to set. If `null` attribute will be deleted. + * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. + * Defaults to true. + * @param {string=} attrName Optional none normalized name. Defaults to key. + */ + $set: function(key, value, writeAttr, attrName) { + // TODO: decide whether or not to throw an error if "class" + //is set through this function since it may cause $updateClass to + //become unstable. + + var booleanKey = getBooleanAttrName(this.$$element[0], key), + normalizedVal, + nodeName; + + if (booleanKey) { + this.$$element.prop(key, value); + attrName = booleanKey; + } + + this[key] = value; + + // translate normalized key to actual key + if (attrName) { + this.$attr[key] = attrName; + } else { + attrName = this.$attr[key]; + if (!attrName) { + this.$attr[key] = attrName = snake_case(key, '-'); + } + } + + nodeName = nodeName_(this.$$element); + + // sanitize a[href] and img[src] values + if ((nodeName === 'A' && key === 'href') || + (nodeName === 'IMG' && key === 'src')) { + this[key] = value = $$sanitizeUri(value, key === 'src'); + } + + if (writeAttr !== false) { + if (value === null || value === undefined) { + this.$$element.removeAttr(attrName); + } else { + this.$$element.attr(attrName, value); + } + } + + // fire observers + var $$observers = this.$$observers; + $$observers && forEach($$observers[key], function(fn) { + try { + fn(value); + } catch (e) { + $exceptionHandler(e); + } + }); + }, + + + /** + * @ngdoc function + * @name ng.$compile.directive.Attributes#$observe + * @methodOf ng.$compile.directive.Attributes + * @function + * + * @description + * Observes an interpolated attribute. + * + * The observer function will be invoked once during the next `$digest` following + * compilation. The observer is then invoked whenever the interpolated value + * changes. + * + * @param {string} key Normalized key. (ie ngAttribute) . + * @param {function(interpolatedValue)} fn Function that will be called whenever + the interpolated value of the attribute changes. + * See the {@link guide/directive#Attributes Directives} guide for more info. + * @returns {function()} the `fn` parameter. + */ + $observe: function(key, fn) { + var attrs = this, + $$observers = (attrs.$$observers || (attrs.$$observers = {})), + listeners = ($$observers[key] || ($$observers[key] = [])); + + listeners.push(fn); + $rootScope.$evalAsync(function() { + if (!listeners.$$inter) { + // no one registered attribute interpolation function, so lets call it manually + fn(attrs[key]); + } + }); + return fn; + } + }; + + var startSymbol = $interpolate.startSymbol(), + endSymbol = $interpolate.endSymbol(), + denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') + ? identity + : function denormalizeTemplate(template) { + return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); + }, + NG_ATTR_BINDING = /^ngAttr[A-Z]/; + + + return compile; + + //================================ + + function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, + previousCompileContext) { + if (!($compileNodes instanceof jqLite)) { + // jquery always rewraps, whereas we need to preserve the original selector so that we can + // modify it. + $compileNodes = jqLite($compileNodes); + } + // We can not compile top level text elements since text nodes can be merged and we will + // not be able to attach scope data to them, so we will wrap them in + forEach($compileNodes, function(node, index){ + if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { + $compileNodes[index] = node = jqLite(node).wrap('').parent()[0]; + } + }); + var compositeLinkFn = + compileNodes($compileNodes, transcludeFn, $compileNodes, + maxPriority, ignoreDirective, previousCompileContext); + safeAddClass($compileNodes, 'ng-scope'); + return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){ + assertArg(scope, 'scope'); + // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart + // and sometimes changes the structure of the DOM. + var $linkNode = cloneConnectFn + ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! + : $compileNodes; + + forEach(transcludeControllers, function(instance, name) { + $linkNode.data('$' + name + 'Controller', instance); + }); + + // Attach scope only to non-text nodes. + for(var i = 0, ii = $linkNode.length; i + addDirective(directives, + directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); + + // iterate over the attributes + for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes, + j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { + var attrStartName = false; + var attrEndName = false; + + attr = nAttrs[j]; + if (!msie || msie >= 8 || attr.specified) { + name = attr.name; + // support ngAttr attribute binding + ngAttrName = directiveNormalize(name); + if (NG_ATTR_BINDING.test(ngAttrName)) { + name = snake_case(ngAttrName.substr(6), '-'); + } + + var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); + if (ngAttrName === directiveNName + 'Start') { + attrStartName = name; + attrEndName = name.substr(0, name.length - 5) + 'end'; + name = name.substr(0, name.length - 6); + } + + nName = directiveNormalize(name.toLowerCase()); + attrsMap[nName] = name; + attrs[nName] = value = trim(attr.value); + if (getBooleanAttrName(node, nName)) { + attrs[nName] = true; // presence means true + } + addAttrInterpolateDirective(node, directives, value, nName); + addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, + attrEndName); + } + } + + // use class as directive + className = node.className; + if (isString(className) && className !== '') { + while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { + nName = directiveNormalize(match[2]); + if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { + attrs[nName] = trim(match[3]); + } + className = className.substr(match.index + match[0].length); + } + } + break; + case 3: /* Text Node */ + addTextInterpolateDirective(directives, node.nodeValue); + break; + case 8: /* Comment */ + try { + match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); + if (match) { + nName = directiveNormalize(match[1]); + if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { + attrs[nName] = trim(match[2]); + } + } + } catch (e) { + // turns out that under some circumstances IE9 throws errors when one attempts to read + // comment's node value. + // Just ignore it and continue. (Can't seem to reproduce in test case.) + } + break; + } + + directives.sort(byPriority); + return directives; + } + + /** + * Given a node with an directive-start it collects all of the siblings until it finds + * directive-end. + * @param node + * @param attrStart + * @param attrEnd + * @returns {*} + */ + function groupScan(node, attrStart, attrEnd) { + var nodes = []; + var depth = 0; + if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { + var startNode = node; + do { + if (!node) { + throw $compileMinErr('uterdir', + "Unterminated attribute, found '{0}' but no matching '{1}' found.", + attrStart, attrEnd); + } + if (node.nodeType == 1 /** Element **/) { + if (node.hasAttribute(attrStart)) depth++; + if (node.hasAttribute(attrEnd)) depth--; + } + nodes.push(node); + node = node.nextSibling; + } while (depth > 0); + } else { + nodes.push(node); + } + + return jqLite(nodes); + } + + /** + * Wrapper for linking function which converts normal linking function into a grouped + * linking function. + * @param linkFn + * @param attrStart + * @param attrEnd + * @returns {Function} + */ + function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { + return function(scope, element, attrs, controllers, transcludeFn) { + element = groupScan(element[0], attrStart, attrEnd); + return linkFn(scope, element, attrs, controllers, transcludeFn); + }; + } + + /** + * Once the directives have been collected, their compile functions are executed. This method + * is responsible for inlining directive templates as well as terminating the application + * of the directives if the terminal directive has been reached. + * + * @param {Array} directives Array of collected directives to execute their compile function. + * this needs to be pre-sorted by priority order. + * @param {Node} compileNode The raw DOM node to apply the compile functions to + * @param {Object} templateAttrs The shared attribute function + * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the + * scope argument is auto-generated to the new + * child of the transcluded parent scope. + * @param {JQLite} jqCollection If we are working on the root of the compile tree then this + * argument has the root jqLite array so that we can replace nodes + * on it. + * @param {Object=} originalReplaceDirective An optional directive that will be ignored when + * compiling the transclusion. + * @param {Array.} preLinkFns + * @param {Array.} postLinkFns + * @param {Object} previousCompileContext Context used for previous compilation of the current + * node + * @returns linkFn + */ + function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, + jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, + previousCompileContext) { + previousCompileContext = previousCompileContext || {}; + + var terminalPriority = -Number.MAX_VALUE, + newScopeDirective, + controllerDirectives = previousCompileContext.controllerDirectives, + newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, + templateDirective = previousCompileContext.templateDirective, + nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, + hasTranscludeDirective = false, + hasElementTranscludeDirective = false, + $compileNode = templateAttrs.$$element = jqLite(compileNode), + directive, + directiveName, + $template, + replaceDirective = originalReplaceDirective, + childTranscludeFn = transcludeFn, + linkFn, + directiveValue; + + // executes all directives on the current element + for(var i = 0, ii = directives.length; i < ii; i++) { + directive = directives[i]; + var attrStart = directive.$$start; + var attrEnd = directive.$$end; + + // collect multiblock sections + if (attrStart) { + $compileNode = groupScan(compileNode, attrStart, attrEnd); + } + $template = undefined; + + if (terminalPriority > directive.priority) { + break; // prevent further processing of directives + } + + if (directiveValue = directive.scope) { + newScopeDirective = newScopeDirective || directive; + + // skip the check for directives with async templates, we'll check the derived sync + // directive when the template arrives + if (!directive.templateUrl) { + assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, + $compileNode); + if (isObject(directiveValue)) { + newIsolateScopeDirective = directive; + } + } + } + + directiveName = directive.name; + + if (!directive.templateUrl && directive.controller) { + directiveValue = directive.controller; + controllerDirectives = controllerDirectives || {}; + assertNoDuplicate("'" + directiveName + "' controller", + controllerDirectives[directiveName], directive, $compileNode); + controllerDirectives[directiveName] = directive; + } + + if (directiveValue = directive.transclude) { + hasTranscludeDirective = true; + + // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. + // This option should only be used by directives that know how to how to safely handle element transclusion, + // where the transcluded nodes are added or replaced after linking. + if (!directive.$$tlb) { + assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); + nonTlbTranscludeDirective = directive; + } + + if (directiveValue == 'element') { + hasElementTranscludeDirective = true; + terminalPriority = directive.priority; + $template = groupScan(compileNode, attrStart, attrEnd); + $compileNode = templateAttrs.$$element = + jqLite(document.createComment(' ' + directiveName + ': ' + + templateAttrs[directiveName] + ' ')); + compileNode = $compileNode[0]; + replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode); + + childTranscludeFn = compile($template, transcludeFn, terminalPriority, + replaceDirective && replaceDirective.name, { + // Don't pass in: + // - controllerDirectives - otherwise we'll create duplicates controllers + // - newIsolateScopeDirective or templateDirective - combining templates with + // element transclusion doesn't make sense. + // + // We need only nonTlbTranscludeDirective so that we prevent putting transclusion + // on the same element more than once. + nonTlbTranscludeDirective: nonTlbTranscludeDirective + }); + } else { + $template = jqLite(jqLiteClone(compileNode)).contents(); + $compileNode.empty(); // clear contents + childTranscludeFn = compile($template, transcludeFn); + } + } + + if (directive.template) { + assertNoDuplicate('template', templateDirective, directive, $compileNode); + templateDirective = directive; + + directiveValue = (isFunction(directive.template)) + ? directive.template($compileNode, templateAttrs) + : directive.template; + + directiveValue = denormalizeTemplate(directiveValue); + + if (directive.replace) { + replaceDirective = directive; + $template = jqLite('
      ' + + trim(directiveValue) + + '
      ').contents(); + compileNode = $template[0]; + + if ($template.length != 1 || compileNode.nodeType !== 1) { + throw $compileMinErr('tplrt', + "Template for directive '{0}' must have exactly one root element. {1}", + directiveName, ''); + } + + replaceWith(jqCollection, $compileNode, compileNode); + + var newTemplateAttrs = {$attr: {}}; + + // combine directives from the original node and from the template: + // - take the array of directives for this element + // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) + // - collect directives from the template and sort them by priority + // - combine directives as: processed + template + unprocessed + var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); + var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); + + if (newIsolateScopeDirective) { + markDirectivesAsIsolate(templateDirectives); + } + directives = directives.concat(templateDirectives).concat(unprocessedDirectives); + mergeTemplateAttributes(templateAttrs, newTemplateAttrs); + + ii = directives.length; + } else { + $compileNode.html(directiveValue); + } + } + + if (directive.templateUrl) { + assertNoDuplicate('template', templateDirective, directive, $compileNode); + templateDirective = directive; + + if (directive.replace) { + replaceDirective = directive; + } + + nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, + templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, { + controllerDirectives: controllerDirectives, + newIsolateScopeDirective: newIsolateScopeDirective, + templateDirective: templateDirective, + nonTlbTranscludeDirective: nonTlbTranscludeDirective + }); + ii = directives.length; + } else if (directive.compile) { + try { + linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); + if (isFunction(linkFn)) { + addLinkFns(null, linkFn, attrStart, attrEnd); + } else if (linkFn) { + addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); + } + } catch (e) { + $exceptionHandler(e, startingTag($compileNode)); + } + } + + if (directive.terminal) { + nodeLinkFn.terminal = true; + terminalPriority = Math.max(terminalPriority, directive.priority); + } + + } + + nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; + nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn; + + // might be normal or delayed nodeLinkFn depending on if templateUrl is present + return nodeLinkFn; + + //////////////////// + + function addLinkFns(pre, post, attrStart, attrEnd) { + if (pre) { + if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); + pre.require = directive.require; + if (newIsolateScopeDirective === directive || directive.$$isolateScope) { + pre = cloneAndAnnotateFn(pre, {isolateScope: true}); + } + preLinkFns.push(pre); + } + if (post) { + if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); + post.require = directive.require; + if (newIsolateScopeDirective === directive || directive.$$isolateScope) { + post = cloneAndAnnotateFn(post, {isolateScope: true}); + } + postLinkFns.push(post); + } + } + + + function getControllers(require, $element, elementControllers) { + var value, retrievalMethod = 'data', optional = false; + if (isString(require)) { + while((value = require.charAt(0)) == '^' || value == '?') { + require = require.substr(1); + if (value == '^') { + retrievalMethod = 'inheritedData'; + } + optional = optional || value == '?'; + } + value = null; + + if (elementControllers && retrievalMethod === 'data') { + value = elementControllers[require]; + } + value = value || $element[retrievalMethod]('$' + require + 'Controller'); + + if (!value && !optional) { + throw $compileMinErr('ctreq', + "Controller '{0}', required by directive '{1}', can't be found!", + require, directiveName); + } + return value; + } else if (isArray(require)) { + value = []; + forEach(require, function(require) { + value.push(getControllers(require, $element, elementControllers)); + }); + } + return value; + } + + + function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { + var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn; + + if (compileNode === linkNode) { + attrs = templateAttrs; + } else { + attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); + } + $element = attrs.$$element; + + if (newIsolateScopeDirective) { + var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; + var $linkNode = jqLite(linkNode); + + isolateScope = scope.$new(true); + + if (templateDirective && (templateDirective === newIsolateScopeDirective.$$originalDirective)) { + $linkNode.data('$isolateScope', isolateScope) ; + } else { + $linkNode.data('$isolateScopeNoTemplate', isolateScope); + } + + + + safeAddClass($linkNode, 'ng-isolate-scope'); + + forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { + var match = definition.match(LOCAL_REGEXP) || [], + attrName = match[3] || scopeName, + optional = (match[2] == '?'), + mode = match[1], // @, =, or & + lastValue, + parentGet, parentSet, compare; + + isolateScope.$$isolateBindings[scopeName] = mode + attrName; + + switch (mode) { + + case '@': + attrs.$observe(attrName, function(value) { + isolateScope[scopeName] = value; + }); + attrs.$$observers[attrName].$$scope = scope; + if( attrs[attrName] ) { + // If the attribute has been provided then we trigger an interpolation to ensure + // the value is there for use in the link fn + isolateScope[scopeName] = $interpolate(attrs[attrName])(scope); + } + break; + + case '=': + if (optional && !attrs[attrName]) { + return; + } + parentGet = $parse(attrs[attrName]); + if (parentGet.literal) { + compare = equals; + } else { + compare = function(a,b) { return a === b; }; + } + parentSet = parentGet.assign || function() { + // reset the change, or we will throw this exception on every $digest + lastValue = isolateScope[scopeName] = parentGet(scope); + throw $compileMinErr('nonassign', + "Expression '{0}' used with directive '{1}' is non-assignable!", + attrs[attrName], newIsolateScopeDirective.name); + }; + lastValue = isolateScope[scopeName] = parentGet(scope); + isolateScope.$watch(function parentValueWatch() { + var parentValue = parentGet(scope); + if (!compare(parentValue, isolateScope[scopeName])) { + // we are out of sync and need to copy + if (!compare(parentValue, lastValue)) { + // parent changed and it has precedence + isolateScope[scopeName] = parentValue; + } else { + // if the parent can be assigned then do so + parentSet(scope, parentValue = isolateScope[scopeName]); + } + } + return lastValue = parentValue; + }, null, parentGet.literal); + break; + + case '&': + parentGet = $parse(attrs[attrName]); + isolateScope[scopeName] = function(locals) { + return parentGet(scope, locals); + }; + break; + + default: + throw $compileMinErr('iscp', + "Invalid isolate scope definition for directive '{0}'." + + " Definition: {... {1}: '{2}' ...}", + newIsolateScopeDirective.name, scopeName, definition); + } + }); + } + transcludeFn = boundTranscludeFn && controllersBoundTransclude; + if (controllerDirectives) { + forEach(controllerDirectives, function(directive) { + var locals = { + $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, + $element: $element, + $attrs: attrs, + $transclude: transcludeFn + }, controllerInstance; + + controller = directive.controller; + if (controller == '@') { + controller = attrs[directive.name]; + } + + controllerInstance = $controller(controller, locals); + // For directives with element transclusion the element is a comment, + // but jQuery .data doesn't support attaching data to comment nodes as it's hard to + // clean up (http://bugs.jquery.com/ticket/8335). + // Instead, we save the controllers for the element in a local hash and attach to .data + // later, once we have the actual element. + elementControllers[directive.name] = controllerInstance; + if (!hasElementTranscludeDirective) { + $element.data('$' + directive.name + 'Controller', controllerInstance); + } + + if (directive.controllerAs) { + locals.$scope[directive.controllerAs] = controllerInstance; + } + }); + } + + // PRELINKING + for(i = 0, ii = preLinkFns.length; i < ii; i++) { + try { + linkFn = preLinkFns[i]; + linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, + linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); + } catch (e) { + $exceptionHandler(e, startingTag($element)); + } + } + + // RECURSION + // We only pass the isolate scope, if the isolate directive has a template, + // otherwise the child elements do not belong to the isolate directive. + var scopeToChild = scope; + if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { + scopeToChild = isolateScope; + } + childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); + + // POSTLINKING + for(i = postLinkFns.length - 1; i >= 0; i--) { + try { + linkFn = postLinkFns[i]; + linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, + linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); + } catch (e) { + $exceptionHandler(e, startingTag($element)); + } + } + + // This is the function that is injected as `$transclude`. + function controllersBoundTransclude(scope, cloneAttachFn) { + var transcludeControllers; + + // no scope passed + if (arguments.length < 2) { + cloneAttachFn = scope; + scope = undefined; + } + + if (hasElementTranscludeDirective) { + transcludeControllers = elementControllers; + } + + return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); + } + } + } + + function markDirectivesAsIsolate(directives) { + // mark all directives as needing isolate scope. + for (var j = 0, jj = directives.length; j < jj; j++) { + directives[j] = inherit(directives[j], {$$isolateScope: true}); + } + } + + /** + * looks up the directive and decorates it with exception handling and proper parameters. We + * call this the boundDirective. + * + * @param {string} name name of the directive to look up. + * @param {string} location The directive must be found in specific format. + * String containing any of theses characters: + * + * * `E`: element name + * * `A': attribute + * * `C`: class + * * `M`: comment + * @returns true if directive was added. + */ + function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, + endAttrName) { + if (name === ignoreDirective) return null; + var match = null; + if (hasDirectives.hasOwnProperty(name)) { + for(var directive, directives = $injector.get(name + Suffix), + i = 0, ii = directives.length; i directive.priority) && + directive.restrict.indexOf(location) != -1) { + if (startAttrName) { + directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); + } + tDirectives.push(directive); + match = directive; + } + } catch(e) { $exceptionHandler(e); } + } + } + return match; + } + + + /** + * When the element is replaced with HTML template then the new attributes + * on the template need to be merged with the existing attributes in the DOM. + * The desired effect is to have both of the attributes present. + * + * @param {object} dst destination attributes (original DOM) + * @param {object} src source attributes (from the directive template) + */ + function mergeTemplateAttributes(dst, src) { + var srcAttr = src.$attr, + dstAttr = dst.$attr, + $element = dst.$$element; + + // reapply the old attributes to the new element + forEach(dst, function(value, key) { + if (key.charAt(0) != '$') { + if (src[key]) { + value += (key === 'style' ? ';' : ' ') + src[key]; + } + dst.$set(key, value, true, srcAttr[key]); + } + }); + + // copy the new attributes on the old attrs object + forEach(src, function(value, key) { + if (key == 'class') { + safeAddClass($element, value); + dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; + } else if (key == 'style') { + $element.attr('style', $element.attr('style') + ';' + value); + dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; + // `dst` will never contain hasOwnProperty as DOM parser won't let it. + // You will get an "InvalidCharacterError: DOM Exception 5" error if you + // have an attribute like "has-own-property" or "data-has-own-property", etc. + } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { + dst[key] = value; + dstAttr[key] = srcAttr[key]; + } + }); + } + + + function compileTemplateUrl(directives, $compileNode, tAttrs, + $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { + var linkQueue = [], + afterTemplateNodeLinkFn, + afterTemplateChildLinkFn, + beforeTemplateCompileNode = $compileNode[0], + origAsyncDirective = directives.shift(), + // The fact that we have to copy and patch the directive seems wrong! + derivedSyncDirective = extend({}, origAsyncDirective, { + templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective + }), + templateUrl = (isFunction(origAsyncDirective.templateUrl)) + ? origAsyncDirective.templateUrl($compileNode, tAttrs) + : origAsyncDirective.templateUrl; + + $compileNode.empty(); + + $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). + success(function(content) { + var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; + + content = denormalizeTemplate(content); + + if (origAsyncDirective.replace) { + $template = jqLite('
      ' + trim(content) + '
      ').contents(); + compileNode = $template[0]; + + if ($template.length != 1 || compileNode.nodeType !== 1) { + throw $compileMinErr('tplrt', + "Template for directive '{0}' must have exactly one root element. {1}", + origAsyncDirective.name, templateUrl); + } + + tempTemplateAttrs = {$attr: {}}; + replaceWith($rootElement, $compileNode, compileNode); + var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); + + if (isObject(origAsyncDirective.scope)) { + markDirectivesAsIsolate(templateDirectives); + } + directives = templateDirectives.concat(directives); + mergeTemplateAttributes(tAttrs, tempTemplateAttrs); + } else { + compileNode = beforeTemplateCompileNode; + $compileNode.html(content); + } + + directives.unshift(derivedSyncDirective); + + afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, + childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, + previousCompileContext); + forEach($rootElement, function(node, i) { + if (node == compileNode) { + $rootElement[i] = $compileNode[0]; + } + }); + afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); + + + while(linkQueue.length) { + var scope = linkQueue.shift(), + beforeTemplateLinkNode = linkQueue.shift(), + linkRootElement = linkQueue.shift(), + boundTranscludeFn = linkQueue.shift(), + linkNode = $compileNode[0]; + + if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { + // it was cloned therefore we have to clone as well. + linkNode = jqLiteClone(compileNode); + replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); + } + if (afterTemplateNodeLinkFn.transclude) { + childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude); + } else { + childBoundTranscludeFn = boundTranscludeFn; + } + afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, + childBoundTranscludeFn); + } + linkQueue = null; + }). + error(function(response, code, headers, config) { + throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); + }); + + return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { + if (linkQueue) { + linkQueue.push(scope); + linkQueue.push(node); + linkQueue.push(rootElement); + linkQueue.push(boundTranscludeFn); + } else { + afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn); + } + }; + } + + + /** + * Sorting function for bound directives. + */ + function byPriority(a, b) { + var diff = b.priority - a.priority; + if (diff !== 0) return diff; + if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; + return a.index - b.index; + } + + + function assertNoDuplicate(what, previousDirective, directive, element) { + if (previousDirective) { + throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', + previousDirective.name, directive.name, what, startingTag(element)); + } + } + + + function addTextInterpolateDirective(directives, text) { + var interpolateFn = $interpolate(text, true); + if (interpolateFn) { + directives.push({ + priority: 0, + compile: valueFn(function textInterpolateLinkFn(scope, node) { + var parent = node.parent(), + bindings = parent.data('$binding') || []; + bindings.push(interpolateFn); + safeAddClass(parent.data('$binding', bindings), 'ng-binding'); + scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { + node[0].nodeValue = value; + }); + }) + }); + } + } + + + function getTrustedContext(node, attrNormalizedName) { + if (attrNormalizedName == "srcdoc") { + return $sce.HTML; + } + var tag = nodeName_(node); + // maction[xlink:href] can source SVG. It's not limited to . + if (attrNormalizedName == "xlinkHref" || + (tag == "FORM" && attrNormalizedName == "action") || + (tag != "IMG" && (attrNormalizedName == "src" || + attrNormalizedName == "ngSrc"))) { + return $sce.RESOURCE_URL; + } + } + + + function addAttrInterpolateDirective(node, directives, value, name) { + var interpolateFn = $interpolate(value, true); + + // no interpolation found -> ignore + if (!interpolateFn) return; + + + if (name === "multiple" && nodeName_(node) === "SELECT") { + throw $compileMinErr("selmulti", + "Binding to the 'multiple' attribute is not supported. Element: {0}", + startingTag(node)); + } + + directives.push({ + priority: 100, + compile: function() { + return { + pre: function attrInterpolatePreLinkFn(scope, element, attr) { + var $$observers = (attr.$$observers || (attr.$$observers = {})); + + if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { + throw $compileMinErr('nodomevents', + "Interpolations for HTML DOM event attributes are disallowed. Please use the " + + "ng- versions (such as ng-click instead of onclick) instead."); + } + + // we need to interpolate again, in case the attribute value has been updated + // (e.g. by another directive's compile function) + interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name)); + + // if attribute was updated so that there is no interpolation going on we don't want to + // register any observers + if (!interpolateFn) return; + + // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the + // actual attr value + attr[name] = interpolateFn(scope); + ($$observers[name] || ($$observers[name] = [])).$$inter = true; + (attr.$$observers && attr.$$observers[name].$$scope || scope). + $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { + //special case for class attribute addition + removal + //so that class changes can tap into the animation + //hooks provided by the $animate service. Be sure to + //skip animations when the first digest occurs (when + //both the new and the old values are the same) since + //the CSS classes are the non-interpolated values + if(name === 'class' && newValue != oldValue) { + attr.$updateClass(newValue, oldValue); + } else { + attr.$set(name, newValue); + } + }); + } + }; + } + }); + } + + + /** + * This is a special jqLite.replaceWith, which can replace items which + * have no parents, provided that the containing jqLite collection is provided. + * + * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes + * in the root of the tree. + * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep + * the shell, but replace its DOM node reference. + * @param {Node} newNode The new DOM node. + */ + function replaceWith($rootElement, elementsToRemove, newNode) { + var firstElementToRemove = elementsToRemove[0], + removeCount = elementsToRemove.length, + parent = firstElementToRemove.parentNode, + i, ii; + + if ($rootElement) { + for(i = 0, ii = $rootElement.length; i < ii; i++) { + if ($rootElement[i] == firstElementToRemove) { + $rootElement[i++] = newNode; + for (var j = i, j2 = j + removeCount - 1, + jj = $rootElement.length; + j < jj; j++, j2++) { + if (j2 < jj) { + $rootElement[j] = $rootElement[j2]; + } else { + delete $rootElement[j]; + } + } + $rootElement.length -= removeCount - 1; + break; + } + } + } + + if (parent) { + parent.replaceChild(newNode, firstElementToRemove); + } + var fragment = document.createDocumentFragment(); + fragment.appendChild(firstElementToRemove); + newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; + for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { + var element = elementsToRemove[k]; + jqLite(element).remove(); // must do this way to clean up expando + fragment.appendChild(element); + delete elementsToRemove[k]; + } + + elementsToRemove[0] = newNode; + elementsToRemove.length = 1; + } + + + function cloneAndAnnotateFn(fn, annotation) { + return extend(function() { return fn.apply(null, arguments); }, fn, annotation); + } + }]; +} + +var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; +/** + * Converts all accepted directives format into proper directive name. + * All of these will become 'myDirective': + * my:Directive + * my-directive + * x-my-directive + * data-my:directive + * + * Also there is special case for Moz prefix starting with upper case letter. + * @param name Name to normalize + */ +function directiveNormalize(name) { + return camelCase(name.replace(PREFIX_REGEXP, '')); +} + +/** + * @ngdoc object + * @name ng.$compile.directive.Attributes + * + * @description + * A shared object between directive compile / linking functions which contains normalized DOM + * element attributes. The values reflect current binding state `{{ }}`. The normalization is + * needed since all of these are treated as equivalent in Angular: + * + * + */ + +/** + * @ngdoc property + * @name ng.$compile.directive.Attributes#$attr + * @propertyOf ng.$compile.directive.Attributes + * @returns {object} A map of DOM element attribute names to the normalized name. This is + * needed to do reverse lookup from normalized name back to actual name. + */ + + +/** + * @ngdoc function + * @name ng.$compile.directive.Attributes#$set + * @methodOf ng.$compile.directive.Attributes + * @function + * + * @description + * Set DOM element attribute value. + * + * + * @param {string} name Normalized element attribute name of the property to modify. The name is + * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} + * property to the original name. + * @param {string} value Value to set the attribute to. The value can be an interpolated string. + */ + + + +/** + * Closure compiler type information + */ + +function nodesetLinkingFn( + /* angular.Scope */ scope, + /* NodeList */ nodeList, + /* Element */ rootElement, + /* function(Function) */ boundTranscludeFn +){} + +function directiveLinkingFn( + /* nodesetLinkingFn */ nodesetLinkingFn, + /* angular.Scope */ scope, + /* Node */ node, + /* Element */ rootElement, + /* function(Function) */ boundTranscludeFn +){} + +function tokenDifference(str1, str2) { + var values = '', + tokens1 = str1.split(/\s+/), + tokens2 = str2.split(/\s+/); + + outer: + for(var i = 0; i < tokens1.length; i++) { + var token = tokens1[i]; + for(var j = 0; j < tokens2.length; j++) { + if(token == tokens2[j]) continue outer; + } + values += (values.length > 0 ? ' ' : '') + token; + } + return values; +} + +/** + * @ngdoc object + * @name ng.$controllerProvider + * @description + * The {@link ng.$controller $controller service} is used by Angular to create new + * controllers. + * + * This provider allows controller registration via the + * {@link ng.$controllerProvider#methods_register register} method. + */ +function $ControllerProvider() { + var controllers = {}, + CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; + + + /** + * @ngdoc function + * @name ng.$controllerProvider#register + * @methodOf ng.$controllerProvider + * @param {string|Object} name Controller name, or an object map of controllers where the keys are + * the names and the values are the constructors. + * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI + * annotations in the array notation). + */ + this.register = function(name, constructor) { + assertNotHasOwnProperty(name, 'controller'); + if (isObject(name)) { + extend(controllers, name); + } else { + controllers[name] = constructor; + } + }; + + + this.$get = ['$injector', '$window', function($injector, $window) { + + /** + * @ngdoc function + * @name ng.$controller + * @requires $injector + * + * @param {Function|string} constructor If called with a function then it's considered to be the + * controller constructor function. Otherwise it's considered to be a string which is used + * to retrieve the controller constructor using the following steps: + * + * * check if a controller with given name is registered via `$controllerProvider` + * * check if evaluating the string on the current scope returns a constructor + * * check `window[constructor]` on the global `window` object + * + * @param {Object} locals Injection locals for Controller. + * @return {Object} Instance of given controller. + * + * @description + * `$controller` service is responsible for instantiating controllers. + * + * It's just a simple call to {@link AUTO.$injector $injector}, but extracted into + * a service, so that one can override this service with {@link https://gist.github.com/1649788 + * BC version}. + */ + return function(expression, locals) { + var instance, match, constructor, identifier; + + if(isString(expression)) { + match = expression.match(CNTRL_REG), + constructor = match[1], + identifier = match[3]; + expression = controllers.hasOwnProperty(constructor) + ? controllers[constructor] + : getter(locals.$scope, constructor, true) || getter($window, constructor, true); + + assertArgFn(expression, constructor, true); + } + + instance = $injector.instantiate(expression, locals); + + if (identifier) { + if (!(locals && typeof locals.$scope == 'object')) { + throw minErr('$controller')('noscp', + "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", + constructor || expression.name, identifier); + } + + locals.$scope[identifier] = instance; + } + + return instance; + }; + }]; +} + +/** + * @ngdoc object + * @name ng.$document + * @requires $window + * + * @description + * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. + */ +function $DocumentProvider(){ + this.$get = ['$window', function(window){ + return jqLite(window.document); + }]; +} + +/** + * @ngdoc function + * @name ng.$exceptionHandler + * @requires $log + * + * @description + * Any uncaught exception in angular expressions is delegated to this service. + * The default implementation simply delegates to `$log.error` which logs it into + * the browser console. + * + * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by + * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. + * + * ## Example: + * + *
      + *   angular.module('exceptionOverride', []).factory('$exceptionHandler', function () {
      + *     return function (exception, cause) {
      + *       exception.message += ' (caused by "' + cause + '")';
      + *       throw exception;
      + *     };
      + *   });
      + * 
      + * + * This example will override the normal action of `$exceptionHandler`, to make angular + * exceptions fail hard when they happen, instead of just logging to the console. + * + * @param {Error} exception Exception associated with the error. + * @param {string=} cause optional information about the context in which + * the error was thrown. + * + */ +function $ExceptionHandlerProvider() { + this.$get = ['$log', function($log) { + return function(exception, cause) { + $log.error.apply($log, arguments); + }; + }]; +} + +/** + * Parse headers into key value object + * + * @param {string} headers Raw headers as a string + * @returns {Object} Parsed headers as key value object + */ +function parseHeaders(headers) { + var parsed = {}, key, val, i; + + if (!headers) return parsed; + + forEach(headers.split('\n'), function(line) { + i = line.indexOf(':'); + key = lowercase(trim(line.substr(0, i))); + val = trim(line.substr(i + 1)); + + if (key) { + if (parsed[key]) { + parsed[key] += ', ' + val; + } else { + parsed[key] = val; + } + } + }); + + return parsed; +} + + +/** + * Returns a function that provides access to parsed headers. + * + * Headers are lazy parsed when first requested. + * @see parseHeaders + * + * @param {(string|Object)} headers Headers to provide access to. + * @returns {function(string=)} Returns a getter function which if called with: + * + * - if called with single an argument returns a single header value or null + * - if called with no arguments returns an object containing all headers. + */ +function headersGetter(headers) { + var headersObj = isObject(headers) ? headers : undefined; + + return function(name) { + if (!headersObj) headersObj = parseHeaders(headers); + + if (name) { + return headersObj[lowercase(name)] || null; + } + + return headersObj; + }; +} + + +/** + * Chain all given functions + * + * This function is used for both request and response transforming + * + * @param {*} data Data to transform. + * @param {function(string=)} headers Http headers getter fn. + * @param {(function|Array.)} fns Function or an array of functions. + * @returns {*} Transformed data. + */ +function transformData(data, headers, fns) { + if (isFunction(fns)) + return fns(data, headers); + + forEach(fns, function(fn) { + data = fn(data, headers); + }); + + return data; +} + + +function isSuccess(status) { + return 200 <= status && status < 300; +} + + +function $HttpProvider() { + var JSON_START = /^\s*(\[|\{[^\{])/, + JSON_END = /[\}\]]\s*$/, + PROTECTION_PREFIX = /^\)\]\}',?\n/, + CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; + + var defaults = this.defaults = { + // transform incoming response data + transformResponse: [function(data) { + if (isString(data)) { + // strip json vulnerability protection prefix + data = data.replace(PROTECTION_PREFIX, ''); + if (JSON_START.test(data) && JSON_END.test(data)) + data = fromJson(data); + } + return data; + }], + + // transform outgoing request data + transformRequest: [function(d) { + return isObject(d) && !isFile(d) ? toJson(d) : d; + }], + + // default headers + headers: { + common: { + 'Accept': 'application/json, text/plain, */*' + }, + post: copy(CONTENT_TYPE_APPLICATION_JSON), + put: copy(CONTENT_TYPE_APPLICATION_JSON), + patch: copy(CONTENT_TYPE_APPLICATION_JSON) + }, + + xsrfCookieName: 'XSRF-TOKEN', + xsrfHeaderName: 'X-XSRF-TOKEN' + }; + + /** + * Are ordered by request, i.e. they are applied in the same order as the + * array, on request, but reverse order, on response. + */ + var interceptorFactories = this.interceptors = []; + + /** + * For historical reasons, response interceptors are ordered by the order in which + * they are applied to the response. (This is the opposite of interceptorFactories) + */ + var responseInterceptorFactories = this.responseInterceptors = []; + + this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', + function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { + + var defaultCache = $cacheFactory('$http'); + + /** + * Interceptors stored in reverse order. Inner interceptors before outer interceptors. + * The reversal is needed so that we can build up the interception chain around the + * server request. + */ + var reversedInterceptors = []; + + forEach(interceptorFactories, function(interceptorFactory) { + reversedInterceptors.unshift(isString(interceptorFactory) + ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); + }); + + forEach(responseInterceptorFactories, function(interceptorFactory, index) { + var responseFn = isString(interceptorFactory) + ? $injector.get(interceptorFactory) + : $injector.invoke(interceptorFactory); + + /** + * Response interceptors go before "around" interceptors (no real reason, just + * had to pick one.) But they are already reversed, so we can't use unshift, hence + * the splice. + */ + reversedInterceptors.splice(index, 0, { + response: function(response) { + return responseFn($q.when(response)); + }, + responseError: function(response) { + return responseFn($q.reject(response)); + } + }); + }); + + + /** + * @ngdoc function + * @name ng.$http + * @requires $httpBackend + * @requires $browser + * @requires $cacheFactory + * @requires $rootScope + * @requires $q + * @requires $injector + * + * @description + * The `$http` service is a core Angular service that facilitates communication with the remote + * HTTP servers via the browser's {@link https://developer.mozilla.org/en/xmlhttprequest + * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}. + * + * For unit testing applications that use `$http` service, see + * {@link ngMock.$httpBackend $httpBackend mock}. + * + * For a higher level of abstraction, please check out the {@link ngResource.$resource + * $resource} service. + * + * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by + * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage + * it is important to familiarize yourself with these APIs and the guarantees they provide. + * + * + * # General usage + * The `$http` service is a function which takes a single argument — a configuration object — + * that is used to generate an HTTP request and returns a {@link ng.$q promise} + * with two $http specific methods: `success` and `error`. + * + *
      +     *   $http({method: 'GET', url: '/someUrl'}).
      +     *     success(function(data, status, headers, config) {
      +     *       // this callback will be called asynchronously
      +     *       // when the response is available
      +     *     }).
      +     *     error(function(data, status, headers, config) {
      +     *       // called asynchronously if an error occurs
      +     *       // or server returns response with an error status.
      +     *     });
      +     * 
      + * + * Since the returned value of calling the $http function is a `promise`, you can also use + * the `then` method to register callbacks, and these callbacks will receive a single argument – + * an object representing the response. See the API signature and type info below for more + * details. + * + * A response status code between 200 and 299 is considered a success status and + * will result in the success callback being called. Note that if the response is a redirect, + * XMLHttpRequest will transparently follow it, meaning that the error callback will not be + * called for such responses. + * + * # Calling $http from outside AngularJS + * The `$http` service will not actually send the request until the next `$digest()` is + * executed. Normally this is not an issue, since almost all the time your call to `$http` will + * be from within a `$apply()` block. + * If you are calling `$http` from outside Angular, then you should wrap it in a call to + * `$apply` to cause a $digest to occur and also to handle errors in the block correctly. + * + * ``` + * $scope.$apply(function() { + * $http(...); + * }); + * ``` + * + * # Writing Unit Tests that use $http + * When unit testing you are mostly responsible for scheduling the `$digest` cycle. If you do + * not trigger a `$digest` before calling `$httpBackend.flush()` then the request will not have + * been made and `$httpBackend.expect(...)` expectations will fail. The solution is to run the + * code that calls the `$http()` method inside a $apply block as explained in the previous + * section. + * + * ``` + * $httpBackend.expectGET(...); + * $scope.$apply(function() { + * $http.get(...); + * }); + * $httpBackend.flush(); + * ``` + * + * # Shortcut methods + * + * Since all invocations of the $http service require passing in an HTTP method and URL, and + * POST/PUT requests require request data to be provided as well, shortcut methods + * were created: + * + *
      +     *   $http.get('/someUrl').success(successCallback);
      +     *   $http.post('/someUrl', data).success(successCallback);
      +     * 
      + * + * Complete list of shortcut methods: + * + * - {@link ng.$http#methods_get $http.get} + * - {@link ng.$http#methods_head $http.head} + * - {@link ng.$http#methods_post $http.post} + * - {@link ng.$http#methods_put $http.put} + * - {@link ng.$http#methods_delete $http.delete} + * - {@link ng.$http#methods_jsonp $http.jsonp} + * + * + * # Setting HTTP Headers + * + * The $http service will automatically add certain HTTP headers to all requests. These defaults + * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration + * object, which currently contains this default configuration: + * + * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): + * - `Accept: application/json, text/plain, * / *` + * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) + * - `Content-Type: application/json` + * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) + * - `Content-Type: application/json` + * + * To add or overwrite these defaults, simply add or remove a property from these configuration + * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object + * with the lowercased HTTP method name as the key, e.g. + * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. + * + * The defaults can also be set at runtime via the `$http.defaults` object in the same + * fashion. For example: + * + * ``` + * module.run(function($http) { + * $http.defaults.headers.common.Authentication = 'Basic YmVlcDpib29w' + * }); + * ``` + * + * In addition, you can supply a `headers` property in the config object passed when + * calling `$http(config)`, which overrides the defaults without changing them globally. + * + * + * # Transforming Requests and Responses + * + * Both requests and responses can be transformed using transform functions. By default, Angular + * applies these transformations: + * + * Request transformations: + * + * - If the `data` property of the request configuration object contains an object, serialize it + * into JSON format. + * + * Response transformations: + * + * - If XSRF prefix is detected, strip it (see Security Considerations section below). + * - If JSON response is detected, deserialize it using a JSON parser. + * + * To globally augment or override the default transforms, modify the + * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse` + * properties. These properties are by default an array of transform functions, which allows you + * to `push` or `unshift` a new transformation function into the transformation chain. You can + * also decide to completely override any default transformations by assigning your + * transformation functions to these properties directly without the array wrapper. These defaults + * are again available on the $http factory at run-time, which may be useful if you have run-time + * services you wish to be involved in your transformations. + * + * Similarly, to locally override the request/response transforms, augment the + * `transformRequest` and/or `transformResponse` properties of the configuration object passed + * into `$http`. + * + * + * # Caching + * + * To enable caching, set the request configuration `cache` property to `true` (to use default + * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). + * When the cache is enabled, `$http` stores the response from the server in the specified + * cache. The next time the same request is made, the response is served from the cache without + * sending a request to the server. + * + * Note that even if the response is served from cache, delivery of the data is asynchronous in + * the same way that real requests are. + * + * If there are multiple GET requests for the same URL that should be cached using the same + * cache, but the cache is not populated yet, only one request to the server will be made and + * the remaining requests will be fulfilled using the response from the first request. + * + * You can change the default cache to a new object (built with + * {@link ng.$cacheFactory `$cacheFactory`}) by updating the + * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set + * their `cache` property to `true` will now use this cache object. + * + * If you set the default cache to `false` then only requests that specify their own custom + * cache object will be cached. + * + * # Interceptors + * + * Before you start creating interceptors, be sure to understand the + * {@link ng.$q $q and deferred/promise APIs}. + * + * For purposes of global error handling, authentication, or any kind of synchronous or + * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be + * able to intercept requests before they are handed to the server and + * responses before they are handed over to the application code that + * initiated these requests. The interceptors leverage the {@link ng.$q + * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. + * + * The interceptors are service factories that are registered with the `$httpProvider` by + * adding them to the `$httpProvider.interceptors` array. The factory is called and + * injected with dependencies (if specified) and returns the interceptor. + * + * There are two kinds of interceptors (and two kinds of rejection interceptors): + * + * * `request`: interceptors get called with http `config` object. The function is free to + * modify the `config` or create a new one. The function needs to return the `config` + * directly or as a promise. + * * `requestError`: interceptor gets called when a previous interceptor threw an error or + * resolved with a rejection. + * * `response`: interceptors get called with http `response` object. The function is free to + * modify the `response` or create a new one. The function needs to return the `response` + * directly or as a promise. + * * `responseError`: interceptor gets called when a previous interceptor threw an error or + * resolved with a rejection. + * + * + *
      +     *   // register the interceptor as a service
      +     *   $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
      +     *     return {
      +     *       // optional method
      +     *       'request': function(config) {
      +     *         // do something on success
      +     *         return config || $q.when(config);
      +     *       },
      +     *
      +     *       // optional method
      +     *      'requestError': function(rejection) {
      +     *         // do something on error
      +     *         if (canRecover(rejection)) {
      +     *           return responseOrNewPromise
      +     *         }
      +     *         return $q.reject(rejection);
      +     *       },
      +     *
      +     *
      +     *
      +     *       // optional method
      +     *       'response': function(response) {
      +     *         // do something on success
      +     *         return response || $q.when(response);
      +     *       },
      +     *
      +     *       // optional method
      +     *      'responseError': function(rejection) {
      +     *         // do something on error
      +     *         if (canRecover(rejection)) {
      +     *           return responseOrNewPromise
      +     *         }
      +     *         return $q.reject(rejection);
      +     *       }
      +     *     };
      +     *   });
      +     *
      +     *   $httpProvider.interceptors.push('myHttpInterceptor');
      +     *
      +     *
      +     *   // alternatively, register the interceptor via an anonymous factory
      +     *   $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
      +     *     return {
      +     *      'request': function(config) {
      +     *          // same as above
      +     *       },
      +     *
      +     *       'response': function(response) {
      +     *          // same as above
      +     *       }
      +     *     };
      +     *   });
      +     * 
      + * + * # Response interceptors (DEPRECATED) + * + * Before you start creating interceptors, be sure to understand the + * {@link ng.$q $q and deferred/promise APIs}. + * + * For purposes of global error handling, authentication or any kind of synchronous or + * asynchronous preprocessing of received responses, it is desirable to be able to intercept + * responses for http requests before they are handed over to the application code that + * initiated these requests. The response interceptors leverage the {@link ng.$q + * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. + * + * The interceptors are service factories that are registered with the $httpProvider by + * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and + * injected with dependencies (if specified) and returns the interceptor — a function that + * takes a {@link ng.$q promise} and returns the original or a new promise. + * + *
      +     *   // register the interceptor as a service
      +     *   $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
      +     *     return function(promise) {
      +     *       return promise.then(function(response) {
      +     *         // do something on success
      +     *         return response;
      +     *       }, function(response) {
      +     *         // do something on error
      +     *         if (canRecover(response)) {
      +     *           return responseOrNewPromise
      +     *         }
      +     *         return $q.reject(response);
      +     *       });
      +     *     }
      +     *   });
      +     *
      +     *   $httpProvider.responseInterceptors.push('myHttpInterceptor');
      +     *
      +     *
      +     *   // register the interceptor via an anonymous factory
      +     *   $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) {
      +     *     return function(promise) {
      +     *       // same as above
      +     *     }
      +     *   });
      +     * 
      + * + * + * # Security Considerations + * + * When designing web applications, consider security threats from: + * + * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx + * JSON vulnerability} + * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} + * + * Both server and the client must cooperate in order to eliminate these threats. Angular comes + * pre-configured with strategies that address these issues, but for this to work backend server + * cooperation is required. + * + * ## JSON Vulnerability Protection + * + * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx + * JSON vulnerability} allows third party website to turn your JSON resource URL into + * {@link http://en.wikipedia.org/wiki/JSONP JSONP} request under some conditions. To + * counter this your server can prefix all JSON requests with following string `")]}',\n"`. + * Angular will automatically strip the prefix before processing it as JSON. + * + * For example if your server needs to return: + *
      +     * ['one','two']
      +     * 
      + * + * which is vulnerable to attack, your server can return: + *
      +     * )]}',
      +     * ['one','two']
      +     * 
      + * + * Angular will strip the prefix, before processing the JSON. + * + * + * ## Cross Site Request Forgery (XSRF) Protection + * + * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which + * an unauthorized site can gain your user's private data. Angular provides a mechanism + * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie + * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only + * JavaScript that runs on your domain could read the cookie, your server can be assured that + * the XHR came from JavaScript running on your domain. The header will not be set for + * cross-domain requests. + * + * To take advantage of this, your server needs to set a token in a JavaScript readable session + * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the + * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure + * that only JavaScript running on your domain could have sent the request. The token must be + * unique for each user and must be verifiable by the server (to prevent the JavaScript from + * making up its own tokens). We recommend that the token is a digest of your site's + * authentication cookie with a {@link https://en.wikipedia.org/wiki/Salt_(cryptography) salt} + * for added security. + * + * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName + * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, + * or the per-request config object. + * + * + * @param {object} config Object describing the request to be made and how it should be + * processed. The object has following properties: + * + * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) + * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. + * - **params** – `{Object.}` – Map of strings or objects which will be turned + * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be + * JSONified. + * - **data** – `{string|Object}` – Data to be sent as the request message data. + * - **headers** – `{Object}` – Map of strings or functions which return strings representing + * HTTP headers to send to the server. If the return value of a function is null, the + * header will not be sent. + * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token. + * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token. + * - **transformRequest** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * request body and headers and returns its transformed (typically serialized) version. + * - **transformResponse** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * response body and headers and returns its transformed (typically deserialized) version. + * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the + * GET request, otherwise if a cache instance built with + * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for + * caching. + * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} + * that should abort the request when resolved. + * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the + * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 + * requests with credentials} for more information. + * - **responseType** - `{string}` - see {@link + * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. + * + * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the + * standard `then` method and two http specific methods: `success` and `error`. The `then` + * method takes two arguments a success and an error callback which will be called with a + * response object. The `success` and `error` methods take a single argument - a function that + * will be called when the request succeeds or fails respectively. The arguments passed into + * these functions are destructured representation of the response object passed into the + * `then` method. The response object has these properties: + * + * - **data** – `{string|Object}` – The response body transformed with the transform + * functions. + * - **status** – `{number}` – HTTP status code of the response. + * - **headers** – `{function([headerName])}` – Header getter function. + * - **config** – `{Object}` – The configuration object that was used to generate the request. + * + * @property {Array.} pendingRequests Array of config objects for currently pending + * requests. This is primarily meant to be used for debugging purposes. + * + * + * @example + + +
      + + +
      + + + +
      http status code: {{status}}
      +
      http response data: {{data}}
      +
      +
      + + function FetchCtrl($scope, $http, $templateCache) { + $scope.method = 'GET'; + $scope.url = 'http-hello.html'; + + $scope.fetch = function() { + $scope.code = null; + $scope.response = null; + + $http({method: $scope.method, url: $scope.url, cache: $templateCache}). + success(function(data, status) { + $scope.status = status; + $scope.data = data; + }). + error(function(data, status) { + $scope.data = data || "Request failed"; + $scope.status = status; + }); + }; + + $scope.updateModel = function(method, url) { + $scope.method = method; + $scope.url = url; + }; + } + + + Hello, $http! + + + it('should make an xhr GET request', function() { + element(':button:contains("Sample GET")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('200'); + expect(binding('data')).toMatch(/Hello, \$http!/); + }); + + it('should make a JSONP request to angularjs.org', function() { + element(':button:contains("Sample JSONP")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('200'); + expect(binding('data')).toMatch(/Super Hero!/); + }); + + it('should make JSONP request to invalid URL and invoke the error handler', + function() { + element(':button:contains("Invalid JSONP")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('0'); + expect(binding('data')).toBe('Request failed'); + }); + +
      + */ + function $http(requestConfig) { + var config = { + transformRequest: defaults.transformRequest, + transformResponse: defaults.transformResponse + }; + var headers = mergeHeaders(requestConfig); + + extend(config, requestConfig); + config.headers = headers; + config.method = uppercase(config.method); + + var xsrfValue = urlIsSameOrigin(config.url) + ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] + : undefined; + if (xsrfValue) { + headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; + } + + + var serverRequest = function(config) { + headers = config.headers; + var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); + + // strip content-type if data is undefined + if (isUndefined(config.data)) { + forEach(headers, function(value, header) { + if (lowercase(header) === 'content-type') { + delete headers[header]; + } + }); + } + + if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { + config.withCredentials = defaults.withCredentials; + } + + // send request + return sendReq(config, reqData, headers).then(transformResponse, transformResponse); + }; + + var chain = [serverRequest, undefined]; + var promise = $q.when(config); + + // apply interceptors + forEach(reversedInterceptors, function(interceptor) { + if (interceptor.request || interceptor.requestError) { + chain.unshift(interceptor.request, interceptor.requestError); + } + if (interceptor.response || interceptor.responseError) { + chain.push(interceptor.response, interceptor.responseError); + } + }); + + while(chain.length) { + var thenFn = chain.shift(); + var rejectFn = chain.shift(); + + promise = promise.then(thenFn, rejectFn); + } + + promise.success = function(fn) { + promise.then(function(response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + promise.error = function(fn) { + promise.then(null, function(response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + return promise; + + function transformResponse(response) { + // make a copy since the response must be cacheable + var resp = extend({}, response, { + data: transformData(response.data, response.headers, config.transformResponse) + }); + return (isSuccess(response.status)) + ? resp + : $q.reject(resp); + } + + function mergeHeaders(config) { + var defHeaders = defaults.headers, + reqHeaders = extend({}, config.headers), + defHeaderName, lowercaseDefHeaderName, reqHeaderName; + + defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); + + // execute if header value is function + execHeaders(defHeaders); + execHeaders(reqHeaders); + + // using for-in instead of forEach to avoid unecessary iteration after header has been found + defaultHeadersIteration: + for (defHeaderName in defHeaders) { + lowercaseDefHeaderName = lowercase(defHeaderName); + + for (reqHeaderName in reqHeaders) { + if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { + continue defaultHeadersIteration; + } + } + + reqHeaders[defHeaderName] = defHeaders[defHeaderName]; + } + + return reqHeaders; + + function execHeaders(headers) { + var headerContent; + + forEach(headers, function(headerFn, header) { + if (isFunction(headerFn)) { + headerContent = headerFn(); + if (headerContent != null) { + headers[header] = headerContent; + } else { + delete headers[header]; + } + } + }); + } + } + } + + $http.pendingRequests = []; + + /** + * @ngdoc method + * @name ng.$http#get + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `GET` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name ng.$http#delete + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `DELETE` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name ng.$http#head + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `HEAD` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name ng.$http#jsonp + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `JSONP` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request. + * Should contain `JSON_CALLBACK` string. + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + createShortMethods('get', 'delete', 'head', 'jsonp'); + + /** + * @ngdoc method + * @name ng.$http#post + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `POST` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {*} data Request content + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name ng.$http#put + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `PUT` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {*} data Request content + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + createShortMethodsWithData('post', 'put'); + + /** + * @ngdoc property + * @name ng.$http#defaults + * @propertyOf ng.$http + * + * @description + * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of + * default headers, withCredentials as well as request and response transformations. + * + * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. + */ + $http.defaults = defaults; + + + return $http; + + + function createShortMethods(names) { + forEach(arguments, function(name) { + $http[name] = function(url, config) { + return $http(extend(config || {}, { + method: name, + url: url + })); + }; + }); + } + + + function createShortMethodsWithData(name) { + forEach(arguments, function(name) { + $http[name] = function(url, data, config) { + return $http(extend(config || {}, { + method: name, + url: url, + data: data + })); + }; + }); + } + + + /** + * Makes the request. + * + * !!! ACCESSES CLOSURE VARS: + * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests + */ + function sendReq(config, reqData, reqHeaders) { + var deferred = $q.defer(), + promise = deferred.promise, + cache, + cachedResp, + url = buildUrl(config.url, config.params); + + $http.pendingRequests.push(config); + promise.then(removePendingReq, removePendingReq); + + + if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') { + cache = isObject(config.cache) ? config.cache + : isObject(defaults.cache) ? defaults.cache + : defaultCache; + } + + if (cache) { + cachedResp = cache.get(url); + if (isDefined(cachedResp)) { + if (cachedResp.then) { + // cached request has already been sent, but there is no response yet + cachedResp.then(removePendingReq, removePendingReq); + return cachedResp; + } else { + // serving from cache + if (isArray(cachedResp)) { + resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2])); + } else { + resolvePromise(cachedResp, 200, {}); + } + } + } else { + // put the promise for the non-transformed response into cache as a placeholder + cache.put(url, promise); + } + } + + // if we won't have the response in cache, send the request to the backend + if (isUndefined(cachedResp)) { + $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, + config.withCredentials, config.responseType); + } + + return promise; + + + /** + * Callback registered to $httpBackend(): + * - caches the response if desired + * - resolves the raw $http promise + * - calls $apply + */ + function done(status, response, headersString) { + if (cache) { + if (isSuccess(status)) { + cache.put(url, [status, response, parseHeaders(headersString)]); + } else { + // remove promise from the cache + cache.remove(url); + } + } + + resolvePromise(response, status, headersString); + if (!$rootScope.$$phase) $rootScope.$apply(); + } + + + /** + * Resolves the raw $http promise. + */ + function resolvePromise(response, status, headers) { + // normalize internal statuses to 0 + status = Math.max(status, 0); + + (isSuccess(status) ? deferred.resolve : deferred.reject)({ + data: response, + status: status, + headers: headersGetter(headers), + config: config + }); + } + + + function removePendingReq() { + var idx = indexOf($http.pendingRequests, config); + if (idx !== -1) $http.pendingRequests.splice(idx, 1); + } + } + + + function buildUrl(url, params) { + if (!params) return url; + var parts = []; + forEachSorted(params, function(value, key) { + if (value === null || isUndefined(value)) return; + if (!isArray(value)) value = [value]; + + forEach(value, function(v) { + if (isObject(v)) { + v = toJson(v); + } + parts.push(encodeUriQuery(key) + '=' + + encodeUriQuery(v)); + }); + }); + return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); + } + + + }]; +} + +function createXhr(method) { + // IE8 doesn't support PATCH method, but the ActiveX object does + /* global ActiveXObject */ + return (msie <= 8 && lowercase(method) === 'patch') + ? new ActiveXObject('Microsoft.XMLHTTP') + : new window.XMLHttpRequest(); +} + + +/** + * @ngdoc object + * @name ng.$httpBackend + * @requires $browser + * @requires $window + * @requires $document + * + * @description + * HTTP backend used by the {@link ng.$http service} that delegates to + * XMLHttpRequest object or JSONP and deals with browser incompatibilities. + * + * You should never need to use this service directly, instead use the higher-level abstractions: + * {@link ng.$http $http} or {@link ngResource.$resource $resource}. + * + * During testing this implementation is swapped with {@link ngMock.$httpBackend mock + * $httpBackend} which can be trained with responses. + */ +function $HttpBackendProvider() { + this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { + return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); + }]; +} + +function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { + var ABORTED = -1; + + // TODO(vojta): fix the signature + return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { + var status; + $browser.$$incOutstandingRequestCount(); + url = url || $browser.url(); + + if (lowercase(method) == 'jsonp') { + var callbackId = '_' + (callbacks.counter++).toString(36); + callbacks[callbackId] = function(data) { + callbacks[callbackId].data = data; + }; + + var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), + function() { + if (callbacks[callbackId].data) { + completeRequest(callback, 200, callbacks[callbackId].data); + } else { + completeRequest(callback, status || -2); + } + callbacks[callbackId] = angular.noop; + }); + } else { + + var xhr = createXhr(method); + + xhr.open(method, url, true); + forEach(headers, function(value, key) { + if (isDefined(value)) { + xhr.setRequestHeader(key, value); + } + }); + + // In IE6 and 7, this might be called synchronously when xhr.send below is called and the + // response is in the cache. the promise api will ensure that to the app code the api is + // always async + xhr.onreadystatechange = function() { + // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by + // xhrs that are resolved while the app is in the background (see #5426). + // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before + // continuing + // + // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and + // Safari respectively. + if (xhr && xhr.readyState == 4) { + var responseHeaders = null, + response = null; + + if(status !== ABORTED) { + responseHeaders = xhr.getAllResponseHeaders(); + + // responseText is the old-school way of retrieving response (supported by IE8 & 9) + // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) + response = ('response' in xhr) ? xhr.response : xhr.responseText; + } + + completeRequest(callback, + status || xhr.status, + response, + responseHeaders); + } + }; + + if (withCredentials) { + xhr.withCredentials = true; + } + + if (responseType) { + xhr.responseType = responseType; + } + + xhr.send(post || null); + } + + if (timeout > 0) { + var timeoutId = $browserDefer(timeoutRequest, timeout); + } else if (timeout && timeout.then) { + timeout.then(timeoutRequest); + } + + + function timeoutRequest() { + status = ABORTED; + jsonpDone && jsonpDone(); + xhr && xhr.abort(); + } + + function completeRequest(callback, status, response, headersString) { + // cancel timeout and subsequent timeout promise resolution + timeoutId && $browserDefer.cancel(timeoutId); + jsonpDone = xhr = null; + + // fix status code when it is 0 (0 status is undocumented). + // Occurs when accessing file resources. + // On Android 4.1 stock browser it occurs while retrieving files from application cache. + status = (status === 0) ? (response ? 200 : 404) : status; + + // normalize IE bug (http://bugs.jquery.com/ticket/1450) + status = status == 1223 ? 204 : status; + + callback(status, response, headersString); + $browser.$$completeOutstandingRequest(noop); + } + }; + + function jsonpReq(url, done) { + // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: + // - fetches local scripts via XHR and evals them + // - adds and immediately removes script elements from the document + var script = rawDocument.createElement('script'), + doneWrapper = function() { + script.onreadystatechange = script.onload = script.onerror = null; + rawDocument.body.removeChild(script); + if (done) done(); + }; + + script.type = 'text/javascript'; + script.src = url; + + if (msie && msie <= 8) { + script.onreadystatechange = function() { + if (/loaded|complete/.test(script.readyState)) { + doneWrapper(); + } + }; + } else { + script.onload = script.onerror = function() { + doneWrapper(); + }; + } + + rawDocument.body.appendChild(script); + return doneWrapper; + } +} + +var $interpolateMinErr = minErr('$interpolate'); + +/** + * @ngdoc object + * @name ng.$interpolateProvider + * @function + * + * @description + * + * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. + * + * @example + + + +
      + //demo.label// +
      +
      + + it('should interpolate binding with custom symbols', function() { + expect(binding('demo.label')).toBe('This binding is brought you by // interpolation symbols.'); + }); + +
      + */ +function $InterpolateProvider() { + var startSymbol = '{{'; + var endSymbol = '}}'; + + /** + * @ngdoc method + * @name ng.$interpolateProvider#startSymbol + * @methodOf ng.$interpolateProvider + * @description + * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. + * + * @param {string=} value new value to set the starting symbol to. + * @returns {string|self} Returns the symbol when used as getter and self if used as setter. + */ + this.startSymbol = function(value){ + if (value) { + startSymbol = value; + return this; + } else { + return startSymbol; + } + }; + + /** + * @ngdoc method + * @name ng.$interpolateProvider#endSymbol + * @methodOf ng.$interpolateProvider + * @description + * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. + * + * @param {string=} value new value to set the ending symbol to. + * @returns {string|self} Returns the symbol when used as getter and self if used as setter. + */ + this.endSymbol = function(value){ + if (value) { + endSymbol = value; + return this; + } else { + return endSymbol; + } + }; + + + this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { + var startSymbolLength = startSymbol.length, + endSymbolLength = endSymbol.length; + + /** + * @ngdoc function + * @name ng.$interpolate + * @function + * + * @requires $parse + * @requires $sce + * + * @description + * + * Compiles a string with markup into an interpolation function. This service is used by the + * HTML {@link ng.$compile $compile} service for data binding. See + * {@link ng.$interpolateProvider $interpolateProvider} for configuring the + * interpolation markup. + * + * +
      +         var $interpolate = ...; // injected
      +         var exp = $interpolate('Hello {{name | uppercase}}!');
      +         expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!');
      +       
      + * + * + * @param {string} text The text with markup to interpolate. + * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have + * embedded expression in order to return an interpolation function. Strings with no + * embedded expression will return null for the interpolation function. + * @param {string=} trustedContext when provided, the returned function passes the interpolated + * result through {@link ng.$sce#methods_getTrusted $sce.getTrusted(interpolatedResult, + * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that + * provides Strict Contextual Escaping for details. + * @returns {function(context)} an interpolation function which is used to compute the + * interpolated string. The function has these parameters: + * + * * `context`: an object against which any expressions embedded in the strings are evaluated + * against. + * + */ + function $interpolate(text, mustHaveExpression, trustedContext) { + var startIndex, + endIndex, + index = 0, + parts = [], + length = text.length, + hasInterpolation = false, + fn, + exp, + concat = []; + + while(index < length) { + if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && + ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { + (index != startIndex) && parts.push(text.substring(index, startIndex)); + parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); + fn.exp = exp; + index = endIndex + endSymbolLength; + hasInterpolation = true; + } else { + // we did not find anything, so we have to add the remainder to the parts array + (index != length) && parts.push(text.substring(index)); + index = length; + } + } + + if (!(length = parts.length)) { + // we added, nothing, must have been an empty string. + parts.push(''); + length = 1; + } + + // Concatenating expressions makes it hard to reason about whether some combination of + // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a + // single expression be used for iframe[src], object[src], etc., we ensure that the value + // that's used is assigned or constructed by some JS code somewhere that is more testable or + // make it obvious that you bound the value to some user controlled value. This helps reduce + // the load when auditing for XSS issues. + if (trustedContext && parts.length > 1) { + throw $interpolateMinErr('noconcat', + "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + + "interpolations that concatenate multiple expressions when a trusted value is " + + "required. See http://docs.angularjs.org/api/ng.$sce", text); + } + + if (!mustHaveExpression || hasInterpolation) { + concat.length = length; + fn = function(context) { + try { + for(var i = 0, ii = length, part; i + * **Note**: Intervals created by this service must be explicitly destroyed when you are finished + * with them. In particular they are not automatically destroyed when a controller's scope or a + * directive's element are destroyed. + * You should take this into consideration and make sure to always cancel the interval at the + * appropriate moment. See the example below for more details on how and when to do this. + * + * + * @param {function()} fn A function that should be called repeatedly. + * @param {number} delay Number of milliseconds between each function call. + * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat + * indefinitely. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block. + * @returns {promise} A promise which will be notified on each iteration. + * + * @example + + + + +
      +
      + Date format:
      + Current time is: +
      + Blood 1 : {{blood_1}} + Blood 2 : {{blood_2}} + + + +
      +
      + +
      +
      + */ + function interval(fn, delay, count, invokeApply) { + var setInterval = $window.setInterval, + clearInterval = $window.clearInterval, + deferred = $q.defer(), + promise = deferred.promise, + iteration = 0, + skipApply = (isDefined(invokeApply) && !invokeApply); + + count = isDefined(count) ? count : 0, + + promise.then(null, null, fn); + + promise.$$intervalId = setInterval(function tick() { + deferred.notify(iteration++); + + if (count > 0 && iteration >= count) { + deferred.resolve(iteration); + clearInterval(promise.$$intervalId); + delete intervals[promise.$$intervalId]; + } + + if (!skipApply) $rootScope.$apply(); + + }, delay); + + intervals[promise.$$intervalId] = deferred; + + return promise; + } + + + /** + * @ngdoc function + * @name ng.$interval#cancel + * @methodOf ng.$interval + * + * @description + * Cancels a task associated with the `promise`. + * + * @param {number} promise Promise returned by the `$interval` function. + * @returns {boolean} Returns `true` if the task was successfully canceled. + */ + interval.cancel = function(promise) { + if (promise && promise.$$intervalId in intervals) { + intervals[promise.$$intervalId].reject('canceled'); + clearInterval(promise.$$intervalId); + delete intervals[promise.$$intervalId]; + return true; + } + return false; + }; + + return interval; + }]; +} + +/** + * @ngdoc object + * @name ng.$locale + * + * @description + * $locale service provides localization rules for various Angular components. As of right now the + * only public api is: + * + * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) + */ +function $LocaleProvider(){ + this.$get = function() { + return { + id: 'en-us', + + NUMBER_FORMATS: { + DECIMAL_SEP: '.', + GROUP_SEP: ',', + PATTERNS: [ + { // Decimal Pattern + minInt: 1, + minFrac: 0, + maxFrac: 3, + posPre: '', + posSuf: '', + negPre: '-', + negSuf: '', + gSize: 3, + lgSize: 3 + },{ //Currency Pattern + minInt: 1, + minFrac: 2, + maxFrac: 2, + posPre: '\u00A4', + posSuf: '', + negPre: '(\u00A4', + negSuf: ')', + gSize: 3, + lgSize: 3 + } + ], + CURRENCY_SYM: '$' + }, + + DATETIME_FORMATS: { + MONTH: + 'January,February,March,April,May,June,July,August,September,October,November,December' + .split(','), + SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), + DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), + SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), + AMPMS: ['AM','PM'], + medium: 'MMM d, y h:mm:ss a', + short: 'M/d/yy h:mm a', + fullDate: 'EEEE, MMMM d, y', + longDate: 'MMMM d, y', + mediumDate: 'MMM d, y', + shortDate: 'M/d/yy', + mediumTime: 'h:mm:ss a', + shortTime: 'h:mm a' + }, + + pluralCat: function(num) { + if (num === 1) { + return 'one'; + } + return 'other'; + } + }; + }; +} + +var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, + DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; +var $locationMinErr = minErr('$location'); + + +/** + * Encode path using encodeUriSegment, ignoring forward slashes + * + * @param {string} path Path to encode + * @returns {string} + */ +function encodePath(path) { + var segments = path.split('/'), + i = segments.length; + + while (i--) { + segments[i] = encodeUriSegment(segments[i]); + } + + return segments.join('/'); +} + +function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) { + var parsedUrl = urlResolve(absoluteUrl, appBase); + + locationObj.$$protocol = parsedUrl.protocol; + locationObj.$$host = parsedUrl.hostname; + locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; +} + + +function parseAppUrl(relativeUrl, locationObj, appBase) { + var prefixed = (relativeUrl.charAt(0) !== '/'); + if (prefixed) { + relativeUrl = '/' + relativeUrl; + } + var match = urlResolve(relativeUrl, appBase); + locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? + match.pathname.substring(1) : match.pathname); + locationObj.$$search = parseKeyValue(match.search); + locationObj.$$hash = decodeURIComponent(match.hash); + + // make sure path starts with '/'; + if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { + locationObj.$$path = '/' + locationObj.$$path; + } +} + + +/** + * + * @param {string} begin + * @param {string} whole + * @returns {string} returns text from whole after begin or undefined if it does not begin with + * expected string. + */ +function beginsWith(begin, whole) { + if (whole.indexOf(begin) === 0) { + return whole.substr(begin.length); + } +} + + +function stripHash(url) { + var index = url.indexOf('#'); + return index == -1 ? url : url.substr(0, index); +} + + +function stripFile(url) { + return url.substr(0, stripHash(url).lastIndexOf('/') + 1); +} + +/* return the server only (scheme://host:port) */ +function serverBase(url) { + return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); +} + + +/** + * LocationHtml5Url represents an url + * This object is exposed as $location service when HTML5 mode is enabled and supported + * + * @constructor + * @param {string} appBase application base URL + * @param {string} basePrefix url path prefix + */ +function LocationHtml5Url(appBase, basePrefix) { + this.$$html5 = true; + basePrefix = basePrefix || ''; + var appBaseNoFile = stripFile(appBase); + parseAbsoluteUrl(appBase, this, appBase); + + + /** + * Parse given html5 (regular) url string into properties + * @param {string} newAbsoluteUrl HTML5 url + * @private + */ + this.$$parse = function(url) { + var pathUrl = beginsWith(appBaseNoFile, url); + if (!isString(pathUrl)) { + throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, + appBaseNoFile); + } + + parseAppUrl(pathUrl, this, appBase); + + if (!this.$$path) { + this.$$path = '/'; + } + + this.$$compose(); + }; + + /** + * Compose url and update `absUrl` property + * @private + */ + this.$$compose = function() { + var search = toKeyValue(this.$$search), + hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; + + this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' + }; + + this.$$rewrite = function(url) { + var appUrl, prevAppUrl; + + if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { + prevAppUrl = appUrl; + if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { + return appBaseNoFile + (beginsWith('/', appUrl) || appUrl); + } else { + return appBase + prevAppUrl; + } + } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { + return appBaseNoFile + appUrl; + } else if (appBaseNoFile == url + '/') { + return appBaseNoFile; + } + }; +} + + +/** + * LocationHashbangUrl represents url + * This object is exposed as $location service when developer doesn't opt into html5 mode. + * It also serves as the base class for html5 mode fallback on legacy browsers. + * + * @constructor + * @param {string} appBase application base URL + * @param {string} hashPrefix hashbang prefix + */ +function LocationHashbangUrl(appBase, hashPrefix) { + var appBaseNoFile = stripFile(appBase); + + parseAbsoluteUrl(appBase, this, appBase); + + + /** + * Parse given hashbang url into properties + * @param {string} url Hashbang url + * @private + */ + this.$$parse = function(url) { + var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); + var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' + ? beginsWith(hashPrefix, withoutBaseUrl) + : (this.$$html5) + ? withoutBaseUrl + : ''; + + if (!isString(withoutHashUrl)) { + throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, + hashPrefix); + } + parseAppUrl(withoutHashUrl, this, appBase); + + this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); + + this.$$compose(); + + /* + * In Windows, on an anchor node on documents loaded from + * the filesystem, the browser will return a pathname + * prefixed with the drive name ('/C:/path') when a + * pathname without a drive is set: + * * a.setAttribute('href', '/foo') + * * a.pathname === '/C:/foo' //true + * + * Inside of Angular, we're always using pathnames that + * do not include drive names for routing. + */ + function removeWindowsDriveName (path, url, base) { + /* + Matches paths for file protocol on windows, + such as /C:/foo/bar, and captures only /foo/bar. + */ + var windowsFilePathExp = /^\/?.*?:(\/.*)/; + + var firstPathSegmentMatch; + + //Get the relative path from the input URL. + if (url.indexOf(base) === 0) { + url = url.replace(base, ''); + } + + /* + * The input URL intentionally contains a + * first path segment that ends with a colon. + */ + if (windowsFilePathExp.exec(url)) { + return path; + } + + firstPathSegmentMatch = windowsFilePathExp.exec(path); + return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; + } + }; + + /** + * Compose hashbang url and update `absUrl` property + * @private + */ + this.$$compose = function() { + var search = toKeyValue(this.$$search), + hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; + + this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); + }; + + this.$$rewrite = function(url) { + if(stripHash(appBase) == stripHash(url)) { + return url; + } + }; +} + + +/** + * LocationHashbangUrl represents url + * This object is exposed as $location service when html5 history api is enabled but the browser + * does not support it. + * + * @constructor + * @param {string} appBase application base URL + * @param {string} hashPrefix hashbang prefix + */ +function LocationHashbangInHtml5Url(appBase, hashPrefix) { + this.$$html5 = true; + LocationHashbangUrl.apply(this, arguments); + + var appBaseNoFile = stripFile(appBase); + + this.$$rewrite = function(url) { + var appUrl; + + if ( appBase == stripHash(url) ) { + return url; + } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { + return appBase + hashPrefix + appUrl; + } else if ( appBaseNoFile === url + '/') { + return appBaseNoFile; + } + }; +} + + +LocationHashbangInHtml5Url.prototype = + LocationHashbangUrl.prototype = + LocationHtml5Url.prototype = { + + /** + * Are we in html5 mode? + * @private + */ + $$html5: false, + + /** + * Has any change been replacing ? + * @private + */ + $$replace: false, + + /** + * @ngdoc method + * @name ng.$location#absUrl + * @methodOf ng.$location + * + * @description + * This method is getter only. + * + * Return full url representation with all segments encoded according to rules specified in + * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}. + * + * @return {string} full url + */ + absUrl: locationGetter('$$absUrl'), + + /** + * @ngdoc method + * @name ng.$location#url + * @methodOf ng.$location + * + * @description + * This method is getter / setter. + * + * Return url (e.g. `/path?a=b#hash`) when called without any parameter. + * + * Change path, search and hash, when called with parameter and return `$location`. + * + * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) + * @param {string=} replace The path that will be changed + * @return {string} url + */ + url: function(url, replace) { + if (isUndefined(url)) + return this.$$url; + + var match = PATH_MATCH.exec(url); + if (match[1]) this.path(decodeURIComponent(match[1])); + if (match[2] || match[1]) this.search(match[3] || ''); + this.hash(match[5] || '', replace); + + return this; + }, + + /** + * @ngdoc method + * @name ng.$location#protocol + * @methodOf ng.$location + * + * @description + * This method is getter only. + * + * Return protocol of current url. + * + * @return {string} protocol of current url + */ + protocol: locationGetter('$$protocol'), + + /** + * @ngdoc method + * @name ng.$location#host + * @methodOf ng.$location + * + * @description + * This method is getter only. + * + * Return host of current url. + * + * @return {string} host of current url. + */ + host: locationGetter('$$host'), + + /** + * @ngdoc method + * @name ng.$location#port + * @methodOf ng.$location + * + * @description + * This method is getter only. + * + * Return port of current url. + * + * @return {Number} port + */ + port: locationGetter('$$port'), + + /** + * @ngdoc method + * @name ng.$location#path + * @methodOf ng.$location + * + * @description + * This method is getter / setter. + * + * Return path of current url when called without any parameter. + * + * Change path when called with parameter and return `$location`. + * + * Note: Path should always begin with forward slash (/), this method will add the forward slash + * if it is missing. + * + * @param {string=} path New path + * @return {string} path + */ + path: locationGetterSetter('$$path', function(path) { + return path.charAt(0) == '/' ? path : '/' + path; + }), + + /** + * @ngdoc method + * @name ng.$location#search + * @methodOf ng.$location + * + * @description + * This method is getter / setter. + * + * Return search part (as object) of current url when called without any parameter. + * + * Change search part when called with parameter and return `$location`. + * + * @param {string|Object.|Object.>} search New search params - string or + * hash object. Hash object may contain an array of values, which will be decoded as duplicates in + * the url. + * + * @param {(string|Array)=} paramValue If `search` is a string, then `paramValue` will override only a + * single search parameter. If `paramValue` is an array, it will set the parameter as a + * comma-separated value. If `paramValue` is `null`, the parameter will be deleted. + * + * @return {string} search + */ + search: function(search, paramValue) { + switch (arguments.length) { + case 0: + return this.$$search; + case 1: + if (isString(search)) { + this.$$search = parseKeyValue(search); + } else if (isObject(search)) { + this.$$search = search; + } else { + throw $locationMinErr('isrcharg', + 'The first argument of the `$location#search()` call must be a string or an object.'); + } + break; + default: + if (isUndefined(paramValue) || paramValue === null) { + delete this.$$search[search]; + } else { + this.$$search[search] = paramValue; + } + } + + this.$$compose(); + return this; + }, + + /** + * @ngdoc method + * @name ng.$location#hash + * @methodOf ng.$location + * + * @description + * This method is getter / setter. + * + * Return hash fragment when called without any parameter. + * + * Change hash fragment when called with parameter and return `$location`. + * + * @param {string=} hash New hash fragment + * @return {string} hash + */ + hash: locationGetterSetter('$$hash', identity), + + /** + * @ngdoc method + * @name ng.$location#replace + * @methodOf ng.$location + * + * @description + * If called, all changes to $location during current `$digest` will be replacing current history + * record, instead of adding new one. + */ + replace: function() { + this.$$replace = true; + return this; + } +}; + +function locationGetter(property) { + return function() { + return this[property]; + }; +} + + +function locationGetterSetter(property, preprocess) { + return function(value) { + if (isUndefined(value)) + return this[property]; + + this[property] = preprocess(value); + this.$$compose(); + + return this; + }; +} + + +/** + * @ngdoc object + * @name ng.$location + * + * @requires $browser + * @requires $sniffer + * @requires $rootElement + * + * @description + * The $location service parses the URL in the browser address bar (based on the + * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL + * available to your application. Changes to the URL in the address bar are reflected into + * $location service and changes to $location are reflected into the browser address bar. + * + * **The $location service:** + * + * - Exposes the current URL in the browser address bar, so you can + * - Watch and observe the URL. + * - Change the URL. + * - Synchronizes the URL with the browser when the user + * - Changes the address bar. + * - Clicks the back or forward button (or clicks a History link). + * - Clicks on a link. + * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). + * + * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular + * Services: Using $location} + */ + +/** + * @ngdoc object + * @name ng.$locationProvider + * @description + * Use the `$locationProvider` to configure how the application deep linking paths are stored. + */ +function $LocationProvider(){ + var hashPrefix = '', + html5Mode = false; + + /** + * @ngdoc property + * @name ng.$locationProvider#hashPrefix + * @methodOf ng.$locationProvider + * @description + * @param {string=} prefix Prefix for hash part (containing path and search) + * @returns {*} current value if used as getter or itself (chaining) if used as setter + */ + this.hashPrefix = function(prefix) { + if (isDefined(prefix)) { + hashPrefix = prefix; + return this; + } else { + return hashPrefix; + } + }; + + /** + * @ngdoc property + * @name ng.$locationProvider#html5Mode + * @methodOf ng.$locationProvider + * @description + * @param {boolean=} mode Use HTML5 strategy if available. + * @returns {*} current value if used as getter or itself (chaining) if used as setter + */ + this.html5Mode = function(mode) { + if (isDefined(mode)) { + html5Mode = mode; + return this; + } else { + return html5Mode; + } + }; + + /** + * @ngdoc event + * @name ng.$location#$locationChangeStart + * @eventOf ng.$location + * @eventType broadcast on root scope + * @description + * Broadcasted before a URL will change. This change can be prevented by calling + * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more + * details about event object. Upon successful change + * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. + * + * @param {Object} angularEvent Synthetic event object. + * @param {string} newUrl New URL + * @param {string=} oldUrl URL that was before it was changed. + */ + + /** + * @ngdoc event + * @name ng.$location#$locationChangeSuccess + * @eventOf ng.$location + * @eventType broadcast on root scope + * @description + * Broadcasted after a URL was changed. + * + * @param {Object} angularEvent Synthetic event object. + * @param {string} newUrl New URL + * @param {string=} oldUrl URL that was before it was changed. + */ + + this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', + function( $rootScope, $browser, $sniffer, $rootElement) { + var $location, + LocationMode, + baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' + initialUrl = $browser.url(), + appBase; + + if (html5Mode) { + appBase = serverBase(initialUrl) + (baseHref || '/'); + LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; + } else { + appBase = stripHash(initialUrl); + LocationMode = LocationHashbangUrl; + } + $location = new LocationMode(appBase, '#' + hashPrefix); + $location.$$parse($location.$$rewrite(initialUrl)); + + $rootElement.on('click', function(event) { + // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) + // currently we open nice url link and redirect then + + if (event.ctrlKey || event.metaKey || event.which == 2) return; + + var elm = jqLite(event.target); + + // traverse the DOM up to find first A tag + while (lowercase(elm[0].nodeName) !== 'a') { + // ignore rewriting if no A tag (reached root element, or no parent - removed from document) + if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; + } + + var absHref = elm.prop('href'); + + if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { + // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during + // an animation. + absHref = urlResolve(absHref.animVal).href; + } + + var rewrittenUrl = $location.$$rewrite(absHref); + + if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) { + event.preventDefault(); + if (rewrittenUrl != $browser.url()) { + // update location manually + $location.$$parse(rewrittenUrl); + $rootScope.$apply(); + // hack to work around FF6 bug 684208 when scenario runner clicks on links + window.angular['ff-684208-preventDefault'] = true; + } + } + }); + + + // rewrite hashbang url <> html5 url + if ($location.absUrl() != initialUrl) { + $browser.url($location.absUrl(), true); + } + + // update $location when $browser url changes + $browser.onUrlChange(function(newUrl) { + if ($location.absUrl() != newUrl) { + $rootScope.$evalAsync(function() { + var oldUrl = $location.absUrl(); + + $location.$$parse(newUrl); + if ($rootScope.$broadcast('$locationChangeStart', newUrl, + oldUrl).defaultPrevented) { + $location.$$parse(oldUrl); + $browser.url(oldUrl); + } else { + afterLocationChange(oldUrl); + } + }); + if (!$rootScope.$$phase) $rootScope.$digest(); + } + }); + + // update browser + var changeCounter = 0; + $rootScope.$watch(function $locationWatch() { + var oldUrl = $browser.url(); + var currentReplace = $location.$$replace; + + if (!changeCounter || oldUrl != $location.absUrl()) { + changeCounter++; + $rootScope.$evalAsync(function() { + if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). + defaultPrevented) { + $location.$$parse(oldUrl); + } else { + $browser.url($location.absUrl(), currentReplace); + afterLocationChange(oldUrl); + } + }); + } + $location.$$replace = false; + + return changeCounter; + }); + + return $location; + + function afterLocationChange(oldUrl) { + $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); + } +}]; +} + +/** + * @ngdoc object + * @name ng.$log + * @requires $window + * + * @description + * Simple service for logging. Default implementation safely writes the message + * into the browser's console (if present). + * + * The main purpose of this service is to simplify debugging and troubleshooting. + * + * The default is to log `debug` messages. You can use + * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. + * + * @example + + + function LogCtrl($scope, $log) { + $scope.$log = $log; + $scope.message = 'Hello World!'; + } + + +
      +

      Reload this page with open console, enter text and hit the log button...

      + Message: + + + + + +
      +
      +
      + */ + +/** + * @ngdoc object + * @name ng.$logProvider + * @description + * Use the `$logProvider` to configure how the application logs messages + */ +function $LogProvider(){ + var debug = true, + self = this; + + /** + * @ngdoc property + * @name ng.$logProvider#debugEnabled + * @methodOf ng.$logProvider + * @description + * @param {string=} flag enable or disable debug level messages + * @returns {*} current value if used as getter or itself (chaining) if used as setter + */ + this.debugEnabled = function(flag) { + if (isDefined(flag)) { + debug = flag; + return this; + } else { + return debug; + } + }; + + this.$get = ['$window', function($window){ + return { + /** + * @ngdoc method + * @name ng.$log#log + * @methodOf ng.$log + * + * @description + * Write a log message + */ + log: consoleLog('log'), + + /** + * @ngdoc method + * @name ng.$log#info + * @methodOf ng.$log + * + * @description + * Write an information message + */ + info: consoleLog('info'), + + /** + * @ngdoc method + * @name ng.$log#warn + * @methodOf ng.$log + * + * @description + * Write a warning message + */ + warn: consoleLog('warn'), + + /** + * @ngdoc method + * @name ng.$log#error + * @methodOf ng.$log + * + * @description + * Write an error message + */ + error: consoleLog('error'), + + /** + * @ngdoc method + * @name ng.$log#debug + * @methodOf ng.$log + * + * @description + * Write a debug message + */ + debug: (function () { + var fn = consoleLog('debug'); + + return function() { + if (debug) { + fn.apply(self, arguments); + } + }; + }()) + }; + + function formatError(arg) { + if (arg instanceof Error) { + if (arg.stack) { + arg = (arg.message && arg.stack.indexOf(arg.message) === -1) + ? 'Error: ' + arg.message + '\n' + arg.stack + : arg.stack; + } else if (arg.sourceURL) { + arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; + } + } + return arg; + } + + function consoleLog(type) { + var console = $window.console || {}, + logFn = console[type] || console.log || noop, + hasApply = false; + + // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. + // The reason behind this is that console.log has type "object" in IE8... + try { + hasApply = !! logFn.apply; + } catch (e) {} + + if (hasApply) { + return function() { + var args = []; + forEach(arguments, function(arg) { + args.push(formatError(arg)); + }); + return logFn.apply(console, args); + }; + } + + // we are IE which either doesn't have window.console => this is noop and we do nothing, + // or we are IE where console.log doesn't have apply so we log at least first 2 args + return function(arg1, arg2) { + logFn(arg1, arg2 == null ? '' : arg2); + }; + } + }]; +} + +var $parseMinErr = minErr('$parse'); +var promiseWarningCache = {}; +var promiseWarning; + +// Sandboxing Angular Expressions +// ------------------------------ +// Angular expressions are generally considered safe because these expressions only have direct +// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by +// obtaining a reference to native JS functions such as the Function constructor. +// +// As an example, consider the following Angular expression: +// +// {}.toString.constructor(alert("evil JS code")) +// +// We want to prevent this type of access. For the sake of performance, during the lexing phase we +// disallow any "dotted" access to any member named "constructor". +// +// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor +// while evaluating the expression, which is a stronger but more expensive test. Since reflective +// calls are expensive anyway, this is not such a big deal compared to static dereferencing. +// +// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits +// against the expression language, but not to prevent exploits that were enabled by exposing +// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good +// practice and therefore we are not even trying to protect against interaction with an object +// explicitly exposed in this way. +// +// A developer could foil the name check by aliasing the Function constructor under a different +// name on the scope. +// +// In general, it is not possible to access a Window object from an angular expression unless a +// window or some DOM object that has a reference to window is published onto a Scope. + +function ensureSafeMemberName(name, fullExpression) { + if (name === "constructor") { + throw $parseMinErr('isecfld', + 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } + return name; +} + +function ensureSafeObject(obj, fullExpression) { + // nifty check if obj is Function that is fast and works across iframes and other contexts + if (obj) { + if (obj.constructor === obj) { + throw $parseMinErr('isecfn', + 'Referencing Function in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else if (// isWindow(obj) + obj.document && obj.location && obj.alert && obj.setInterval) { + throw $parseMinErr('isecwindow', + 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else if (// isElement(obj) + obj.children && (obj.nodeName || (obj.on && obj.find))) { + throw $parseMinErr('isecdom', + 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } + } + return obj; +} + +var OPERATORS = { + /* jshint bitwise : false */ + 'null':function(){return null;}, + 'true':function(){return true;}, + 'false':function(){return false;}, + undefined:noop, + '+':function(self, locals, a,b){ + a=a(self, locals); b=b(self, locals); + if (isDefined(a)) { + if (isDefined(b)) { + return a + b; + } + return a; + } + return isDefined(b)?b:undefined;}, + '-':function(self, locals, a,b){ + a=a(self, locals); b=b(self, locals); + return (isDefined(a)?a:0)-(isDefined(b)?b:0); + }, + '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, + '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, + '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, + '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, + '=':noop, + '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, + '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, + '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, + '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, + '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, + '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, + '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, + '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, + '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, + '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, +// '|':function(self, locals, a,b){return a|b;}, + '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, + '!':function(self, locals, a){return !a(self, locals);} +}; +/* jshint bitwise: true */ +var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; + + +///////////////////////////////////////// + + +/** + * @constructor + */ +var Lexer = function (options) { + this.options = options; +}; + +Lexer.prototype = { + constructor: Lexer, + + lex: function (text) { + this.text = text; + + this.index = 0; + this.ch = undefined; + this.lastCh = ':'; // can start regexp + + this.tokens = []; + + var token; + var json = []; + + while (this.index < this.text.length) { + this.ch = this.text.charAt(this.index); + if (this.is('"\'')) { + this.readString(this.ch); + } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { + this.readNumber(); + } else if (this.isIdent(this.ch)) { + this.readIdent(); + // identifiers can only be if the preceding char was a { or , + if (this.was('{,') && json[0] === '{' && + (token = this.tokens[this.tokens.length - 1])) { + token.json = token.text.indexOf('.') === -1; + } + } else if (this.is('(){}[].,;:?')) { + this.tokens.push({ + index: this.index, + text: this.ch, + json: (this.was(':[,') && this.is('{[')) || this.is('}]:,') + }); + if (this.is('{[')) json.unshift(this.ch); + if (this.is('}]')) json.shift(); + this.index++; + } else if (this.isWhitespace(this.ch)) { + this.index++; + continue; + } else { + var ch2 = this.ch + this.peek(); + var ch3 = ch2 + this.peek(2); + var fn = OPERATORS[this.ch]; + var fn2 = OPERATORS[ch2]; + var fn3 = OPERATORS[ch3]; + if (fn3) { + this.tokens.push({index: this.index, text: ch3, fn: fn3}); + this.index += 3; + } else if (fn2) { + this.tokens.push({index: this.index, text: ch2, fn: fn2}); + this.index += 2; + } else if (fn) { + this.tokens.push({ + index: this.index, + text: this.ch, + fn: fn, + json: (this.was('[,:') && this.is('+-')) + }); + this.index += 1; + } else { + this.throwError('Unexpected next character ', this.index, this.index + 1); + } + } + this.lastCh = this.ch; + } + return this.tokens; + }, + + is: function(chars) { + return chars.indexOf(this.ch) !== -1; + }, + + was: function(chars) { + return chars.indexOf(this.lastCh) !== -1; + }, + + peek: function(i) { + var num = i || 1; + return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; + }, + + isNumber: function(ch) { + return ('0' <= ch && ch <= '9'); + }, + + isWhitespace: function(ch) { + // IE treats non-breaking space as \u00A0 + return (ch === ' ' || ch === '\r' || ch === '\t' || + ch === '\n' || ch === '\v' || ch === '\u00A0'); + }, + + isIdent: function(ch) { + return ('a' <= ch && ch <= 'z' || + 'A' <= ch && ch <= 'Z' || + '_' === ch || ch === '$'); + }, + + isExpOperator: function(ch) { + return (ch === '-' || ch === '+' || this.isNumber(ch)); + }, + + throwError: function(error, start, end) { + end = end || this.index; + var colStr = (isDefined(start) + ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' + : ' ' + end); + throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', + error, colStr, this.text); + }, + + readNumber: function() { + var number = ''; + var start = this.index; + while (this.index < this.text.length) { + var ch = lowercase(this.text.charAt(this.index)); + if (ch == '.' || this.isNumber(ch)) { + number += ch; + } else { + var peekCh = this.peek(); + if (ch == 'e' && this.isExpOperator(peekCh)) { + number += ch; + } else if (this.isExpOperator(ch) && + peekCh && this.isNumber(peekCh) && + number.charAt(number.length - 1) == 'e') { + number += ch; + } else if (this.isExpOperator(ch) && + (!peekCh || !this.isNumber(peekCh)) && + number.charAt(number.length - 1) == 'e') { + this.throwError('Invalid exponent'); + } else { + break; + } + } + this.index++; + } + number = 1 * number; + this.tokens.push({ + index: start, + text: number, + json: true, + fn: function() { return number; } + }); + }, + + readIdent: function() { + var parser = this; + + var ident = ''; + var start = this.index; + + var lastDot, peekIndex, methodName, ch; + + while (this.index < this.text.length) { + ch = this.text.charAt(this.index); + if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { + if (ch === '.') lastDot = this.index; + ident += ch; + } else { + break; + } + this.index++; + } + + //check if this is not a method invocation and if it is back out to last dot + if (lastDot) { + peekIndex = this.index; + while (peekIndex < this.text.length) { + ch = this.text.charAt(peekIndex); + if (ch === '(') { + methodName = ident.substr(lastDot - start + 1); + ident = ident.substr(0, lastDot - start); + this.index = peekIndex; + break; + } + if (this.isWhitespace(ch)) { + peekIndex++; + } else { + break; + } + } + } + + + var token = { + index: start, + text: ident + }; + + // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn + if (OPERATORS.hasOwnProperty(ident)) { + token.fn = OPERATORS[ident]; + token.json = OPERATORS[ident]; + } else { + var getter = getterFn(ident, this.options, this.text); + token.fn = extend(function(self, locals) { + return (getter(self, locals)); + }, { + assign: function(self, value) { + return setter(self, ident, value, parser.text, parser.options); + } + }); + } + + this.tokens.push(token); + + if (methodName) { + this.tokens.push({ + index:lastDot, + text: '.', + json: false + }); + this.tokens.push({ + index: lastDot + 1, + text: methodName, + json: false + }); + } + }, + + readString: function(quote) { + var start = this.index; + this.index++; + var string = ''; + var rawString = quote; + var escape = false; + while (this.index < this.text.length) { + var ch = this.text.charAt(this.index); + rawString += ch; + if (escape) { + if (ch === 'u') { + var hex = this.text.substring(this.index + 1, this.index + 5); + if (!hex.match(/[\da-f]{4}/i)) + this.throwError('Invalid unicode escape [\\u' + hex + ']'); + this.index += 4; + string += String.fromCharCode(parseInt(hex, 16)); + } else { + var rep = ESCAPE[ch]; + if (rep) { + string += rep; + } else { + string += ch; + } + } + escape = false; + } else if (ch === '\\') { + escape = true; + } else if (ch === quote) { + this.index++; + this.tokens.push({ + index: start, + text: rawString, + string: string, + json: true, + fn: function() { return string; } + }); + return; + } else { + string += ch; + } + this.index++; + } + this.throwError('Unterminated quote', start); + } +}; + + +/** + * @constructor + */ +var Parser = function (lexer, $filter, options) { + this.lexer = lexer; + this.$filter = $filter; + this.options = options; +}; + +Parser.ZERO = function () { return 0; }; + +Parser.prototype = { + constructor: Parser, + + parse: function (text, json) { + this.text = text; + + //TODO(i): strip all the obsolte json stuff from this file + this.json = json; + + this.tokens = this.lexer.lex(text); + + if (json) { + // The extra level of aliasing is here, just in case the lexer misses something, so that + // we prevent any accidental execution in JSON. + this.assignment = this.logicalOR; + + this.functionCall = + this.fieldAccess = + this.objectIndex = + this.filterChain = function() { + this.throwError('is not valid json', {text: text, index: 0}); + }; + } + + var value = json ? this.primary() : this.statements(); + + if (this.tokens.length !== 0) { + this.throwError('is an unexpected token', this.tokens[0]); + } + + value.literal = !!value.literal; + value.constant = !!value.constant; + + return value; + }, + + primary: function () { + var primary; + if (this.expect('(')) { + primary = this.filterChain(); + this.consume(')'); + } else if (this.expect('[')) { + primary = this.arrayDeclaration(); + } else if (this.expect('{')) { + primary = this.object(); + } else { + var token = this.expect(); + primary = token.fn; + if (!primary) { + this.throwError('not a primary expression', token); + } + if (token.json) { + primary.constant = true; + primary.literal = true; + } + } + + var next, context; + while ((next = this.expect('(', '[', '.'))) { + if (next.text === '(') { + primary = this.functionCall(primary, context); + context = null; + } else if (next.text === '[') { + context = primary; + primary = this.objectIndex(primary); + } else if (next.text === '.') { + context = primary; + primary = this.fieldAccess(primary); + } else { + this.throwError('IMPOSSIBLE'); + } + } + return primary; + }, + + throwError: function(msg, token) { + throw $parseMinErr('syntax', + 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', + token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); + }, + + peekToken: function() { + if (this.tokens.length === 0) + throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); + return this.tokens[0]; + }, + + peek: function(e1, e2, e3, e4) { + if (this.tokens.length > 0) { + var token = this.tokens[0]; + var t = token.text; + if (t === e1 || t === e2 || t === e3 || t === e4 || + (!e1 && !e2 && !e3 && !e4)) { + return token; + } + } + return false; + }, + + expect: function(e1, e2, e3, e4){ + var token = this.peek(e1, e2, e3, e4); + if (token) { + if (this.json && !token.json) { + this.throwError('is not valid json', token); + } + this.tokens.shift(); + return token; + } + return false; + }, + + consume: function(e1){ + if (!this.expect(e1)) { + this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); + } + }, + + unaryFn: function(fn, right) { + return extend(function(self, locals) { + return fn(self, locals, right); + }, { + constant:right.constant + }); + }, + + ternaryFn: function(left, middle, right){ + return extend(function(self, locals){ + return left(self, locals) ? middle(self, locals) : right(self, locals); + }, { + constant: left.constant && middle.constant && right.constant + }); + }, + + binaryFn: function(left, fn, right) { + return extend(function(self, locals) { + return fn(self, locals, left, right); + }, { + constant:left.constant && right.constant + }); + }, + + statements: function() { + var statements = []; + while (true) { + if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) + statements.push(this.filterChain()); + if (!this.expect(';')) { + // optimize for the common case where there is only one statement. + // TODO(size): maybe we should not support multiple statements? + return (statements.length === 1) + ? statements[0] + : function(self, locals) { + var value; + for (var i = 0; i < statements.length; i++) { + var statement = statements[i]; + if (statement) { + value = statement(self, locals); + } + } + return value; + }; + } + } + }, + + filterChain: function() { + var left = this.expression(); + var token; + while (true) { + if ((token = this.expect('|'))) { + left = this.binaryFn(left, token.fn, this.filter()); + } else { + return left; + } + } + }, + + filter: function() { + var token = this.expect(); + var fn = this.$filter(token.text); + var argsFn = []; + while (true) { + if ((token = this.expect(':'))) { + argsFn.push(this.expression()); + } else { + var fnInvoke = function(self, locals, input) { + var args = [input]; + for (var i = 0; i < argsFn.length; i++) { + args.push(argsFn[i](self, locals)); + } + return fn.apply(self, args); + }; + return function() { + return fnInvoke; + }; + } + } + }, + + expression: function() { + return this.assignment(); + }, + + assignment: function() { + var left = this.ternary(); + var right; + var token; + if ((token = this.expect('='))) { + if (!left.assign) { + this.throwError('implies assignment but [' + + this.text.substring(0, token.index) + '] can not be assigned to', token); + } + right = this.ternary(); + return function(scope, locals) { + return left.assign(scope, right(scope, locals), locals); + }; + } + return left; + }, + + ternary: function() { + var left = this.logicalOR(); + var middle; + var token; + if ((token = this.expect('?'))) { + middle = this.ternary(); + if ((token = this.expect(':'))) { + return this.ternaryFn(left, middle, this.ternary()); + } else { + this.throwError('expected :', token); + } + } else { + return left; + } + }, + + logicalOR: function() { + var left = this.logicalAND(); + var token; + while (true) { + if ((token = this.expect('||'))) { + left = this.binaryFn(left, token.fn, this.logicalAND()); + } else { + return left; + } + } + }, + + logicalAND: function() { + var left = this.equality(); + var token; + if ((token = this.expect('&&'))) { + left = this.binaryFn(left, token.fn, this.logicalAND()); + } + return left; + }, + + equality: function() { + var left = this.relational(); + var token; + if ((token = this.expect('==','!=','===','!=='))) { + left = this.binaryFn(left, token.fn, this.equality()); + } + return left; + }, + + relational: function() { + var left = this.additive(); + var token; + if ((token = this.expect('<', '>', '<=', '>='))) { + left = this.binaryFn(left, token.fn, this.relational()); + } + return left; + }, + + additive: function() { + var left = this.multiplicative(); + var token; + while ((token = this.expect('+','-'))) { + left = this.binaryFn(left, token.fn, this.multiplicative()); + } + return left; + }, + + multiplicative: function() { + var left = this.unary(); + var token; + while ((token = this.expect('*','/','%'))) { + left = this.binaryFn(left, token.fn, this.unary()); + } + return left; + }, + + unary: function() { + var token; + if (this.expect('+')) { + return this.primary(); + } else if ((token = this.expect('-'))) { + return this.binaryFn(Parser.ZERO, token.fn, this.unary()); + } else if ((token = this.expect('!'))) { + return this.unaryFn(token.fn, this.unary()); + } else { + return this.primary(); + } + }, + + fieldAccess: function(object) { + var parser = this; + var field = this.expect().text; + var getter = getterFn(field, this.options, this.text); + + return extend(function(scope, locals, self) { + return getter(self || object(scope, locals), locals); + }, { + assign: function(scope, value, locals) { + return setter(object(scope, locals), field, value, parser.text, parser.options); + } + }); + }, + + objectIndex: function(obj) { + var parser = this; + + var indexFn = this.expression(); + this.consume(']'); + + return extend(function(self, locals) { + var o = obj(self, locals), + i = indexFn(self, locals), + v, p; + + if (!o) return undefined; + v = ensureSafeObject(o[i], parser.text); + if (v && v.then && parser.options.unwrapPromises) { + p = v; + if (!('$$v' in v)) { + p.$$v = undefined; + p.then(function(val) { p.$$v = val; }); + } + v = v.$$v; + } + return v; + }, { + assign: function(self, value, locals) { + var key = indexFn(self, locals); + // prevent overwriting of Function.constructor which would break ensureSafeObject check + var safe = ensureSafeObject(obj(self, locals), parser.text); + return safe[key] = value; + } + }); + }, + + functionCall: function(fn, contextGetter) { + var argsFn = []; + if (this.peekToken().text !== ')') { + do { + argsFn.push(this.expression()); + } while (this.expect(',')); + } + this.consume(')'); + + var parser = this; + + return function(scope, locals) { + var args = []; + var context = contextGetter ? contextGetter(scope, locals) : scope; + + for (var i = 0; i < argsFn.length; i++) { + args.push(argsFn[i](scope, locals)); + } + var fnPtr = fn(scope, locals, context) || noop; + + ensureSafeObject(context, parser.text); + ensureSafeObject(fnPtr, parser.text); + + // IE stupidity! (IE doesn't have apply for some native functions) + var v = fnPtr.apply + ? fnPtr.apply(context, args) + : fnPtr(args[0], args[1], args[2], args[3], args[4]); + + return ensureSafeObject(v, parser.text); + }; + }, + + // This is used with json array declaration + arrayDeclaration: function () { + var elementFns = []; + var allConstant = true; + if (this.peekToken().text !== ']') { + do { + var elementFn = this.expression(); + elementFns.push(elementFn); + if (!elementFn.constant) { + allConstant = false; + } + } while (this.expect(',')); + } + this.consume(']'); + + return extend(function(self, locals) { + var array = []; + for (var i = 0; i < elementFns.length; i++) { + array.push(elementFns[i](self, locals)); + } + return array; + }, { + literal: true, + constant: allConstant + }); + }, + + object: function () { + var keyValues = []; + var allConstant = true; + if (this.peekToken().text !== '}') { + do { + var token = this.expect(), + key = token.string || token.text; + this.consume(':'); + var value = this.expression(); + keyValues.push({key: key, value: value}); + if (!value.constant) { + allConstant = false; + } + } while (this.expect(',')); + } + this.consume('}'); + + return extend(function(self, locals) { + var object = {}; + for (var i = 0; i < keyValues.length; i++) { + var keyValue = keyValues[i]; + object[keyValue.key] = keyValue.value(self, locals); + } + return object; + }, { + literal: true, + constant: allConstant + }); + } +}; + + +////////////////////////////////////////////////// +// Parser helper functions +////////////////////////////////////////////////// + +function setter(obj, path, setValue, fullExp, options) { + //needed? + options = options || {}; + + var element = path.split('.'), key; + for (var i = 0; element.length > 1; i++) { + key = ensureSafeMemberName(element.shift(), fullExp); + var propertyObj = obj[key]; + if (!propertyObj) { + propertyObj = {}; + obj[key] = propertyObj; + } + obj = propertyObj; + if (obj.then && options.unwrapPromises) { + promiseWarning(fullExp); + if (!("$$v" in obj)) { + (function(promise) { + promise.then(function(val) { promise.$$v = val; }); } + )(obj); + } + if (obj.$$v === undefined) { + obj.$$v = {}; + } + obj = obj.$$v; + } + } + key = ensureSafeMemberName(element.shift(), fullExp); + obj[key] = setValue; + return setValue; +} + +var getterFnCache = {}; + +/** + * Implementation of the "Black Hole" variant from: + * - http://jsperf.com/angularjs-parse-getter/4 + * - http://jsperf.com/path-evaluation-simplified/7 + */ +function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { + ensureSafeMemberName(key0, fullExp); + ensureSafeMemberName(key1, fullExp); + ensureSafeMemberName(key2, fullExp); + ensureSafeMemberName(key3, fullExp); + ensureSafeMemberName(key4, fullExp); + + return !options.unwrapPromises + ? function cspSafeGetter(scope, locals) { + var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; + + if (pathVal == null) return pathVal; + pathVal = pathVal[key0]; + + if (!key1) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key1]; + + if (!key2) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key2]; + + if (!key3) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key3]; + + if (!key4) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key4]; + + return pathVal; + } + : function cspSafePromiseEnabledGetter(scope, locals) { + var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, + promise; + + if (pathVal == null) return pathVal; + + pathVal = pathVal[key0]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + + if (!key1) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key1]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + + if (!key2) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key2]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + + if (!key3) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key3]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + + if (!key4) return pathVal; + if (pathVal == null) return undefined; + pathVal = pathVal[key4]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + return pathVal; + }; +} + +function simpleGetterFn1(key0, fullExp) { + ensureSafeMemberName(key0, fullExp); + + return function simpleGetterFn1(scope, locals) { + if (scope == null) return undefined; + return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; + }; +} + +function simpleGetterFn2(key0, key1, fullExp) { + ensureSafeMemberName(key0, fullExp); + ensureSafeMemberName(key1, fullExp); + + return function simpleGetterFn2(scope, locals) { + if (scope == null) return undefined; + scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; + return scope == null ? undefined : scope[key1]; + }; +} + +function getterFn(path, options, fullExp) { + // Check whether the cache has this getter already. + // We can use hasOwnProperty directly on the cache because we ensure, + // see below, that the cache never stores a path called 'hasOwnProperty' + if (getterFnCache.hasOwnProperty(path)) { + return getterFnCache[path]; + } + + var pathKeys = path.split('.'), + pathKeysLength = pathKeys.length, + fn; + + // When we have only 1 or 2 tokens, use optimized special case closures. + // http://jsperf.com/angularjs-parse-getter/6 + if (!options.unwrapPromises && pathKeysLength === 1) { + fn = simpleGetterFn1(pathKeys[0], fullExp); + } else if (!options.unwrapPromises && pathKeysLength === 2) { + fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp); + } else if (options.csp) { + if (pathKeysLength < 6) { + fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, + options); + } else { + fn = function(scope, locals) { + var i = 0, val; + do { + val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], + pathKeys[i++], fullExp, options)(scope, locals); + + locals = undefined; // clear after first iteration + scope = val; + } while (i < pathKeysLength); + return val; + }; + } + } else { + var code = 'var p;\n'; + forEach(pathKeys, function(key, index) { + ensureSafeMemberName(key, fullExp); + code += 'if(s == null) return undefined;\n' + + 's='+ (index + // we simply dereference 's' on any .dot notation + ? 's' + // but if we are first then we check locals first, and if so read it first + : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + + (options.unwrapPromises + ? 'if (s && s.then) {\n' + + ' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' + + ' if (!("$$v" in s)) {\n' + + ' p=s;\n' + + ' p.$$v = undefined;\n' + + ' p.then(function(v) {p.$$v=v;});\n' + + '}\n' + + ' s=s.$$v\n' + + '}\n' + : ''); + }); + code += 'return s;'; + + /* jshint -W054 */ + var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning + /* jshint +W054 */ + evaledFnGetter.toString = valueFn(code); + fn = options.unwrapPromises ? function(scope, locals) { + return evaledFnGetter(scope, locals, promiseWarning); + } : evaledFnGetter; + } + + // Only cache the value if it's not going to mess up the cache object + // This is more performant that using Object.prototype.hasOwnProperty.call + if (path !== 'hasOwnProperty') { + getterFnCache[path] = fn; + } + return fn; +} + +/////////////////////////////////// + +/** + * @ngdoc function + * @name ng.$parse + * @function + * + * @description + * + * Converts Angular {@link guide/expression expression} into a function. + * + *
      + *   var getter = $parse('user.name');
      + *   var setter = getter.assign;
      + *   var context = {user:{name:'angular'}};
      + *   var locals = {user:{name:'local'}};
      + *
      + *   expect(getter(context)).toEqual('angular');
      + *   setter(context, 'newValue');
      + *   expect(context.user.name).toEqual('newValue');
      + *   expect(getter(context, locals)).toEqual('local');
      + * 
      + * + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + * + * The returned function also has the following properties: + * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript + * literal. + * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript + * constant literals. + * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be + * set to a function to change its value on the given context. + * + */ + + +/** + * @ngdoc object + * @name ng.$parseProvider + * @function + * + * @description + * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} + * service. + */ +function $ParseProvider() { + var cache = {}; + + var $parseOptions = { + csp: false, + unwrapPromises: false, + logPromiseWarnings: true + }; + + + /** + * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. + * + * @ngdoc method + * @name ng.$parseProvider#unwrapPromises + * @methodOf ng.$parseProvider + * @description + * + * **This feature is deprecated, see deprecation notes below for more info** + * + * If set to true (default is false), $parse will unwrap promises automatically when a promise is + * found at any part of the expression. In other words, if set to true, the expression will always + * result in a non-promise value. + * + * While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, + * the fulfillment value is used in place of the promise while evaluating the expression. + * + * **Deprecation notice** + * + * This is a feature that didn't prove to be wildly useful or popular, primarily because of the + * dichotomy between data access in templates (accessed as raw values) and controller code + * (accessed as promises). + * + * In most code we ended up resolving promises manually in controllers anyway and thus unifying + * the model access there. + * + * Other downsides of automatic promise unwrapping: + * + * - when building components it's often desirable to receive the raw promises + * - adds complexity and slows down expression evaluation + * - makes expression code pre-generation unattractive due to the amount of code that needs to be + * generated + * - makes IDE auto-completion and tool support hard + * + * **Warning Logs** + * + * If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a + * promise (to reduce the noise, each expression is logged only once). To disable this logging use + * `$parseProvider.logPromiseWarnings(false)` api. + * + * + * @param {boolean=} value New value. + * @returns {boolean|self} Returns the current setting when used as getter and self if used as + * setter. + */ + this.unwrapPromises = function(value) { + if (isDefined(value)) { + $parseOptions.unwrapPromises = !!value; + return this; + } else { + return $parseOptions.unwrapPromises; + } + }; + + + /** + * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. + * + * @ngdoc method + * @name ng.$parseProvider#logPromiseWarnings + * @methodOf ng.$parseProvider + * @description + * + * Controls whether Angular should log a warning on any encounter of a promise in an expression. + * + * The default is set to `true`. + * + * This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well. + * + * @param {boolean=} value New value. + * @returns {boolean|self} Returns the current setting when used as getter and self if used as + * setter. + */ + this.logPromiseWarnings = function(value) { + if (isDefined(value)) { + $parseOptions.logPromiseWarnings = value; + return this; + } else { + return $parseOptions.logPromiseWarnings; + } + }; + + + this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { + $parseOptions.csp = $sniffer.csp; + + promiseWarning = function promiseWarningFn(fullExp) { + if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return; + promiseWarningCache[fullExp] = true; + $log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' + + 'Automatic unwrapping of promises in Angular expressions is deprecated.'); + }; + + return function(exp) { + var parsedExpression; + + switch (typeof exp) { + case 'string': + + if (cache.hasOwnProperty(exp)) { + return cache[exp]; + } + + var lexer = new Lexer($parseOptions); + var parser = new Parser(lexer, $filter, $parseOptions); + parsedExpression = parser.parse(exp, false); + + if (exp !== 'hasOwnProperty') { + // Only cache the value if it's not going to mess up the cache object + // This is more performant that using Object.prototype.hasOwnProperty.call + cache[exp] = parsedExpression; + } + + return parsedExpression; + + case 'function': + return exp; + + default: + return noop; + } + }; + }]; +} + +/** + * @ngdoc service + * @name ng.$q + * @requires $rootScope + * + * @description + * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). + * + * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an + * interface for interacting with an object that represents the result of an action that is + * performed asynchronously, and may or may not be finished at any given point in time. + * + * From the perspective of dealing with error handling, deferred and promise APIs are to + * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. + * + *
      + *   // for the purpose of this example let's assume that variables `$q` and `scope` are
      + *   // available in the current lexical scope (they could have been injected or passed in).
      + *
      + *   function asyncGreet(name) {
      + *     var deferred = $q.defer();
      + *
      + *     setTimeout(function() {
      + *       // since this fn executes async in a future turn of the event loop, we need to wrap
      + *       // our code into an $apply call so that the model changes are properly observed.
      + *       scope.$apply(function() {
      + *         deferred.notify('About to greet ' + name + '.');
      + *
      + *         if (okToGreet(name)) {
      + *           deferred.resolve('Hello, ' + name + '!');
      + *         } else {
      + *           deferred.reject('Greeting ' + name + ' is not allowed.');
      + *         }
      + *       });
      + *     }, 1000);
      + *
      + *     return deferred.promise;
      + *   }
      + *
      + *   var promise = asyncGreet('Robin Hood');
      + *   promise.then(function(greeting) {
      + *     alert('Success: ' + greeting);
      + *   }, function(reason) {
      + *     alert('Failed: ' + reason);
      + *   }, function(update) {
      + *     alert('Got notification: ' + update);
      + *   });
      + * 
      + * + * At first it might not be obvious why this extra complexity is worth the trouble. The payoff + * comes in the way of guarantees that promise and deferred APIs make, see + * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. + * + * Additionally the promise api allows for composition that is very hard to do with the + * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. + * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the + * section on serial or parallel joining of promises. + * + * + * # The Deferred API + * + * A new instance of deferred is constructed by calling `$q.defer()`. + * + * The purpose of the deferred object is to expose the associated Promise instance as well as APIs + * that can be used for signaling the successful or unsuccessful completion, as well as the status + * of the task. + * + * **Methods** + * + * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection + * constructed via `$q.reject`, the promise will be rejected instead. + * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to + * resolving it with a rejection constructed via `$q.reject`. + * - `notify(value)` - provides updates on the status of the promises execution. This may be called + * multiple times before the promise is either resolved or rejected. + * + * **Properties** + * + * - promise – `{Promise}` – promise object associated with this deferred. + * + * + * # The Promise API + * + * A new promise instance is created when a deferred instance is created and can be retrieved by + * calling `deferred.promise`. + * + * The purpose of the promise object is to allow for interested parties to get access to the result + * of the deferred task when it completes. + * + * **Methods** + * + * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or + * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously + * as soon as the result is available. The callbacks are called with a single argument: the result + * or rejection reason. Additionally, the notify callback may be called zero or more times to + * provide a progress indication, before the promise is resolved or rejected. + * + * This method *returns a new promise* which is resolved or rejected via the return value of the + * `successCallback`, `errorCallback`. It also notifies via the return value of the + * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback + * method. + * + * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` + * + * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise, + * but to do so without modifying the final value. This is useful to release resources or do some + * clean-up that needs to be done whether the promise was rejected or resolved. See the [full + * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for + * more information. + * + * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as + * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to + * make your code IE8 compatible. + * + * # Chaining promises + * + * Because calling the `then` method of a promise returns a new derived promise, it is easily + * possible to create a chain of promises: + * + *
      + *   promiseB = promiseA.then(function(result) {
      + *     return result + 1;
      + *   });
      + *
      + *   // promiseB will be resolved immediately after promiseA is resolved and its value
      + *   // will be the result of promiseA incremented by 1
      + * 
      + * + * It is possible to create chains of any length and since a promise can be resolved with another + * promise (which will defer its resolution further), it is possible to pause/defer resolution of + * the promises at any point in the chain. This makes it possible to implement powerful APIs like + * $http's response interceptors. + * + * + * # Differences between Kris Kowal's Q and $q + * + * There are two main differences: + * + * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation + * mechanism in angular, which means faster propagation of resolution or rejection into your + * models and avoiding unnecessary browser repaints, which would result in flickering UI. + * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains + * all the important functionality needed for common async tasks. + * + * # Testing + * + *
      + *    it('should simulate promise', inject(function($q, $rootScope) {
      + *      var deferred = $q.defer();
      + *      var promise = deferred.promise;
      + *      var resolvedValue;
      + *
      + *      promise.then(function(value) { resolvedValue = value; });
      + *      expect(resolvedValue).toBeUndefined();
      + *
      + *      // Simulate resolving of promise
      + *      deferred.resolve(123);
      + *      // Note that the 'then' function does not get called synchronously.
      + *      // This is because we want the promise API to always be async, whether or not
      + *      // it got called synchronously or asynchronously.
      + *      expect(resolvedValue).toBeUndefined();
      + *
      + *      // Propagate promise resolution to 'then' functions using $apply().
      + *      $rootScope.$apply();
      + *      expect(resolvedValue).toEqual(123);
      + *    }));
      + *  
      + */ +function $QProvider() { + + this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { + return qFactory(function(callback) { + $rootScope.$evalAsync(callback); + }, $exceptionHandler); + }]; +} + + +/** + * Constructs a promise manager. + * + * @param {function(function)} nextTick Function for executing functions in the next turn. + * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for + * debugging purposes. + * @returns {object} Promise manager. + */ +function qFactory(nextTick, exceptionHandler) { + + /** + * @ngdoc + * @name ng.$q#defer + * @methodOf ng.$q + * @description + * Creates a `Deferred` object which represents a task which will finish in the future. + * + * @returns {Deferred} Returns a new instance of deferred. + */ + var defer = function() { + var pending = [], + value, deferred; + + deferred = { + + resolve: function(val) { + if (pending) { + var callbacks = pending; + pending = undefined; + value = ref(val); + + if (callbacks.length) { + nextTick(function() { + var callback; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + callback = callbacks[i]; + value.then(callback[0], callback[1], callback[2]); + } + }); + } + } + }, + + + reject: function(reason) { + deferred.resolve(reject(reason)); + }, + + + notify: function(progress) { + if (pending) { + var callbacks = pending; + + if (pending.length) { + nextTick(function() { + var callback; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + callback = callbacks[i]; + callback[2](progress); + } + }); + } + } + }, + + + promise: { + then: function(callback, errback, progressback) { + var result = defer(); + + var wrappedCallback = function(value) { + try { + result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); + } catch(e) { + result.reject(e); + exceptionHandler(e); + } + }; + + var wrappedErrback = function(reason) { + try { + result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); + } catch(e) { + result.reject(e); + exceptionHandler(e); + } + }; + + var wrappedProgressback = function(progress) { + try { + result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); + } catch(e) { + exceptionHandler(e); + } + }; + + if (pending) { + pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); + } else { + value.then(wrappedCallback, wrappedErrback, wrappedProgressback); + } + + return result.promise; + }, + + "catch": function(callback) { + return this.then(null, callback); + }, + + "finally": function(callback) { + + function makePromise(value, resolved) { + var result = defer(); + if (resolved) { + result.resolve(value); + } else { + result.reject(value); + } + return result.promise; + } + + function handleCallback(value, isResolved) { + var callbackOutput = null; + try { + callbackOutput = (callback ||defaultCallback)(); + } catch(e) { + return makePromise(e, false); + } + if (callbackOutput && isFunction(callbackOutput.then)) { + return callbackOutput.then(function() { + return makePromise(value, isResolved); + }, function(error) { + return makePromise(error, false); + }); + } else { + return makePromise(value, isResolved); + } + } + + return this.then(function(value) { + return handleCallback(value, true); + }, function(error) { + return handleCallback(error, false); + }); + } + } + }; + + return deferred; + }; + + + var ref = function(value) { + if (value && isFunction(value.then)) return value; + return { + then: function(callback) { + var result = defer(); + nextTick(function() { + result.resolve(callback(value)); + }); + return result.promise; + } + }; + }; + + + /** + * @ngdoc + * @name ng.$q#reject + * @methodOf ng.$q + * @description + * Creates a promise that is resolved as rejected with the specified `reason`. This api should be + * used to forward rejection in a chain of promises. If you are dealing with the last promise in + * a promise chain, you don't need to worry about it. + * + * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of + * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via + * a promise error callback and you want to forward the error to the promise derived from the + * current promise, you have to "rethrow" the error by returning a rejection constructed via + * `reject`. + * + *
      +   *   promiseB = promiseA.then(function(result) {
      +   *     // success: do something and resolve promiseB
      +   *     //          with the old or a new result
      +   *     return result;
      +   *   }, function(reason) {
      +   *     // error: handle the error if possible and
      +   *     //        resolve promiseB with newPromiseOrValue,
      +   *     //        otherwise forward the rejection to promiseB
      +   *     if (canHandle(reason)) {
      +   *      // handle the error and recover
      +   *      return newPromiseOrValue;
      +   *     }
      +   *     return $q.reject(reason);
      +   *   });
      +   * 
      + * + * @param {*} reason Constant, message, exception or an object representing the rejection reason. + * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. + */ + var reject = function(reason) { + return { + then: function(callback, errback) { + var result = defer(); + nextTick(function() { + try { + result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); + } catch(e) { + result.reject(e); + exceptionHandler(e); + } + }); + return result.promise; + } + }; + }; + + + /** + * @ngdoc + * @name ng.$q#when + * @methodOf ng.$q + * @description + * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. + * This is useful when you are dealing with an object that might or might not be a promise, or if + * the promise comes from a source that can't be trusted. + * + * @param {*} value Value or a promise + * @returns {Promise} Returns a promise of the passed value or promise + */ + var when = function(value, callback, errback, progressback) { + var result = defer(), + done; + + var wrappedCallback = function(value) { + try { + return (isFunction(callback) ? callback : defaultCallback)(value); + } catch (e) { + exceptionHandler(e); + return reject(e); + } + }; + + var wrappedErrback = function(reason) { + try { + return (isFunction(errback) ? errback : defaultErrback)(reason); + } catch (e) { + exceptionHandler(e); + return reject(e); + } + }; + + var wrappedProgressback = function(progress) { + try { + return (isFunction(progressback) ? progressback : defaultCallback)(progress); + } catch (e) { + exceptionHandler(e); + } + }; + + nextTick(function() { + ref(value).then(function(value) { + if (done) return; + done = true; + result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); + }, function(reason) { + if (done) return; + done = true; + result.resolve(wrappedErrback(reason)); + }, function(progress) { + if (done) return; + result.notify(wrappedProgressback(progress)); + }); + }); + + return result.promise; + }; + + + function defaultCallback(value) { + return value; + } + + + function defaultErrback(reason) { + return reject(reason); + } + + + /** + * @ngdoc + * @name ng.$q#all + * @methodOf ng.$q + * @description + * Combines multiple promises into a single promise that is resolved when all of the input + * promises are resolved. + * + * @param {Array.|Object.} promises An array or hash of promises. + * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, + * each value corresponding to the promise at the same index/key in the `promises` array/hash. + * If any of the promises is resolved with a rejection, this resulting promise will be rejected + * with the same rejection value. + */ + function all(promises) { + var deferred = defer(), + counter = 0, + results = isArray(promises) ? [] : {}; + + forEach(promises, function(promise, key) { + counter++; + ref(promise).then(function(value) { + if (results.hasOwnProperty(key)) return; + results[key] = value; + if (!(--counter)) deferred.resolve(results); + }, function(reason) { + if (results.hasOwnProperty(key)) return; + deferred.reject(reason); + }); + }); + + if (counter === 0) { + deferred.resolve(results); + } + + return deferred.promise; + } + + return { + defer: defer, + reject: reject, + when: when, + all: all + }; +} + +/** + * DESIGN NOTES + * + * The design decisions behind the scope are heavily favored for speed and memory consumption. + * + * The typical use of scope is to watch the expressions, which most of the time return the same + * value as last time so we optimize the operation. + * + * Closures construction is expensive in terms of speed as well as memory: + * - No closures, instead use prototypical inheritance for API + * - Internal state needs to be stored on scope directly, which means that private state is + * exposed as $$____ properties + * + * Loop operations are optimized by using while(count--) { ... } + * - this means that in order to keep the same order of execution as addition we have to add + * items to the array at the beginning (shift) instead of at the end (push) + * + * Child scopes are created and removed often + * - Using an array would be slow since inserts in middle are expensive so we use linked list + * + * There are few watches then a lot of observers. This is why you don't want the observer to be + * implemented in the same way as watch. Watch requires return of initialization function which + * are expensive to construct. + */ + + +/** + * @ngdoc object + * @name ng.$rootScopeProvider + * @description + * + * Provider for the $rootScope service. + */ + +/** + * @ngdoc function + * @name ng.$rootScopeProvider#digestTtl + * @methodOf ng.$rootScopeProvider + * @description + * + * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and + * assuming that the model is unstable. + * + * The current default is 10 iterations. + * + * In complex applications it's possible that the dependencies between `$watch`s will result in + * several digest iterations. However if an application needs more than the default 10 digest + * iterations for its model to stabilize then you should investigate what is causing the model to + * continuously change during the digest. + * + * Increasing the TTL could have performance implications, so you should not change it without + * proper justification. + * + * @param {number} limit The number of digest iterations. + */ + + +/** + * @ngdoc object + * @name ng.$rootScope + * @description + * + * Every application has a single root {@link ng.$rootScope.Scope scope}. + * All other scopes are descendant scopes of the root scope. Scopes provide separation + * between the model and the view, via a mechanism for watching the model for changes. + * They also provide an event emission/broadcast and subscription facility. See the + * {@link guide/scope developer guide on scopes}. + */ +function $RootScopeProvider(){ + var TTL = 10; + var $rootScopeMinErr = minErr('$rootScope'); + var lastDirtyWatch = null; + + this.digestTtl = function(value) { + if (arguments.length) { + TTL = value; + } + return TTL; + }; + + this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', + function( $injector, $exceptionHandler, $parse, $browser) { + + /** + * @ngdoc function + * @name ng.$rootScope.Scope + * + * @description + * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the + * {@link AUTO.$injector $injector}. Child scopes are created using the + * {@link ng.$rootScope.Scope#methods_$new $new()} method. (Most scopes are created automatically when + * compiled HTML template is executed.) + * + * Here is a simple scope snippet to show how you can interact with the scope. + *
      +     * 
      +     * 
      + * + * # Inheritance + * A scope can inherit from a parent scope, as in this example: + *
      +         var parent = $rootScope;
      +         var child = parent.$new();
      +
      +         parent.salutation = "Hello";
      +         child.name = "World";
      +         expect(child.salutation).toEqual('Hello');
      +
      +         child.salutation = "Welcome";
      +         expect(child.salutation).toEqual('Welcome');
      +         expect(parent.salutation).toEqual('Hello');
      +     * 
      + * + * + * @param {Object.=} providers Map of service factory which need to be + * provided for the current scope. Defaults to {@link ng}. + * @param {Object.=} instanceCache Provides pre-instantiated services which should + * append/override services provided by `providers`. This is handy + * when unit-testing and having the need to override a default + * service. + * @returns {Object} Newly created scope. + * + */ + function Scope() { + this.$id = nextUid(); + this.$$phase = this.$parent = this.$$watchers = + this.$$nextSibling = this.$$prevSibling = + this.$$childHead = this.$$childTail = null; + this['this'] = this.$root = this; + this.$$destroyed = false; + this.$$asyncQueue = []; + this.$$postDigestQueue = []; + this.$$listeners = {}; + this.$$listenerCount = {}; + this.$$isolateBindings = {}; + } + + /** + * @ngdoc property + * @name ng.$rootScope.Scope#$id + * @propertyOf ng.$rootScope.Scope + * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for + * debugging. + */ + + + Scope.prototype = { + constructor: Scope, + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$new + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Creates a new child {@link ng.$rootScope.Scope scope}. + * + * The parent scope will propagate the {@link ng.$rootScope.Scope#methods_$digest $digest()} and + * {@link ng.$rootScope.Scope#methods_$digest $digest()} events. The scope can be removed from the + * scope hierarchy using {@link ng.$rootScope.Scope#methods_$destroy $destroy()}. + * + * {@link ng.$rootScope.Scope#methods_$destroy $destroy()} must be called on a scope when it is + * desired for the scope and its child scopes to be permanently detached from the parent and + * thus stop participating in model change detection and listener notification by invoking. + * + * @param {boolean} isolate If true, then the scope does not prototypically inherit from the + * parent scope. The scope is isolated, as it can not see parent scope properties. + * When creating widgets, it is useful for the widget to not accidentally read parent + * state. + * + * @returns {Object} The newly created child scope. + * + */ + $new: function(isolate) { + var ChildScope, + child; + + if (isolate) { + child = new Scope(); + child.$root = this.$root; + // ensure that there is just one async queue per $rootScope and its children + child.$$asyncQueue = this.$$asyncQueue; + child.$$postDigestQueue = this.$$postDigestQueue; + } else { + ChildScope = function() {}; // should be anonymous; This is so that when the minifier munges + // the name it does not become random set of chars. This will then show up as class + // name in the web inspector. + ChildScope.prototype = this; + child = new ChildScope(); + child.$id = nextUid(); + } + child['this'] = child; + child.$$listeners = {}; + child.$$listenerCount = {}; + child.$parent = this; + child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null; + child.$$prevSibling = this.$$childTail; + if (this.$$childHead) { + this.$$childTail.$$nextSibling = child; + this.$$childTail = child; + } else { + this.$$childHead = this.$$childTail = child; + } + return child; + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$watch + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Registers a `listener` callback to be executed whenever the `watchExpression` changes. + * + * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#methods_$digest + * $digest()} and should return the value that will be watched. (Since + * {@link ng.$rootScope.Scope#methods_$digest $digest()} reruns when it detects changes the + * `watchExpression` can execute multiple times per + * {@link ng.$rootScope.Scope#methods_$digest $digest()} and should be idempotent.) + * - The `listener` is called only when the value from the current `watchExpression` and the + * previous call to `watchExpression` are not equal (with the exception of the initial run, + * see below). The inequality is determined according to + * {@link angular.equals} function. To save the value of the object for later comparison, + * the {@link angular.copy} function is used. It also means that watching complex options + * will have adverse memory and performance implications. + * - The watch `listener` may change the model, which may trigger other `listener`s to fire. + * This is achieved by rerunning the watchers until no changes are detected. The rerun + * iteration limit is 10 to prevent an infinite loop deadlock. + * + * + * If you want to be notified whenever {@link ng.$rootScope.Scope#methods_$digest $digest} is called, + * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` + * can execute multiple times per {@link ng.$rootScope.Scope#methods_$digest $digest} cycle when a + * change is detected, be prepared for multiple calls to your listener.) + * + * After a watcher is registered with the scope, the `listener` fn is called asynchronously + * (via {@link ng.$rootScope.Scope#methods_$evalAsync $evalAsync}) to initialize the + * watcher. In rare cases, this is undesirable because the listener is called when the result + * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you + * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the + * listener was called due to initialization. + * + * The example below contains an illustration of using a function as your $watch listener + * + * + * # Example + *
      +           // let's assume that scope was dependency injected as the $rootScope
      +           var scope = $rootScope;
      +           scope.name = 'misko';
      +           scope.counter = 0;
      +
      +           expect(scope.counter).toEqual(0);
      +           scope.$watch('name', function(newValue, oldValue) {
      +             scope.counter = scope.counter + 1;
      +           });
      +           expect(scope.counter).toEqual(0);
      +
      +           scope.$digest();
      +           // no variable change
      +           expect(scope.counter).toEqual(0);
      +
      +           scope.name = 'adam';
      +           scope.$digest();
      +           expect(scope.counter).toEqual(1);
      +
      +
      +
      +           // Using a listener function
      +           var food;
      +           scope.foodCounter = 0;
      +           expect(scope.foodCounter).toEqual(0);
      +           scope.$watch(
      +             // This is the listener function
      +             function() { return food; },
      +             // This is the change handler
      +             function(newValue, oldValue) {
      +               if ( newValue !== oldValue ) {
      +                 // Only increment the counter if the value changed
      +                 scope.foodCounter = scope.foodCounter + 1;
      +               }
      +             }
      +           );
      +           // No digest has been run so the counter will be zero
      +           expect(scope.foodCounter).toEqual(0);
      +
      +           // Run the digest but since food has not changed count will still be zero
      +           scope.$digest();
      +           expect(scope.foodCounter).toEqual(0);
      +
      +           // Update food and run digest.  Now the counter will increment
      +           food = 'cheeseburger';
      +           scope.$digest();
      +           expect(scope.foodCounter).toEqual(1);
      +
      +       * 
      + * + * + * + * @param {(function()|string)} watchExpression Expression that is evaluated on each + * {@link ng.$rootScope.Scope#methods_$digest $digest} cycle. A change in the return value triggers + * a call to the `listener`. + * + * - `string`: Evaluated as {@link guide/expression expression} + * - `function(scope)`: called with current `scope` as a parameter. + * @param {(function()|string)=} listener Callback called whenever the return value of + * the `watchExpression` changes. + * + * - `string`: Evaluated as {@link guide/expression expression} + * - `function(newValue, oldValue, scope)`: called with current and previous values as + * parameters. + * + * @param {boolean=} objectEquality Compare object for equality rather than for reference. + * @returns {function()} Returns a deregistration function for this listener. + */ + $watch: function(watchExp, listener, objectEquality) { + var scope = this, + get = compileToFn(watchExp, 'watch'), + array = scope.$$watchers, + watcher = { + fn: listener, + last: initWatchVal, + get: get, + exp: watchExp, + eq: !!objectEquality + }; + + lastDirtyWatch = null; + + // in the case user pass string, we need to compile it, do we really need this ? + if (!isFunction(listener)) { + var listenFn = compileToFn(listener || noop, 'listener'); + watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; + } + + if (typeof watchExp == 'string' && get.constant) { + var originalFn = watcher.fn; + watcher.fn = function(newVal, oldVal, scope) { + originalFn.call(this, newVal, oldVal, scope); + arrayRemove(array, watcher); + }; + } + + if (!array) { + array = scope.$$watchers = []; + } + // we use unshift since we use a while loop in $digest for speed. + // the while loop reads in reverse order. + array.unshift(watcher); + + return function() { + arrayRemove(array, watcher); + lastDirtyWatch = null; + }; + }, + + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$watchCollection + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Shallow watches the properties of an object and fires whenever any of the properties change + * (for arrays, this implies watching the array items; for object maps, this implies watching + * the properties). If a change is detected, the `listener` callback is fired. + * + * - The `obj` collection is observed via standard $watch operation and is examined on every + * call to $digest() to see if any items have been added, removed, or moved. + * - The `listener` is called whenever anything within the `obj` has changed. Examples include + * adding, removing, and moving items belonging to an object or array. + * + * + * # Example + *
      +          $scope.names = ['igor', 'matias', 'misko', 'james'];
      +          $scope.dataCount = 4;
      +
      +          $scope.$watchCollection('names', function(newNames, oldNames) {
      +            $scope.dataCount = newNames.length;
      +          });
      +
      +          expect($scope.dataCount).toEqual(4);
      +          $scope.$digest();
      +
      +          //still at 4 ... no changes
      +          expect($scope.dataCount).toEqual(4);
      +
      +          $scope.names.pop();
      +          $scope.$digest();
      +
      +          //now there's been a change
      +          expect($scope.dataCount).toEqual(3);
      +       * 
      + * + * + * @param {string|Function(scope)} obj Evaluated as {@link guide/expression expression}. The + * expression value should evaluate to an object or an array which is observed on each + * {@link ng.$rootScope.Scope#methods_$digest $digest} cycle. Any shallow change within the + * collection will trigger a call to the `listener`. + * + * @param {function(newCollection, oldCollection, scope)} listener a callback function that is + * fired with both the `newCollection` and `oldCollection` as parameters. + * The `newCollection` object is the newly modified data obtained from the `obj` expression + * and the `oldCollection` object is a copy of the former collection data. + * The `scope` refers to the current scope. + * + * @returns {function()} Returns a de-registration function for this listener. When the + * de-registration function is executed, the internal watch operation is terminated. + */ + $watchCollection: function(obj, listener) { + var self = this; + var oldValue; + var newValue; + var changeDetected = 0; + var objGetter = $parse(obj); + var internalArray = []; + var internalObject = {}; + var oldLength = 0; + + function $watchCollectionWatch() { + newValue = objGetter(self); + var newLength, key; + + if (!isObject(newValue)) { + if (oldValue !== newValue) { + oldValue = newValue; + changeDetected++; + } + } else if (isArrayLike(newValue)) { + if (oldValue !== internalArray) { + // we are transitioning from something which was not an array into array. + oldValue = internalArray; + oldLength = oldValue.length = 0; + changeDetected++; + } + + newLength = newValue.length; + + if (oldLength !== newLength) { + // if lengths do not match we need to trigger change notification + changeDetected++; + oldValue.length = oldLength = newLength; + } + // copy the items to oldValue and look for changes. + for (var i = 0; i < newLength; i++) { + if (oldValue[i] !== newValue[i]) { + changeDetected++; + oldValue[i] = newValue[i]; + } + } + } else { + if (oldValue !== internalObject) { + // we are transitioning from something which was not an object into object. + oldValue = internalObject = {}; + oldLength = 0; + changeDetected++; + } + // copy the items to oldValue and look for changes. + newLength = 0; + for (key in newValue) { + if (newValue.hasOwnProperty(key)) { + newLength++; + if (oldValue.hasOwnProperty(key)) { + if (oldValue[key] !== newValue[key]) { + changeDetected++; + oldValue[key] = newValue[key]; + } + } else { + oldLength++; + oldValue[key] = newValue[key]; + changeDetected++; + } + } + } + if (oldLength > newLength) { + // we used to have more keys, need to find them and destroy them. + changeDetected++; + for(key in oldValue) { + if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { + oldLength--; + delete oldValue[key]; + } + } + } + } + return changeDetected; + } + + function $watchCollectionAction() { + listener(newValue, oldValue, self); + } + + return this.$watch($watchCollectionWatch, $watchCollectionAction); + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$digest + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Processes all of the {@link ng.$rootScope.Scope#methods_$watch watchers} of the current scope and + * its children. Because a {@link ng.$rootScope.Scope#methods_$watch watcher}'s listener can change + * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#methods_$watch watchers} + * until no more listeners are firing. This means that it is possible to get into an infinite + * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of + * iterations exceeds 10. + * + * Usually, you don't call `$digest()` directly in + * {@link ng.directive:ngController controllers} or in + * {@link ng.$compileProvider#methods_directive directives}. + * Instead, you should call {@link ng.$rootScope.Scope#methods_$apply $apply()} (typically from within + * a {@link ng.$compileProvider#methods_directive directives}), which will force a `$digest()`. + * + * If you want to be notified whenever `$digest()` is called, + * you can register a `watchExpression` function with + * {@link ng.$rootScope.Scope#methods_$watch $watch()} with no `listener`. + * + * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. + * + * # Example + *
      +           var scope = ...;
      +           scope.name = 'misko';
      +           scope.counter = 0;
      +
      +           expect(scope.counter).toEqual(0);
      +           scope.$watch('name', function(newValue, oldValue) {
      +             scope.counter = scope.counter + 1;
      +           });
      +           expect(scope.counter).toEqual(0);
      +
      +           scope.$digest();
      +           // no variable change
      +           expect(scope.counter).toEqual(0);
      +
      +           scope.name = 'adam';
      +           scope.$digest();
      +           expect(scope.counter).toEqual(1);
      +       * 
      + * + */ + $digest: function() { + var watch, value, last, + watchers, + asyncQueue = this.$$asyncQueue, + postDigestQueue = this.$$postDigestQueue, + length, + dirty, ttl = TTL, + next, current, target = this, + watchLog = [], + logIdx, logMsg, asyncTask; + + beginPhase('$digest'); + + lastDirtyWatch = null; + + do { // "while dirty" loop + dirty = false; + current = target; + + while(asyncQueue.length) { + try { + asyncTask = asyncQueue.shift(); + asyncTask.scope.$eval(asyncTask.expression); + } catch (e) { + clearPhase(); + $exceptionHandler(e); + } + lastDirtyWatch = null; + } + + traverseScopesLoop: + do { // "traverse the scopes" loop + if ((watchers = current.$$watchers)) { + // process our watches + length = watchers.length; + while (length--) { + try { + watch = watchers[length]; + // Most common watches are on primitives, in which case we can short + // circuit it with === operator, only when === fails do we use .equals + if (watch) { + if ((value = watch.get(current)) !== (last = watch.last) && + !(watch.eq + ? equals(value, last) + : (typeof value == 'number' && typeof last == 'number' + && isNaN(value) && isNaN(last)))) { + dirty = true; + lastDirtyWatch = watch; + watch.last = watch.eq ? copy(value) : value; + watch.fn(value, ((last === initWatchVal) ? value : last), current); + if (ttl < 5) { + logIdx = 4 - ttl; + if (!watchLog[logIdx]) watchLog[logIdx] = []; + logMsg = (isFunction(watch.exp)) + ? 'fn: ' + (watch.exp.name || watch.exp.toString()) + : watch.exp; + logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); + watchLog[logIdx].push(logMsg); + } + } else if (watch === lastDirtyWatch) { + // If the most recently dirty watcher is now clean, short circuit since the remaining watchers + // have already been tested. + dirty = false; + break traverseScopesLoop; + } + } + } catch (e) { + clearPhase(); + $exceptionHandler(e); + } + } + } + + // Insanity Warning: scope depth-first traversal + // yes, this code is a bit crazy, but it works and we have tests to prove it! + // this piece should be kept in sync with the traversal in $broadcast + if (!(next = (current.$$childHead || + (current !== target && current.$$nextSibling)))) { + while(current !== target && !(next = current.$$nextSibling)) { + current = current.$parent; + } + } + } while ((current = next)); + + // `break traverseScopesLoop;` takes us to here + + if((dirty || asyncQueue.length) && !(ttl--)) { + clearPhase(); + throw $rootScopeMinErr('infdig', + '{0} $digest() iterations reached. Aborting!\n' + + 'Watchers fired in the last 5 iterations: {1}', + TTL, toJson(watchLog)); + } + + } while (dirty || asyncQueue.length); + + clearPhase(); + + while(postDigestQueue.length) { + try { + postDigestQueue.shift()(); + } catch (e) { + $exceptionHandler(e); + } + } + }, + + + /** + * @ngdoc event + * @name ng.$rootScope.Scope#$destroy + * @eventOf ng.$rootScope.Scope + * @eventType broadcast on scope being destroyed + * + * @description + * Broadcasted when a scope and its children are being destroyed. + * + * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to + * clean up DOM bindings before an element is removed from the DOM. + */ + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$destroy + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Removes the current scope (and all of its children) from the parent scope. Removal implies + * that calls to {@link ng.$rootScope.Scope#methods_$digest $digest()} will no longer + * propagate to the current scope and its children. Removal also implies that the current + * scope is eligible for garbage collection. + * + * The `$destroy()` is usually used by directives such as + * {@link ng.directive:ngRepeat ngRepeat} for managing the + * unrolling of the loop. + * + * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. + * Application code can register a `$destroy` event handler that will give it a chance to + * perform any necessary cleanup. + * + * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to + * clean up DOM bindings before an element is removed from the DOM. + */ + $destroy: function() { + // we can't destroy the root scope or a scope that has been already destroyed + if (this.$$destroyed) return; + var parent = this.$parent; + + this.$broadcast('$destroy'); + this.$$destroyed = true; + if (this === $rootScope) return; + + forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); + + if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; + if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; + if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; + if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; + + // This is bogus code that works around Chrome's GC leak + // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 + this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = + this.$$childTail = null; + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$eval + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Executes the `expression` on the current scope and returns the result. Any exceptions in + * the expression are propagated (uncaught). This is useful when evaluating Angular + * expressions. + * + * # Example + *
      +           var scope = ng.$rootScope.Scope();
      +           scope.a = 1;
      +           scope.b = 2;
      +
      +           expect(scope.$eval('a+b')).toEqual(3);
      +           expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
      +       * 
      + * + * @param {(string|function())=} expression An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. + * + * @param {(object)=} locals Local variables object, useful for overriding values in scope. + * @returns {*} The result of evaluating the expression. + */ + $eval: function(expr, locals) { + return $parse(expr)(this, locals); + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$evalAsync + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Executes the expression on the current scope at a later point in time. + * + * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only + * that: + * + * - it will execute after the function that scheduled the evaluation (preferably before DOM + * rendering). + * - at least one {@link ng.$rootScope.Scope#methods_$digest $digest cycle} will be performed after + * `expression` execution. + * + * Any exceptions from the execution of the expression are forwarded to the + * {@link ng.$exceptionHandler $exceptionHandler} service. + * + * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle + * will be scheduled. However, it is encouraged to always call code that changes the model + * from within an `$apply` call. That includes code evaluated via `$evalAsync`. + * + * @param {(string|function())=} expression An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. + * + */ + $evalAsync: function(expr) { + // if we are outside of an $digest loop and this is the first time we are scheduling async + // task also schedule async auto-flush + if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { + $browser.defer(function() { + if ($rootScope.$$asyncQueue.length) { + $rootScope.$digest(); + } + }); + } + + this.$$asyncQueue.push({scope: this, expression: expr}); + }, + + $$postDigest : function(fn) { + this.$$postDigestQueue.push(fn); + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$apply + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * `$apply()` is used to execute an expression in angular from outside of the angular + * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). + * Because we are calling into the angular framework we need to perform proper scope life + * cycle of {@link ng.$exceptionHandler exception handling}, + * {@link ng.$rootScope.Scope#methods_$digest executing watches}. + * + * ## Life cycle + * + * # Pseudo-Code of `$apply()` + *
      +           function $apply(expr) {
      +             try {
      +               return $eval(expr);
      +             } catch (e) {
      +               $exceptionHandler(e);
      +             } finally {
      +               $root.$digest();
      +             }
      +           }
      +       * 
      + * + * + * Scope's `$apply()` method transitions through the following stages: + * + * 1. The {@link guide/expression expression} is executed using the + * {@link ng.$rootScope.Scope#methods_$eval $eval()} method. + * 2. Any exceptions from the execution of the expression are forwarded to the + * {@link ng.$exceptionHandler $exceptionHandler} service. + * 3. The {@link ng.$rootScope.Scope#methods_$watch watch} listeners are fired immediately after the + * expression was executed using the {@link ng.$rootScope.Scope#methods_$digest $digest()} method. + * + * + * @param {(string|function())=} exp An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with current `scope` parameter. + * + * @returns {*} The result of evaluating the expression. + */ + $apply: function(expr) { + try { + beginPhase('$apply'); + return this.$eval(expr); + } catch (e) { + $exceptionHandler(e); + } finally { + clearPhase(); + try { + $rootScope.$digest(); + } catch (e) { + $exceptionHandler(e); + throw e; + } + } + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$on + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Listens on events of a given type. See {@link ng.$rootScope.Scope#methods_$emit $emit} for + * discussion of event life cycle. + * + * The event listener function format is: `function(event, args...)`. The `event` object + * passed into the listener has the following attributes: + * + * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or + * `$broadcast`-ed. + * - `currentScope` - `{Scope}`: the current scope which is handling the event. + * - `name` - `{string}`: name of the event. + * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel + * further event propagation (available only for events that were `$emit`-ed). + * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag + * to true. + * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. + * + * @param {string} name Event name to listen on. + * @param {function(event, args...)} listener Function to call when the event is emitted. + * @returns {function()} Returns a deregistration function for this listener. + */ + $on: function(name, listener) { + var namedListeners = this.$$listeners[name]; + if (!namedListeners) { + this.$$listeners[name] = namedListeners = []; + } + namedListeners.push(listener); + + var current = this; + do { + if (!current.$$listenerCount[name]) { + current.$$listenerCount[name] = 0; + } + current.$$listenerCount[name]++; + } while ((current = current.$parent)); + + var self = this; + return function() { + namedListeners[indexOf(namedListeners, listener)] = null; + decrementListenerCount(self, 1, name); + }; + }, + + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$emit + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Dispatches an event `name` upwards through the scope hierarchy notifying the + * registered {@link ng.$rootScope.Scope#methods_$on} listeners. + * + * The event life cycle starts at the scope on which `$emit` was called. All + * {@link ng.$rootScope.Scope#methods_$on listeners} listening for `name` event on this scope get + * notified. Afterwards, the event traverses upwards toward the root scope and calls all + * registered listeners along the way. The event will stop propagating if one of the listeners + * cancels it. + * + * Any exception emitted from the {@link ng.$rootScope.Scope#methods_$on listeners} will be passed + * onto the {@link ng.$exceptionHandler $exceptionHandler} service. + * + * @param {string} name Event name to emit. + * @param {...*} args Optional set of arguments which will be passed onto the event listeners. + * @return {Object} Event object (see {@link ng.$rootScope.Scope#methods_$on}). + */ + $emit: function(name, args) { + var empty = [], + namedListeners, + scope = this, + stopPropagation = false, + event = { + name: name, + targetScope: scope, + stopPropagation: function() {stopPropagation = true;}, + preventDefault: function() { + event.defaultPrevented = true; + }, + defaultPrevented: false + }, + listenerArgs = concat([event], arguments, 1), + i, length; + + do { + namedListeners = scope.$$listeners[name] || empty; + event.currentScope = scope; + for (i=0, length=namedListeners.length; i= 8 ) { + normalizedVal = urlResolve(uri).href; + if (normalizedVal !== '' && !normalizedVal.match(regex)) { + return 'unsafe:'+normalizedVal; + } + } + return uri; + }; + }; +} + +var $sceMinErr = minErr('$sce'); + +var SCE_CONTEXTS = { + HTML: 'html', + CSS: 'css', + URL: 'url', + // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a + // url. (e.g. ng-include, script src, templateUrl) + RESOURCE_URL: 'resourceUrl', + JS: 'js' +}; + +// Helper functions follow. + +// Copied from: +// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 +// Prereq: s is a string. +function escapeForRegexp(s) { + return s.replace(/([-()\[\]{}+?*.$\^|,:# -1) { + throw $sceMinErr('iwcard', + 'Illegal sequence *** in string matcher. String: {0}', matcher); + } + matcher = escapeForRegexp(matcher). + replace('\\*\\*', '.*'). + replace('\\*', '[^:/.?&;]*'); + return new RegExp('^' + matcher + '$'); + } else if (isRegExp(matcher)) { + // The only other type of matcher allowed is a Regexp. + // Match entire URL / disallow partial matches. + // Flags are reset (i.e. no global, ignoreCase or multiline) + return new RegExp('^' + matcher.source + '$'); + } else { + throw $sceMinErr('imatcher', + 'Matchers may only be "self", string patterns or RegExp objects'); + } +} + + +function adjustMatchers(matchers) { + var adjustedMatchers = []; + if (isDefined(matchers)) { + forEach(matchers, function(matcher) { + adjustedMatchers.push(adjustMatcher(matcher)); + }); + } + return adjustedMatchers; +} + + +/** + * @ngdoc service + * @name ng.$sceDelegate + * @function + * + * @description + * + * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict + * Contextual Escaping (SCE)} services to AngularJS. + * + * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of + * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is + * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to + * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things + * work because `$sce` delegates to `$sceDelegate` for these operations. + * + * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. + * + * The default instance of `$sceDelegate` should work out of the box with little pain. While you + * can override it completely to change the behavior of `$sce`, the common case would + * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting + * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as + * templates. Refer {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist + * $sceDelegateProvider.resourceUrlWhitelist} and {@link + * ng.$sceDelegateProvider#methods_resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} + */ + +/** + * @ngdoc object + * @name ng.$sceDelegateProvider + * @description + * + * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate + * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure + * that the URLs used for sourcing Angular templates are safe. Refer {@link + * ng.$sceDelegateProvider#methods_resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and + * {@link ng.$sceDelegateProvider#methods_resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} + * + * For the general details about this service in Angular, read the main page for {@link ng.$sce + * Strict Contextual Escaping (SCE)}. + * + * **Example**: Consider the following case. + * + * - your app is hosted at url `http://myapp.example.com/` + * - but some of your templates are hosted on other domains you control such as + * `http://srv01.assets.example.com/`,  `http://srv02.assets.example.com/`, etc. + * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. + * + * Here is what a secure configuration for this scenario might look like: + * + *
      + *    angular.module('myApp', []).config(function($sceDelegateProvider) {
      + *      $sceDelegateProvider.resourceUrlWhitelist([
      + *        // Allow same origin resource loads.
      + *        'self',
      + *        // Allow loading from our assets domain.  Notice the difference between * and **.
      + *        'http://srv*.assets.example.com/**']);
      + *
      + *      // The blacklist overrides the whitelist so the open redirect here is blocked.
      + *      $sceDelegateProvider.resourceUrlBlacklist([
      + *        'http://myapp.example.com/clickThru**']);
      + *      });
      + * 
      + */ + +function $SceDelegateProvider() { + this.SCE_CONTEXTS = SCE_CONTEXTS; + + // Resource URLs can also be trusted by policy. + var resourceUrlWhitelist = ['self'], + resourceUrlBlacklist = []; + + /** + * @ngdoc function + * @name ng.sceDelegateProvider#resourceUrlWhitelist + * @methodOf ng.$sceDelegateProvider + * @function + * + * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value + * provided. This must be an array or null. A snapshot of this array is used so further + * changes to the array are ignored. + * + * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array. + * + * Note: **an empty whitelist array will block all URLs**! + * + * @return {Array} the currently set whitelist array. + * + * The **default value** when no whitelist has been explicitly set is `['self']` allowing only + * same origin resource requests. + * + * @description + * Sets/Gets the whitelist of trusted resource URLs. + */ + this.resourceUrlWhitelist = function (value) { + if (arguments.length) { + resourceUrlWhitelist = adjustMatchers(value); + } + return resourceUrlWhitelist; + }; + + /** + * @ngdoc function + * @name ng.sceDelegateProvider#resourceUrlBlacklist + * @methodOf ng.$sceDelegateProvider + * @function + * + * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value + * provided. This must be an array or null. A snapshot of this array is used so further + * changes to the array are ignored. + * + * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array. + * + * The typical usage for the blacklist is to **block + * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as + * these would otherwise be trusted but actually return content from the redirected domain. + * + * Finally, **the blacklist overrides the whitelist** and has the final say. + * + * @return {Array} the currently set blacklist array. + * + * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there + * is no blacklist.) + * + * @description + * Sets/Gets the blacklist of trusted resource URLs. + */ + + this.resourceUrlBlacklist = function (value) { + if (arguments.length) { + resourceUrlBlacklist = adjustMatchers(value); + } + return resourceUrlBlacklist; + }; + + this.$get = ['$injector', function($injector) { + + var htmlSanitizer = function htmlSanitizer(html) { + throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); + }; + + if ($injector.has('$sanitize')) { + htmlSanitizer = $injector.get('$sanitize'); + } + + + function matchUrl(matcher, parsedUrl) { + if (matcher === 'self') { + return urlIsSameOrigin(parsedUrl); + } else { + // definitely a regex. See adjustMatchers() + return !!matcher.exec(parsedUrl.href); + } + } + + function isResourceUrlAllowedByPolicy(url) { + var parsedUrl = urlResolve(url.toString()); + var i, n, allowed = false; + // Ensure that at least one item from the whitelist allows this url. + for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { + if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { + allowed = true; + break; + } + } + if (allowed) { + // Ensure that no item from the blacklist blocked this url. + for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { + if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { + allowed = false; + break; + } + } + } + return allowed; + } + + function generateHolderType(Base) { + var holderType = function TrustedValueHolderType(trustedValue) { + this.$$unwrapTrustedValue = function() { + return trustedValue; + }; + }; + if (Base) { + holderType.prototype = new Base(); + } + holderType.prototype.valueOf = function sceValueOf() { + return this.$$unwrapTrustedValue(); + }; + holderType.prototype.toString = function sceToString() { + return this.$$unwrapTrustedValue().toString(); + }; + return holderType; + } + + var trustedValueHolderBase = generateHolderType(), + byType = {}; + + byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); + + /** + * @ngdoc method + * @name ng.$sceDelegate#trustAs + * @methodOf ng.$sceDelegate + * + * @description + * Returns an object that is trusted by angular for use in specified strict + * contextual escaping contexts (such as ng-html-bind-unsafe, ng-include, any src + * attribute interpolation, any dom event binding attribute interpolation + * such as for onclick, etc.) that uses the provided value. + * See {@link ng.$sce $sce} for enabling strict contextual escaping. + * + * @param {string} type The kind of context in which this value is safe for use. e.g. url, + * resourceUrl, html, js and css. + * @param {*} value The value that that should be considered trusted/safe. + * @returns {*} A value that can be used to stand in for the provided `value` in places + * where Angular expects a $sce.trustAs() return value. + */ + function trustAs(type, trustedValue) { + var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); + if (!Constructor) { + throw $sceMinErr('icontext', + 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', + type, trustedValue); + } + if (trustedValue === null || trustedValue === undefined || trustedValue === '') { + return trustedValue; + } + // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting + // mutable objects, we ensure here that the value passed in is actually a string. + if (typeof trustedValue !== 'string') { + throw $sceMinErr('itype', + 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', + type); + } + return new Constructor(trustedValue); + } + + /** + * @ngdoc method + * @name ng.$sceDelegate#valueOf + * @methodOf ng.$sceDelegate + * + * @description + * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#methods_trustAs + * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link + * ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}. + * + * If the passed parameter is not a value that had been returned by {@link + * ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}, returns it as-is. + * + * @param {*} value The result of a prior {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`} + * call or anything else. + * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#methods_trustAs + * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns + * `value` unchanged. + */ + function valueOf(maybeTrusted) { + if (maybeTrusted instanceof trustedValueHolderBase) { + return maybeTrusted.$$unwrapTrustedValue(); + } else { + return maybeTrusted; + } + } + + /** + * @ngdoc method + * @name ng.$sceDelegate#getTrusted + * @methodOf ng.$sceDelegate + * + * @description + * Takes the result of a {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`} call and + * returns the originally supplied value if the queried context type is a supertype of the + * created type. If this condition isn't satisfied, throws an exception. + * + * @param {string} type The kind of context in which this value is to be used. + * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#methods_trustAs + * `$sceDelegate.trustAs`} call. + * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#methods_trustAs + * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. + */ + function getTrusted(type, maybeTrusted) { + if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { + return maybeTrusted; + } + var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); + if (constructor && maybeTrusted instanceof constructor) { + return maybeTrusted.$$unwrapTrustedValue(); + } + // If we get here, then we may only take one of two actions. + // 1. sanitize the value for the requested type, or + // 2. throw an exception. + if (type === SCE_CONTEXTS.RESOURCE_URL) { + if (isResourceUrlAllowedByPolicy(maybeTrusted)) { + return maybeTrusted; + } else { + throw $sceMinErr('insecurl', + 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', + maybeTrusted.toString()); + } + } else if (type === SCE_CONTEXTS.HTML) { + return htmlSanitizer(maybeTrusted); + } + throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); + } + + return { trustAs: trustAs, + getTrusted: getTrusted, + valueOf: valueOf }; + }]; +} + + +/** + * @ngdoc object + * @name ng.$sceProvider + * @description + * + * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. + * - enable/disable Strict Contextual Escaping (SCE) in a module + * - override the default implementation with a custom delegate + * + * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. + */ + +/* jshint maxlen: false*/ + +/** + * @ngdoc service + * @name ng.$sce + * @function + * + * @description + * + * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. + * + * # Strict Contextual Escaping + * + * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain + * contexts to result in a value that is marked as safe to use for that context. One example of + * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer + * to these contexts as privileged or SCE contexts. + * + * As of version 1.2, Angular ships with SCE enabled by default. + * + * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows + * one to execute arbitrary javascript by the use of the expression() syntax. Refer + * to learn more about them. + * You can ensure your document is in standards mode and not quirks mode by adding `` + * to the top of your HTML document. + * + * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for + * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. + * + * Here's an example of a binding in a privileged context: + * + *
      + *     
      + *     
      + *
      + * + * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE + * disabled, this application allows the user to render arbitrary HTML into the DIV. + * In a more realistic example, one may be rendering user comments, blog articles, etc. via + * bindings. (HTML is just one example of a context where rendering user controlled input creates + * security vulnerabilities.) + * + * For the case of HTML, you might use a library, either on the client side, or on the server side, + * to sanitize unsafe HTML before binding to the value and rendering it in the document. + * + * How would you ensure that every place that used these types of bindings was bound to a value that + * was sanitized by your library (or returned as safe for rendering by your server?) How can you + * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some + * properties/fields and forgot to update the binding to the sanitized value? + * + * To be secure by default, you want to ensure that any such bindings are disallowed unless you can + * determine that something explicitly says it's safe to use a value for binding in that + * context. You can then audit your code (a simple grep would do) to ensure that this is only done + * for those values that you can easily tell are safe - because they were received from your server, + * sanitized by your library, etc. You can organize your codebase to help with this - perhaps + * allowing only the files in a specific directory to do this. Ensuring that the internal API + * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. + * + * In the case of AngularJS' SCE service, one uses {@link ng.$sce#methods_trustAs $sce.trustAs} + * (and shorthand methods such as {@link ng.$sce#methods_trustAsHtml $sce.trustAsHtml}, etc.) to + * obtain values that will be accepted by SCE / privileged contexts. + * + * + * ## How does it work? + * + * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#methods_getTrusted + * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link + * ng.$sce#methods_parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the + * {@link ng.$sce#methods_getTrusted $sce.getTrusted} behind the scenes on non-constant literals. + * + * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link + * ng.$sce#methods_parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly + * simplified): + * + *
      + *   var ngBindHtmlDirective = ['$sce', function($sce) {
      + *     return function(scope, element, attr) {
      + *       scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
      + *         element.html(value || '');
      + *       });
      + *     };
      + *   }];
      + * 
      + * + * ## Impact on loading templates + * + * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as + * `templateUrl`'s specified by {@link guide/directive directives}. + * + * By default, Angular only loads templates from the same domain and protocol as the application + * document. This is done by calling {@link ng.$sce#methods_getTrustedResourceUrl + * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or + * protocols, you may either either {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelist + * them} or {@link ng.$sce#methods_trustAsResourceUrl wrap it} into a trusted value. + * + * *Please note*: + * The browser's + * {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest + * Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing (CORS)} + * policy apply in addition to this and may further restrict whether the template is successfully + * loaded. This means that without the right CORS policy, loading templates from a different domain + * won't work on all browsers. Also, loading templates from `file://` URL does not work on some + * browsers. + * + * ## This feels like too much overhead for the developer? + * + * It's important to remember that SCE only applies to interpolation expressions. + * + * If your expressions are constant literals, they're automatically trusted and you don't need to + * call `$sce.trustAs` on them. (e.g. + * `
      `) just works. + * + * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them + * through {@link ng.$sce#methods_getTrusted $sce.getTrusted}. SCE doesn't play a role here. + * + * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load + * templates in `ng-include` from your application's domain without having to even know about SCE. + * It blocks loading templates from other domains or loading templates over http from an https + * served document. You can change these by setting your own custom {@link + * ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelists} and {@link + * ng.$sceDelegateProvider#methods_resourceUrlBlacklist blacklists} for matching such URLs. + * + * This significantly reduces the overhead. It is far easier to pay the small overhead and have an + * application that's secure and can be audited to verify that with much more ease than bolting + * security onto an application later. + * + * + * ## What trusted context types are supported? + * + * | Context | Notes | + * |---------------------|----------------| + * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. | + * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | + * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`
      Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | + * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | + * + * ## Format of items in {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#methods_resourceUrlBlacklist Blacklist}
      + * + * Each element in these arrays must be one of the following: + * + * - **'self'** + * - The special **string**, `'self'`, can be used to match against all URLs of the **same + * domain** as the application document using the **same protocol**. + * - **String** (except the special value `'self'`) + * - The string is matched against the full *normalized / absolute URL* of the resource + * being tested (substring matches are not good enough.) + * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters + * match themselves. + * - `*`: matches zero or more occurances of any character other than one of the following 6 + * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use + * in a whitelist. + * - `**`: matches zero or more occurances of *any* character. As such, it's not + * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. + * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might + * not have been the intention.) It's usage at the very end of the path is ok. (e.g. + * http://foo.example.com/templates/**). + * - **RegExp** (*see caveat below*) + * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax + * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to + * accidentally introduce a bug when one updates a complex expression (imho, all regexes should + * have good test coverage.). For instance, the use of `.` in the regex is correct only in a + * small number of cases. A `.` character in the regex used when matching the scheme or a + * subdomain could be matched against a `:` or literal `.` that was likely not intended. It + * is highly recommended to use the string patterns and only fall back to regular expressions + * if they as a last resort. + * - The regular expression must be an instance of RegExp (i.e. not a string.) It is + * matched against the **entire** *normalized / absolute URL* of the resource being tested + * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags + * present on the RegExp (such as multiline, global, ignoreCase) are ignored. + * - If you are generating your Javascript from some other templating engine (not + * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), + * remember to escape your regular expression (and be aware that you might need more than + * one level of escaping depending on your templating engine and the way you interpolated + * the value.) Do make use of your platform's escaping mechanism as it might be good + * enough before coding your own. e.g. Ruby has + * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) + * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). + * Javascript lacks a similar built in function for escaping. Take a look at Google + * Closure library's [goog.string.regExpEscape(s)]( + * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). + * + * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. + * + * ## Show me an example using SCE. + * + * @example + + +
      +

      + User comments
      + By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when + $sanitize is available. If $sanitize isn't available, this results in an error instead of an + exploit. +
      +
      + {{userComment.name}}: + +
      +
      +
      +
      +
      + + + var mySceApp = angular.module('mySceApp', ['ngSanitize']); + + mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { + var self = this; + $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { + self.userComments = userComments; + }); + self.explicitlyTrustedHtml = $sce.trustAsHtml( + 'Hover over this text.'); + }); + + + +[ + { "name": "Alice", + "htmlComment": + "Is anyone reading this?" + }, + { "name": "Bob", + "htmlComment": "Yes! Am I the only other one?" + } +] + + + + describe('SCE doc demo', function() { + it('should sanitize untrusted values', function() { + expect(element('.htmlComment').html()).toBe('Is anyone reading this?'); + }); + it('should NOT sanitize explicitly trusted values', function() { + expect(element('#explicitlyTrustedHtml').html()).toBe( + 'Hover over this text.'); + }); + }); + +
      + * + * + * + * ## Can I disable SCE completely? + * + * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits + * for little coding overhead. It will be much harder to take an SCE disabled application and + * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE + * for cases where you have a lot of existing code that was written before SCE was introduced and + * you're migrating them a module at a time. + * + * That said, here's how you can completely disable SCE: + * + *
      + *   angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
      + *     // Completely disable SCE.  For demonstration purposes only!
      + *     // Do not use in new projects.
      + *     $sceProvider.enabled(false);
      + *   });
      + * 
      + * + */ +/* jshint maxlen: 100 */ + +function $SceProvider() { + var enabled = true; + + /** + * @ngdoc function + * @name ng.sceProvider#enabled + * @methodOf ng.$sceProvider + * @function + * + * @param {boolean=} value If provided, then enables/disables SCE. + * @return {boolean} true if SCE is enabled, false otherwise. + * + * @description + * Enables/disables SCE and returns the current value. + */ + this.enabled = function (value) { + if (arguments.length) { + enabled = !!value; + } + return enabled; + }; + + + /* Design notes on the default implementation for SCE. + * + * The API contract for the SCE delegate + * ------------------------------------- + * The SCE delegate object must provide the following 3 methods: + * + * - trustAs(contextEnum, value) + * This method is used to tell the SCE service that the provided value is OK to use in the + * contexts specified by contextEnum. It must return an object that will be accepted by + * getTrusted() for a compatible contextEnum and return this value. + * + * - valueOf(value) + * For values that were not produced by trustAs(), return them as is. For values that were + * produced by trustAs(), return the corresponding input value to trustAs. Basically, if + * trustAs is wrapping the given values into some type, this operation unwraps it when given + * such a value. + * + * - getTrusted(contextEnum, value) + * This function should return the a value that is safe to use in the context specified by + * contextEnum or throw and exception otherwise. + * + * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be + * opaque or wrapped in some holder object. That happens to be an implementation detail. For + * instance, an implementation could maintain a registry of all trusted objects by context. In + * such a case, trustAs() would return the same object that was passed in. getTrusted() would + * return the same object passed in if it was found in the registry under a compatible context or + * throw an exception otherwise. An implementation might only wrap values some of the time based + * on some criteria. getTrusted() might return a value and not throw an exception for special + * constants or objects even if not wrapped. All such implementations fulfill this contract. + * + * + * A note on the inheritance model for SCE contexts + * ------------------------------------------------ + * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This + * is purely an implementation details. + * + * The contract is simply this: + * + * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) + * will also succeed. + * + * Inheritance happens to capture this in a natural way. In some future, we + * may not use inheritance anymore. That is OK because no code outside of + * sce.js and sceSpecs.js would need to be aware of this detail. + */ + + this.$get = ['$parse', '$sniffer', '$sceDelegate', function( + $parse, $sniffer, $sceDelegate) { + // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows + // the "expression(javascript expression)" syntax which is insecure. + if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { + throw $sceMinErr('iequirks', + 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + + 'mode. You can fix this by adding the text to the top of your HTML ' + + 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); + } + + var sce = copy(SCE_CONTEXTS); + + /** + * @ngdoc function + * @name ng.sce#isEnabled + * @methodOf ng.$sce + * @function + * + * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you + * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. + * + * @description + * Returns a boolean indicating if SCE is enabled. + */ + sce.isEnabled = function () { + return enabled; + }; + sce.trustAs = $sceDelegate.trustAs; + sce.getTrusted = $sceDelegate.getTrusted; + sce.valueOf = $sceDelegate.valueOf; + + if (!enabled) { + sce.trustAs = sce.getTrusted = function(type, value) { return value; }; + sce.valueOf = identity; + } + + /** + * @ngdoc method + * @name ng.$sce#parse + * @methodOf ng.$sce + * + * @description + * Converts Angular {@link guide/expression expression} into a function. This is like {@link + * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it + * wraps the expression in a call to {@link ng.$sce#methods_getTrusted $sce.getTrusted(*type*, + * *result*)} + * + * @param {string} type The kind of SCE context in which this result will be used. + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + sce.parseAs = function sceParseAs(type, expr) { + var parsed = $parse(expr); + if (parsed.literal && parsed.constant) { + return parsed; + } else { + return function sceParseAsTrusted(self, locals) { + return sce.getTrusted(type, parsed(self, locals)); + }; + } + }; + + /** + * @ngdoc method + * @name ng.$sce#trustAs + * @methodOf ng.$sce + * + * @description + * Delegates to {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}. As such, + * returns an objectthat is trusted by angular for use in specified strict contextual + * escaping contexts (such as ng-html-bind-unsafe, ng-include, any src attribute + * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) + * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual + * escaping. + * + * @param {string} type The kind of context in which this value is safe for use. e.g. url, + * resource_url, html, js and css. + * @param {*} value The value that that should be considered trusted/safe. + * @returns {*} A value that can be used to stand in for the provided `value` in places + * where Angular expects a $sce.trustAs() return value. + */ + + /** + * @ngdoc method + * @name ng.$sce#trustAsHtml + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.trustAsHtml(value)` → + * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.HTML, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedHtml + * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name ng.$sce#trustAsUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.trustAsUrl(value)` → + * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.URL, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedUrl + * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name ng.$sce#trustAsResourceUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.trustAsResourceUrl(value)` → + * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedResourceUrl + * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the return + * value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name ng.$sce#trustAsJs + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.trustAsJs(value)` → + * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.JS, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedJs + * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrusted + * @methodOf ng.$sce + * + * @description + * Delegates to {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted`}. As such, + * takes the result of a {@link ng.$sce#methods_trustAs `$sce.trustAs`}() call and returns the + * originally supplied value if the queried context type is a supertype of the created type. + * If this condition isn't satisfied, throws an exception. + * + * @param {string} type The kind of context in which this value is to be used. + * @param {*} maybeTrusted The result of a prior {@link ng.$sce#methods_trustAs `$sce.trustAs`} + * call. + * @returns {*} The value the was originally provided to + * {@link ng.$sce#methods_trustAs `$sce.trustAs`} if valid in this context. + * Otherwise, throws an exception. + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedHtml + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedHtml(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedCss + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedCss(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedUrl(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedResourceUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedResourceUrl(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} + * + * @param {*} value The value to pass to `$sceDelegate.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedJs + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedJs(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsHtml + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsHtml(expression string)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.HTML, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsCss + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsCss(value)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.CSS, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsUrl(value)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.URL, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsResourceUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsResourceUrl(value)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.RESOURCE_URL, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsJs + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsJs(value)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.JS, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + // Shorthand delegations. + var parse = sce.parseAs, + getTrusted = sce.getTrusted, + trustAs = sce.trustAs; + + forEach(SCE_CONTEXTS, function (enumValue, name) { + var lName = lowercase(name); + sce[camelCase("parse_as_" + lName)] = function (expr) { + return parse(enumValue, expr); + }; + sce[camelCase("get_trusted_" + lName)] = function (value) { + return getTrusted(enumValue, value); + }; + sce[camelCase("trust_as_" + lName)] = function (value) { + return trustAs(enumValue, value); + }; + }); + + return sce; + }]; +} + +/** + * !!! This is an undocumented "private" service !!! + * + * @name ng.$sniffer + * @requires $window + * @requires $document + * + * @property {boolean} history Does the browser support html5 history api ? + * @property {boolean} hashchange Does the browser support hashchange event ? + * @property {boolean} transitions Does the browser support CSS transition events ? + * @property {boolean} animations Does the browser support CSS animation events ? + * + * @description + * This is very simple implementation of testing browser's features. + */ +function $SnifferProvider() { + this.$get = ['$window', '$document', function($window, $document) { + var eventSupport = {}, + android = + int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), + boxee = /Boxee/i.test(($window.navigator || {}).userAgent), + document = $document[0] || {}, + documentMode = document.documentMode, + vendorPrefix, + vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, + bodyStyle = document.body && document.body.style, + transitions = false, + animations = false, + match; + + if (bodyStyle) { + for(var prop in bodyStyle) { + if(match = vendorRegex.exec(prop)) { + vendorPrefix = match[0]; + vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); + break; + } + } + + if(!vendorPrefix) { + vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; + } + + transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); + animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); + + if (android && (!transitions||!animations)) { + transitions = isString(document.body.style.webkitTransition); + animations = isString(document.body.style.webkitAnimation); + } + } + + + return { + // Android has history.pushState, but it does not update location correctly + // so let's not use the history API at all. + // http://code.google.com/p/android/issues/detail?id=17471 + // https://github.com/angular/angular.js/issues/904 + + // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has + // so let's not use the history API also + // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined + // jshint -W018 + history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), + // jshint +W018 + hashchange: 'onhashchange' in $window && + // IE8 compatible mode lies + (!documentMode || documentMode > 7), + hasEvent: function(event) { + // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have + // it. In particular the event is not fired when backspace or delete key are pressed or + // when cut operation is performed. + if (event == 'input' && msie == 9) return false; + + if (isUndefined(eventSupport[event])) { + var divElm = document.createElement('div'); + eventSupport[event] = 'on' + event in divElm; + } + + return eventSupport[event]; + }, + csp: csp(), + vendorPrefix: vendorPrefix, + transitions : transitions, + animations : animations, + android: android, + msie : msie, + msieDocumentMode: documentMode + }; + }]; +} + +function $TimeoutProvider() { + this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', + function($rootScope, $browser, $q, $exceptionHandler) { + var deferreds = {}; + + + /** + * @ngdoc function + * @name ng.$timeout + * @requires $browser + * + * @description + * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch + * block and delegates any exceptions to + * {@link ng.$exceptionHandler $exceptionHandler} service. + * + * The return value of registering a timeout function is a promise, which will be resolved when + * the timeout is reached and the timeout function is executed. + * + * To cancel a timeout request, call `$timeout.cancel(promise)`. + * + * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to + * synchronously flush the queue of deferred functions. + * + * @param {function()} fn A function, whose execution should be delayed. + * @param {number=} [delay=0] Delay in milliseconds. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block. + * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this + * promise will be resolved with is the return value of the `fn` function. + * + */ + function timeout(fn, delay, invokeApply) { + var deferred = $q.defer(), + promise = deferred.promise, + skipApply = (isDefined(invokeApply) && !invokeApply), + timeoutId; + + timeoutId = $browser.defer(function() { + try { + deferred.resolve(fn()); + } catch(e) { + deferred.reject(e); + $exceptionHandler(e); + } + finally { + delete deferreds[promise.$$timeoutId]; + } + + if (!skipApply) $rootScope.$apply(); + }, delay); + + promise.$$timeoutId = timeoutId; + deferreds[timeoutId] = deferred; + + return promise; + } + + + /** + * @ngdoc function + * @name ng.$timeout#cancel + * @methodOf ng.$timeout + * + * @description + * Cancels a task associated with the `promise`. As a result of this, the promise will be + * resolved with a rejection. + * + * @param {Promise=} promise Promise returned by the `$timeout` function. + * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully + * canceled. + */ + timeout.cancel = function(promise) { + if (promise && promise.$$timeoutId in deferreds) { + deferreds[promise.$$timeoutId].reject('canceled'); + delete deferreds[promise.$$timeoutId]; + return $browser.defer.cancel(promise.$$timeoutId); + } + return false; + }; + + return timeout; + }]; +} + +// NOTE: The usage of window and document instead of $window and $document here is +// deliberate. This service depends on the specific behavior of anchor nodes created by the +// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and +// cause us to break tests. In addition, when the browser resolves a URL for XHR, it +// doesn't know about mocked locations and resolves URLs to the real document - which is +// exactly the behavior needed here. There is little value is mocking these out for this +// service. +var urlParsingNode = document.createElement("a"); +var originUrl = urlResolve(window.location.href, true); + + +/** + * + * Implementation Notes for non-IE browsers + * ---------------------------------------- + * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, + * results both in the normalizing and parsing of the URL. Normalizing means that a relative + * URL will be resolved into an absolute URL in the context of the application document. + * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related + * properties are all populated to reflect the normalized URL. This approach has wide + * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See + * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html + * + * Implementation Notes for IE + * --------------------------- + * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other + * browsers. However, the parsed components will not be set if the URL assigned did not specify + * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We + * work around that by performing the parsing in a 2nd step by taking a previously normalized + * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the + * properties such as protocol, hostname, port, etc. + * + * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one + * uses the inner HTML approach to assign the URL as part of an HTML snippet - + * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. + * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. + * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that + * method and IE < 8 is unsupported. + * + * References: + * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement + * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html + * http://url.spec.whatwg.org/#urlutils + * https://github.com/angular/angular.js/pull/2902 + * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ + * + * @function + * @param {string} url The URL to be parsed. + * @description Normalizes and parses a URL. + * @returns {object} Returns the normalized URL as a dictionary. + * + * | member name | Description | + * |---------------|----------------| + * | href | A normalized version of the provided URL if it was not an absolute URL | + * | protocol | The protocol including the trailing colon | + * | host | The host and port (if the port is non-default) of the normalizedUrl | + * | search | The search params, minus the question mark | + * | hash | The hash string, minus the hash symbol + * | hostname | The hostname + * | port | The port, without ":" + * | pathname | The pathname, beginning with "/" + * + */ +function urlResolve(url, base) { + var href = url; + + if (msie) { + // Normalize before parse. Refer Implementation Notes on why this is + // done in two steps on IE. + urlParsingNode.setAttribute("href", href); + href = urlParsingNode.href; + } + + urlParsingNode.setAttribute('href', href); + + // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils + return { + href: urlParsingNode.href, + protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', + host: urlParsingNode.host, + search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', + hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', + hostname: urlParsingNode.hostname, + port: urlParsingNode.port, + pathname: (urlParsingNode.pathname.charAt(0) === '/') + ? urlParsingNode.pathname + : '/' + urlParsingNode.pathname + }; +} + +/** + * Parse a request URL and determine whether this is a same-origin request as the application document. + * + * @param {string|object} requestUrl The url of the request as a string that will be resolved + * or a parsed URL object. + * @returns {boolean} Whether the request is for the same origin as the application document. + */ +function urlIsSameOrigin(requestUrl) { + var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; + return (parsed.protocol === originUrl.protocol && + parsed.host === originUrl.host); +} + +/** + * @ngdoc object + * @name ng.$window + * + * @description + * A reference to the browser's `window` object. While `window` + * is globally available in JavaScript, it causes testability problems, because + * it is a global variable. In angular we always refer to it through the + * `$window` service, so it may be overridden, removed or mocked for testing. + * + * Expressions, like the one defined for the `ngClick` directive in the example + * below, are evaluated with respect to the current scope. Therefore, there is + * no risk of inadvertently coding in a dependency on a global value in such an + * expression. + * + * @example + + + +
      + + +
      +
      + + it('should display the greeting in the input box', function() { + input('greeting').enter('Hello, E2E Tests'); + // If we click the button it will block the test runner + // element(':button').click(); + }); + +
      + */ +function $WindowProvider(){ + this.$get = valueFn(window); +} + +/** + * @ngdoc object + * @name ng.$filterProvider + * @description + * + * Filters are just functions which transform input to an output. However filters need to be + * Dependency Injected. To achieve this a filter definition consists of a factory function which is + * annotated with dependencies and is responsible for creating a filter function. + * + *
      + *   // Filter registration
      + *   function MyModule($provide, $filterProvider) {
      + *     // create a service to demonstrate injection (not always needed)
      + *     $provide.value('greet', function(name){
      + *       return 'Hello ' + name + '!';
      + *     });
      + *
      + *     // register a filter factory which uses the
      + *     // greet service to demonstrate DI.
      + *     $filterProvider.register('greet', function(greet){
      + *       // return the filter function which uses the greet service
      + *       // to generate salutation
      + *       return function(text) {
      + *         // filters need to be forgiving so check input validity
      + *         return text && greet(text) || text;
      + *       };
      + *     });
      + *   }
      + * 
      + * + * The filter function is registered with the `$injector` under the filter name suffix with + * `Filter`. + * + *
      + *   it('should be the same instance', inject(
      + *     function($filterProvider) {
      + *       $filterProvider.register('reverse', function(){
      + *         return ...;
      + *       });
      + *     },
      + *     function($filter, reverseFilter) {
      + *       expect($filter('reverse')).toBe(reverseFilter);
      + *     });
      + * 
      + * + * + * For more information about how angular filters work, and how to create your own filters, see + * {@link guide/filter Filters} in the Angular Developer Guide. + */ +/** + * @ngdoc method + * @name ng.$filterProvider#register + * @methodOf ng.$filterProvider + * @description + * Register filter factory function. + * + * @param {String} name Name of the filter. + * @param {function} fn The filter factory function which is injectable. + */ + + +/** + * @ngdoc function + * @name ng.$filter + * @function + * @description + * Filters are used for formatting data displayed to the user. + * + * The general syntax in templates is as follows: + * + * {{ expression [| filter_name[:parameter_value] ... ] }} + * + * @param {String} name Name of the filter function to retrieve + * @return {Function} the filter function + */ +$FilterProvider.$inject = ['$provide']; +function $FilterProvider($provide) { + var suffix = 'Filter'; + + /** + * @ngdoc function + * @name ng.$controllerProvider#register + * @methodOf ng.$controllerProvider + * @param {string|Object} name Name of the filter function, or an object map of filters where + * the keys are the filter names and the values are the filter factories. + * @returns {Object} Registered filter instance, or if a map of filters was provided then a map + * of the registered filter instances. + */ + function register(name, factory) { + if(isObject(name)) { + var filters = {}; + forEach(name, function(filter, key) { + filters[key] = register(key, filter); + }); + return filters; + } else { + return $provide.factory(name + suffix, factory); + } + } + this.register = register; + + this.$get = ['$injector', function($injector) { + return function(name) { + return $injector.get(name + suffix); + }; + }]; + + //////////////////////////////////////// + + /* global + currencyFilter: false, + dateFilter: false, + filterFilter: false, + jsonFilter: false, + limitToFilter: false, + lowercaseFilter: false, + numberFilter: false, + orderByFilter: false, + uppercaseFilter: false, + */ + + register('currency', currencyFilter); + register('date', dateFilter); + register('filter', filterFilter); + register('json', jsonFilter); + register('limitTo', limitToFilter); + register('lowercase', lowercaseFilter); + register('number', numberFilter); + register('orderBy', orderByFilter); + register('uppercase', uppercaseFilter); +} + +/** + * @ngdoc filter + * @name ng.filter:filter + * @function + * + * @description + * Selects a subset of items from `array` and returns it as a new array. + * + * @param {Array} array The source array. + * @param {string|Object|function()} expression The predicate to be used for selecting items from + * `array`. + * + * Can be one of: + * + * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against + * the contents of the `array`. All strings or objects with string properties in `array` that contain this string + * will be returned. The predicate can be negated by prefixing the string with `!`. + * + * - `Object`: A pattern object can be used to filter specific properties on objects contained + * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items + * which have property `name` containing "M" and property `phone` containing "1". A special + * property name `$` can be used (as in `{$:"text"}`) to accept a match against any + * property of the object. That's equivalent to the simple substring match with a `string` + * as described above. + * + * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is + * called for each element of `array`. The final result is an array of those elements that + * the predicate returned true for. + * + * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in + * determining if the expected value (from the filter expression) and actual value (from + * the object in the array) should be considered a match. + * + * Can be one of: + * + * - `function(actual, expected)`: + * The function will be given the object value and the predicate value to compare and + * should return true if the item should be included in filtered result. + * + * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. + * this is essentially strict comparison of expected and actual. + * + * - `false|undefined`: A short hand for a function which will look for a substring match in case + * insensitive way. + * + * @example + + +
      + + Search: + + + + + + +
      NamePhone
      {{friend.name}}{{friend.phone}}
      +
      + Any:
      + Name only
      + Phone only
      + Equality
      + + + + + + +
      NamePhone
      {{friend.name}}{{friend.phone}}
      +
      + + it('should search across all fields when filtering with a string', function() { + input('searchText').enter('m'); + expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')). + toEqual(['Mary', 'Mike', 'Adam']); + + input('searchText').enter('76'); + expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')). + toEqual(['John', 'Julie']); + }); + + it('should search in specific fields when filtering with a predicate object', function() { + input('search.$').enter('i'); + expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')). + toEqual(['Mary', 'Mike', 'Julie', 'Juliette']); + }); + it('should use a equal comparison when comparator is true', function() { + input('search.name').enter('Julie'); + input('strict').check(); + expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')). + toEqual(['Julie']); + }); + +
      + */ +function filterFilter() { + return function(array, expression, comparator) { + if (!isArray(array)) return array; + + var comparatorType = typeof(comparator), + predicates = []; + + predicates.check = function(value) { + for (var j = 0; j < predicates.length; j++) { + if(!predicates[j](value)) { + return false; + } + } + return true; + }; + + if (comparatorType !== 'function') { + if (comparatorType === 'boolean' && comparator) { + comparator = function(obj, text) { + return angular.equals(obj, text); + }; + } else { + comparator = function(obj, text) { + text = (''+text).toLowerCase(); + return (''+obj).toLowerCase().indexOf(text) > -1; + }; + } + } + + var search = function(obj, text){ + if (typeof text == 'string' && text.charAt(0) === '!') { + return !search(obj, text.substr(1)); + } + switch (typeof obj) { + case "boolean": + case "number": + case "string": + return comparator(obj, text); + case "object": + switch (typeof text) { + case "object": + return comparator(obj, text); + default: + for ( var objKey in obj) { + if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { + return true; + } + } + break; + } + return false; + case "array": + for ( var i = 0; i < obj.length; i++) { + if (search(obj[i], text)) { + return true; + } + } + return false; + default: + return false; + } + }; + switch (typeof expression) { + case "boolean": + case "number": + case "string": + // Set up expression object and fall through + expression = {$:expression}; + // jshint -W086 + case "object": + // jshint +W086 + for (var key in expression) { + (function(path) { + if (typeof expression[path] == 'undefined') return; + predicates.push(function(value) { + return search(path == '$' ? value : getter(value, path), expression[path]); + }); + })(key); + } + break; + case 'function': + predicates.push(expression); + break; + default: + return array; + } + var filtered = []; + for ( var j = 0; j < array.length; j++) { + var value = array[j]; + if (predicates.check(value)) { + filtered.push(value); + } + } + return filtered; + }; +} + +/** + * @ngdoc filter + * @name ng.filter:currency + * @function + * + * @description + * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default + * symbol for current locale is used. + * + * @param {number} amount Input to filter. + * @param {string=} symbol Currency symbol or identifier to be displayed. + * @returns {string} Formatted number. + * + * + * @example + + + +
      +
      + default currency symbol ($): {{amount | currency}}
      + custom currency identifier (USD$): {{amount | currency:"USD$"}} +
      +
      + + it('should init with 1234.56', function() { + expect(binding('amount | currency')).toBe('$1,234.56'); + expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56'); + }); + it('should update', function() { + input('amount').enter('-1234'); + expect(binding('amount | currency')).toBe('($1,234.00)'); + expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)'); + }); + +
      + */ +currencyFilter.$inject = ['$locale']; +function currencyFilter($locale) { + var formats = $locale.NUMBER_FORMATS; + return function(amount, currencySymbol){ + if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; + return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). + replace(/\u00A4/g, currencySymbol); + }; +} + +/** + * @ngdoc filter + * @name ng.filter:number + * @function + * + * @description + * Formats a number as text. + * + * If the input is not a number an empty string is returned. + * + * @param {number|string} number Number to format. + * @param {(number|string)=} fractionSize Number of decimal places to round the number to. + * If this is not provided then the fraction size is computed from the current locale's number + * formatting pattern. In the case of the default locale, it will be 3. + * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit. + * + * @example + + + +
      + Enter number:
      + Default formatting: {{val | number}}
      + No fractions: {{val | number:0}}
      + Negative number: {{-val | number:4}} +
      +
      + + it('should format numbers', function() { + expect(binding('val | number')).toBe('1,234.568'); + expect(binding('val | number:0')).toBe('1,235'); + expect(binding('-val | number:4')).toBe('-1,234.5679'); + }); + + it('should update', function() { + input('val').enter('3374.333'); + expect(binding('val | number')).toBe('3,374.333'); + expect(binding('val | number:0')).toBe('3,374'); + expect(binding('-val | number:4')).toBe('-3,374.3330'); + }); + +
      + */ + + +numberFilter.$inject = ['$locale']; +function numberFilter($locale) { + var formats = $locale.NUMBER_FORMATS; + return function(number, fractionSize) { + return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, + fractionSize); + }; +} + +var DECIMAL_SEP = '.'; +function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { + if (isNaN(number) || !isFinite(number)) return ''; + + var isNegative = number < 0; + number = Math.abs(number); + var numStr = number + '', + formatedText = '', + parts = []; + + var hasExponent = false; + if (numStr.indexOf('e') !== -1) { + var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); + if (match && match[2] == '-' && match[3] > fractionSize + 1) { + numStr = '0'; + } else { + formatedText = numStr; + hasExponent = true; + } + } + + if (!hasExponent) { + var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; + + // determine fractionSize if it is not specified + if (isUndefined(fractionSize)) { + fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); + } + + var pow = Math.pow(10, fractionSize); + number = Math.round(number * pow) / pow; + var fraction = ('' + number).split(DECIMAL_SEP); + var whole = fraction[0]; + fraction = fraction[1] || ''; + + var i, pos = 0, + lgroup = pattern.lgSize, + group = pattern.gSize; + + if (whole.length >= (lgroup + group)) { + pos = whole.length - lgroup; + for (i = 0; i < pos; i++) { + if ((pos - i)%group === 0 && i !== 0) { + formatedText += groupSep; + } + formatedText += whole.charAt(i); + } + } + + for (i = pos; i < whole.length; i++) { + if ((whole.length - i)%lgroup === 0 && i !== 0) { + formatedText += groupSep; + } + formatedText += whole.charAt(i); + } + + // format fraction part. + while(fraction.length < fractionSize) { + fraction += '0'; + } + + if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); + } else { + + if (fractionSize > 0 && number > -1 && number < 1) { + formatedText = number.toFixed(fractionSize); + } + } + + parts.push(isNegative ? pattern.negPre : pattern.posPre); + parts.push(formatedText); + parts.push(isNegative ? pattern.negSuf : pattern.posSuf); + return parts.join(''); +} + +function padNumber(num, digits, trim) { + var neg = ''; + if (num < 0) { + neg = '-'; + num = -num; + } + num = '' + num; + while(num.length < digits) num = '0' + num; + if (trim) + num = num.substr(num.length - digits); + return neg + num; +} + + +function dateGetter(name, size, offset, trim) { + offset = offset || 0; + return function(date) { + var value = date['get' + name](); + if (offset > 0 || value > -offset) + value += offset; + if (value === 0 && offset == -12 ) value = 12; + return padNumber(value, size, trim); + }; +} + +function dateStrGetter(name, shortForm) { + return function(date, formats) { + var value = date['get' + name](); + var get = uppercase(shortForm ? ('SHORT' + name) : name); + + return formats[get][value]; + }; +} + +function timeZoneGetter(date) { + var zone = -1 * date.getTimezoneOffset(); + var paddedZone = (zone >= 0) ? "+" : ""; + + paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + + padNumber(Math.abs(zone % 60), 2); + + return paddedZone; +} + +function ampmGetter(date, formats) { + return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; +} + +var DATE_FORMATS = { + yyyy: dateGetter('FullYear', 4), + yy: dateGetter('FullYear', 2, 0, true), + y: dateGetter('FullYear', 1), + MMMM: dateStrGetter('Month'), + MMM: dateStrGetter('Month', true), + MM: dateGetter('Month', 2, 1), + M: dateGetter('Month', 1, 1), + dd: dateGetter('Date', 2), + d: dateGetter('Date', 1), + HH: dateGetter('Hours', 2), + H: dateGetter('Hours', 1), + hh: dateGetter('Hours', 2, -12), + h: dateGetter('Hours', 1, -12), + mm: dateGetter('Minutes', 2), + m: dateGetter('Minutes', 1), + ss: dateGetter('Seconds', 2), + s: dateGetter('Seconds', 1), + // while ISO 8601 requires fractions to be prefixed with `.` or `,` + // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions + sss: dateGetter('Milliseconds', 3), + EEEE: dateStrGetter('Day'), + EEE: dateStrGetter('Day', true), + a: ampmGetter, + Z: timeZoneGetter +}; + +var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, + NUMBER_STRING = /^\-?\d+$/; + +/** + * @ngdoc filter + * @name ng.filter:date + * @function + * + * @description + * Formats `date` to a string based on the requested `format`. + * + * `format` string can be composed of the following elements: + * + * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) + * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) + * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) + * * `'MMMM'`: Month in year (January-December) + * * `'MMM'`: Month in year (Jan-Dec) + * * `'MM'`: Month in year, padded (01-12) + * * `'M'`: Month in year (1-12) + * * `'dd'`: Day in month, padded (01-31) + * * `'d'`: Day in month (1-31) + * * `'EEEE'`: Day in Week,(Sunday-Saturday) + * * `'EEE'`: Day in Week, (Sun-Sat) + * * `'HH'`: Hour in day, padded (00-23) + * * `'H'`: Hour in day (0-23) + * * `'hh'`: Hour in am/pm, padded (01-12) + * * `'h'`: Hour in am/pm, (1-12) + * * `'mm'`: Minute in hour, padded (00-59) + * * `'m'`: Minute in hour (0-59) + * * `'ss'`: Second in minute, padded (00-59) + * * `'s'`: Second in minute (0-59) + * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) + * * `'a'`: am/pm marker + * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) + * + * `format` string can also be one of the following predefined + * {@link guide/i18n localizable formats}: + * + * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale + * (e.g. Sep 3, 2010 12:05:08 pm) + * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) + * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale + * (e.g. Friday, September 3, 2010) + * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) + * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) + * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) + * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) + * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) + * + * `format` string can contain literal values. These need to be quoted with single quotes (e.g. + * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence + * (e.g. `"h 'o''clock'"`). + * + * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or + * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its + * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is + * specified in the string input, the time is considered to be in the local timezone. + * @param {string=} format Formatting rules (see Description). If not specified, + * `mediumDate` is used. + * @returns {string} Formatted string or the input if input is not recognized as date/millis. + * + * @example + + + {{1288323623006 | date:'medium'}}: + {{1288323623006 | date:'medium'}}
      + {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}: + {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}
      + {{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}: + {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}
      +
      + + it('should format date', function() { + expect(binding("1288323623006 | date:'medium'")). + toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); + expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")). + toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); + expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")). + toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); + }); + +
      + */ +dateFilter.$inject = ['$locale']; +function dateFilter($locale) { + + + var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; + // 1 2 3 4 5 6 7 8 9 10 11 + function jsonStringToDate(string) { + var match; + if (match = string.match(R_ISO8601_STR)) { + var date = new Date(0), + tzHour = 0, + tzMin = 0, + dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, + timeSetter = match[8] ? date.setUTCHours : date.setHours; + + if (match[9]) { + tzHour = int(match[9] + match[10]); + tzMin = int(match[9] + match[11]); + } + dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); + var h = int(match[4]||0) - tzHour; + var m = int(match[5]||0) - tzMin; + var s = int(match[6]||0); + var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); + timeSetter.call(date, h, m, s, ms); + return date; + } + return string; + } + + + return function(date, format) { + var text = '', + parts = [], + fn, match; + + format = format || 'mediumDate'; + format = $locale.DATETIME_FORMATS[format] || format; + if (isString(date)) { + if (NUMBER_STRING.test(date)) { + date = int(date); + } else { + date = jsonStringToDate(date); + } + } + + if (isNumber(date)) { + date = new Date(date); + } + + if (!isDate(date)) { + return date; + } + + while(format) { + match = DATE_FORMATS_SPLIT.exec(format); + if (match) { + parts = concat(parts, match, 1); + format = parts.pop(); + } else { + parts.push(format); + format = null; + } + } + + forEach(parts, function(value){ + fn = DATE_FORMATS[value]; + text += fn ? fn(date, $locale.DATETIME_FORMATS) + : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); + }); + + return text; + }; +} + + +/** + * @ngdoc filter + * @name ng.filter:json + * @function + * + * @description + * Allows you to convert a JavaScript object into JSON string. + * + * This filter is mostly useful for debugging. When using the double curly {{value}} notation + * the binding is automatically converted to JSON. + * + * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. + * @returns {string} JSON string. + * + * + * @example: + + +
      {{ {'name':'value'} | json }}
      +
      + + it('should jsonify filtered objects', function() { + expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/); + }); + +
      + * + */ +function jsonFilter() { + return function(object) { + return toJson(object, true); + }; +} + + +/** + * @ngdoc filter + * @name ng.filter:lowercase + * @function + * @description + * Converts string to lowercase. + * @see angular.lowercase + */ +var lowercaseFilter = valueFn(lowercase); + + +/** + * @ngdoc filter + * @name ng.filter:uppercase + * @function + * @description + * Converts string to uppercase. + * @see angular.uppercase + */ +var uppercaseFilter = valueFn(uppercase); + +/** + * @ngdoc function + * @name ng.filter:limitTo + * @function + * + * @description + * Creates a new array or string containing only a specified number of elements. The elements + * are taken from either the beginning or the end of the source array or string, as specified by + * the value and sign (positive or negative) of `limit`. + * + * @param {Array|string} input Source array or string to be limited. + * @param {string|number} limit The length of the returned array or string. If the `limit` number + * is positive, `limit` number of items from the beginning of the source array/string are copied. + * If the number is negative, `limit` number of items from the end of the source array/string + * are copied. The `limit` will be trimmed if it exceeds `array.length` + * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array + * had less than `limit` elements. + * + * @example + + + +
      + Limit {{numbers}} to: +

      Output numbers: {{ numbers | limitTo:numLimit }}

      + Limit {{letters}} to: +

      Output letters: {{ letters | limitTo:letterLimit }}

      +
      +
      + + it('should limit the number array to first three items', function() { + expect(element('.doc-example-live input[ng-model=numLimit]').val()).toBe('3'); + expect(element('.doc-example-live input[ng-model=letterLimit]').val()).toBe('3'); + expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3]'); + expect(binding('letters | limitTo:letterLimit')).toEqual('abc'); + }); + + it('should update the output when -3 is entered', function() { + input('numLimit').enter(-3); + input('letterLimit').enter(-3); + expect(binding('numbers | limitTo:numLimit')).toEqual('[7,8,9]'); + expect(binding('letters | limitTo:letterLimit')).toEqual('ghi'); + }); + + it('should not exceed the maximum size of input array', function() { + input('numLimit').enter(100); + input('letterLimit').enter(100); + expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3,4,5,6,7,8,9]'); + expect(binding('letters | limitTo:letterLimit')).toEqual('abcdefghi'); + }); + +
      + */ +function limitToFilter(){ + return function(input, limit) { + if (!isArray(input) && !isString(input)) return input; + + limit = int(limit); + + if (isString(input)) { + //NaN check on limit + if (limit) { + return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); + } else { + return ""; + } + } + + var out = [], + i, n; + + // if abs(limit) exceeds maximum length, trim it + if (limit > input.length) + limit = input.length; + else if (limit < -input.length) + limit = -input.length; + + if (limit > 0) { + i = 0; + n = limit; + } else { + i = input.length + limit; + n = input.length; + } + + for (; i} expression A predicate to be + * used by the comparator to determine the order of elements. + * + * Can be one of: + * + * - `function`: Getter function. The result of this function will be sorted using the + * `<`, `=`, `>` operator. + * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' + * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control + * ascending or descending sort order (for example, +name or -name). + * - `Array`: An array of function or string predicates. The first predicate in the array + * is used for sorting, but when two items are equivalent, the next predicate is used. + * + * @param {boolean=} reverse Reverse the order the array. + * @returns {Array} Sorted copy of the source array. + * + * @example + + + +
      +
      Sorting predicate = {{predicate}}; reverse = {{reverse}}
      +
      + [ unsorted ] + + + + + + + + + + + +
      Name + (^)Phone NumberAge
      {{friend.name}}{{friend.phone}}{{friend.age}}
      +
      +
      + + it('should be reverse ordered by aged', function() { + expect(binding('predicate')).toBe('-age'); + expect(repeater('table.friend', 'friend in friends').column('friend.age')). + toEqual(['35', '29', '21', '19', '10']); + expect(repeater('table.friend', 'friend in friends').column('friend.name')). + toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']); + }); + + it('should reorder the table when user selects different predicate', function() { + element('.doc-example-live a:contains("Name")').click(); + expect(repeater('table.friend', 'friend in friends').column('friend.name')). + toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']); + expect(repeater('table.friend', 'friend in friends').column('friend.age')). + toEqual(['35', '10', '29', '19', '21']); + + element('.doc-example-live a:contains("Phone")').click(); + expect(repeater('table.friend', 'friend in friends').column('friend.phone')). + toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']); + expect(repeater('table.friend', 'friend in friends').column('friend.name')). + toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']); + }); + +
      + */ +orderByFilter.$inject = ['$parse']; +function orderByFilter($parse){ + return function(array, sortPredicate, reverseOrder) { + if (!isArray(array)) return array; + if (!sortPredicate) return array; + sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; + sortPredicate = map(sortPredicate, function(predicate){ + var descending = false, get = predicate || identity; + if (isString(predicate)) { + if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { + descending = predicate.charAt(0) == '-'; + predicate = predicate.substring(1); + } + get = $parse(predicate); + } + return reverseComparator(function(a,b){ + return compare(get(a),get(b)); + }, descending); + }); + var arrayCopy = []; + for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } + return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); + + function comparator(o1, o2){ + for ( var i = 0; i < sortPredicate.length; i++) { + var comp = sortPredicate[i](o1, o2); + if (comp !== 0) return comp; + } + return 0; + } + function reverseComparator(comp, descending) { + return toBoolean(descending) + ? function(a,b){return comp(b,a);} + : comp; + } + function compare(v1, v2){ + var t1 = typeof v1; + var t2 = typeof v2; + if (t1 == t2) { + if (t1 == "string") { + v1 = v1.toLowerCase(); + v2 = v2.toLowerCase(); + } + if (v1 === v2) return 0; + return v1 < v2 ? -1 : 1; + } else { + return t1 < t2 ? -1 : 1; + } + } + }; +} + +function ngDirective(directive) { + if (isFunction(directive)) { + directive = { + link: directive + }; + } + directive.restrict = directive.restrict || 'AC'; + return valueFn(directive); +} + +/** + * @ngdoc directive + * @name ng.directive:a + * @restrict E + * + * @description + * Modifies the default behavior of the html A tag so that the default action is prevented when + * the href attribute is empty. + * + * This change permits the easy creation of action links with the `ngClick` directive + * without changing the location or causing page reloads, e.g.: + * `Add Item` + */ +var htmlAnchorDirective = valueFn({ + restrict: 'E', + compile: function(element, attr) { + + if (msie <= 8) { + + // turn link into a stylable link in IE + // but only if it doesn't have name attribute, in which case it's an anchor + if (!attr.href && !attr.name) { + attr.$set('href', ''); + } + + // add a comment node to anchors to workaround IE bug that causes element content to be reset + // to new attribute content if attribute is updated with value containing @ and element also + // contains value with @ + // see issue #1949 + element.append(document.createComment('IE fix')); + } + + if (!attr.href && !attr.name) { + return function(scope, element) { + element.on('click', function(event){ + // if we have no href url, then don't navigate anywhere. + if (!element.attr('href')) { + event.preventDefault(); + } + }); + }; + } + } +}); + +/** + * @ngdoc directive + * @name ng.directive:ngHref + * @restrict A + * @priority 99 + * + * @description + * Using Angular markup like `{{hash}}` in an href attribute will + * make the link go to the wrong URL if the user clicks it before + * Angular has a chance to replace the `{{hash}}` markup with its + * value. Until Angular replaces the markup the link will be broken + * and will most likely return a 404 error. + * + * The `ngHref` directive solves this problem. + * + * The wrong way to write it: + *
      + * 
      + * 
      + * + * The correct way to write it: + *
      + * 
      + * 
      + * + * @element A + * @param {template} ngHref any string which can contain `{{}}` markup. + * + * @example + * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes + * in links and their different behaviors: + + +
      +
      link 1 (link, don't reload)
      + link 2 (link, don't reload)
      + link 3 (link, reload!)
      + anchor (link, don't reload)
      + anchor (no link)
      + link (link, change location) + + + it('should execute ng-click but not reload when href without value', function() { + element('#link-1').click(); + expect(input('value').val()).toEqual('1'); + expect(element('#link-1').attr('href')).toBe(""); + }); + + it('should execute ng-click but not reload when href empty string', function() { + element('#link-2').click(); + expect(input('value').val()).toEqual('2'); + expect(element('#link-2').attr('href')).toBe(""); + }); + + it('should execute ng-click and change url when ng-href specified', function() { + expect(element('#link-3').attr('href')).toBe("/123"); + + element('#link-3').click(); + expect(browser().window().path()).toEqual('/123'); + }); + + it('should execute ng-click but not reload when href empty string and name specified', function() { + element('#link-4').click(); + expect(input('value').val()).toEqual('4'); + expect(element('#link-4').attr('href')).toBe(''); + }); + + it('should execute ng-click but not reload when no href but name specified', function() { + element('#link-5').click(); + expect(input('value').val()).toEqual('5'); + expect(element('#link-5').attr('href')).toBe(undefined); + }); + + it('should only change url when only ng-href', function() { + input('value').enter('6'); + expect(element('#link-6').attr('href')).toBe('6'); + + element('#link-6').click(); + expect(browser().location().url()).toEqual('/6'); + }); + + + */ + +/** + * @ngdoc directive + * @name ng.directive:ngSrc + * @restrict A + * @priority 99 + * + * @description + * Using Angular markup like `{{hash}}` in a `src` attribute doesn't + * work right: The browser will fetch from the URL with the literal + * text `{{hash}}` until Angular replaces the expression inside + * `{{hash}}`. The `ngSrc` directive solves this problem. + * + * The buggy way to write it: + *
      + * 
      + * 
      + * + * The correct way to write it: + *
      + * 
      + * 
      + * + * @element IMG + * @param {template} ngSrc any string which can contain `{{}}` markup. + */ + +/** + * @ngdoc directive + * @name ng.directive:ngSrcset + * @restrict A + * @priority 99 + * + * @description + * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't + * work right: The browser will fetch from the URL with the literal + * text `{{hash}}` until Angular replaces the expression inside + * `{{hash}}`. The `ngSrcset` directive solves this problem. + * + * The buggy way to write it: + *
      + * 
      + * 
      + * + * The correct way to write it: + *
      + * 
      + * 
      + * + * @element IMG + * @param {template} ngSrcset any string which can contain `{{}}` markup. + */ + +/** + * @ngdoc directive + * @name ng.directive:ngDisabled + * @restrict A + * @priority 100 + * + * @description + * + * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: + *
      + * 
      + * + *
      + *
      + * + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as disabled. (Their presence means true and their absence means false.) + * If we put an Angular interpolation expression into such an attribute then the + * binding information would be lost when the browser removes the attribute. + * The `ngDisabled` directive solves this problem for the `disabled` attribute. + * This complementary directive is not removed by the browser and so provides + * a permanent reliable place to store the binding information. + * + * @example + + + Click me to toggle:
      + +
      + + it('should toggle button', function() { + expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy(); + input('checked').check(); + expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy(); + }); + +
      + * + * @element INPUT + * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, + * then special attribute "disabled" will be set on the element + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngChecked + * @restrict A + * @priority 100 + * + * @description + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as checked. (Their presence means true and their absence means false.) + * If we put an Angular interpolation expression into such an attribute then the + * binding information would be lost when the browser removes the attribute. + * The `ngChecked` directive solves this problem for the `checked` attribute. + * This complementary directive is not removed by the browser and so provides + * a permanent reliable place to store the binding information. + * @example + + + Check me to check both:
      + +
      + + it('should check both checkBoxes', function() { + expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy(); + input('master').check(); + expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy(); + }); + +
      + * + * @element INPUT + * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, + * then special attribute "checked" will be set on the element + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngReadonly + * @restrict A + * @priority 100 + * + * @description + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as readonly. (Their presence means true and their absence means false.) + * If we put an Angular interpolation expression into such an attribute then the + * binding information would be lost when the browser removes the attribute. + * The `ngReadonly` directive solves this problem for the `readonly` attribute. + * This complementary directive is not removed by the browser and so provides + * a permanent reliable place to store the binding information. + * @example + + + Check me to make text readonly:
      + +
      + + it('should toggle readonly attr', function() { + expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy(); + input('checked').check(); + expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy(); + }); + +
      + * + * @element INPUT + * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, + * then special attribute "readonly" will be set on the element + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngSelected + * @restrict A + * @priority 100 + * + * @description + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as selected. (Their presence means true and their absence means false.) + * If we put an Angular interpolation expression into such an attribute then the + * binding information would be lost when the browser removes the attribute. + * The `ngSelected` directive solves this problem for the `selected` atttribute. + * This complementary directive is not removed by the browser and so provides + * a permanent reliable place to store the binding information. + * + * @example + + + Check me to select:
      + +
      + + it('should select Greetings!', function() { + expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy(); + input('selected').check(); + expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy(); + }); + +
      + * + * @element OPTION + * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, + * then special attribute "selected" will be set on the element + */ + +/** + * @ngdoc directive + * @name ng.directive:ngOpen + * @restrict A + * @priority 100 + * + * @description + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as open. (Their presence means true and their absence means false.) + * If we put an Angular interpolation expression into such an attribute then the + * binding information would be lost when the browser removes the attribute. + * The `ngOpen` directive solves this problem for the `open` attribute. + * This complementary directive is not removed by the browser and so provides + * a permanent reliable place to store the binding information. + * @example + + + Check me check multiple:
      +
      + Show/Hide me +
      +
      + + it('should toggle open', function() { + expect(element('#details').prop('open')).toBeFalsy(); + input('open').check(); + expect(element('#details').prop('open')).toBeTruthy(); + }); + +
      + * + * @element DETAILS + * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, + * then special attribute "open" will be set on the element + */ + +var ngAttributeAliasDirectives = {}; + + +// boolean attrs are evaluated +forEach(BOOLEAN_ATTR, function(propName, attrName) { + // binding to multiple is not supported + if (propName == "multiple") return; + + var normalized = directiveNormalize('ng-' + attrName); + ngAttributeAliasDirectives[normalized] = function() { + return { + priority: 100, + link: function(scope, element, attr) { + scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { + attr.$set(attrName, !!value); + }); + } + }; + }; +}); + + +// ng-src, ng-srcset, ng-href are interpolated +forEach(['src', 'srcset', 'href'], function(attrName) { + var normalized = directiveNormalize('ng-' + attrName); + ngAttributeAliasDirectives[normalized] = function() { + return { + priority: 99, // it needs to run after the attributes are interpolated + link: function(scope, element, attr) { + attr.$observe(normalized, function(value) { + if (!value) + return; + + attr.$set(attrName, value); + + // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist + // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need + // to set the property as well to achieve the desired effect. + // we use attr[attrName] value since $set can sanitize the url. + if (msie) element.prop(attrName, attr[attrName]); + }); + } + }; + }; +}); + +/* global -nullFormCtrl */ +var nullFormCtrl = { + $addControl: noop, + $removeControl: noop, + $setValidity: noop, + $setDirty: noop, + $setPristine: noop +}; + +/** + * @ngdoc object + * @name ng.directive:form.FormController + * + * @property {boolean} $pristine True if user has not interacted with the form yet. + * @property {boolean} $dirty True if user has already interacted with the form. + * @property {boolean} $valid True if all of the containing forms and controls are valid. + * @property {boolean} $invalid True if at least one containing control or form is invalid. + * + * @property {Object} $error Is an object hash, containing references to all invalid controls or + * forms, where: + * + * - keys are validation tokens (error names), + * - values are arrays of controls or forms that are invalid for given error name. + * + * + * Built-in validation tokens: + * + * - `email` + * - `max` + * - `maxlength` + * - `min` + * - `minlength` + * - `number` + * - `pattern` + * - `required` + * - `url` + * + * @description + * `FormController` keeps track of all its controls and nested forms as well as state of them, + * such as being valid/invalid or dirty/pristine. + * + * Each {@link ng.directive:form form} directive creates an instance + * of `FormController`. + * + */ +//asks for $scope to fool the BC controller module +FormController.$inject = ['$element', '$attrs', '$scope']; +function FormController(element, attrs) { + var form = this, + parentForm = element.parent().controller('form') || nullFormCtrl, + invalidCount = 0, // used to easily determine if we are valid + errors = form.$error = {}, + controls = []; + + // init state + form.$name = attrs.name || attrs.ngForm; + form.$dirty = false; + form.$pristine = true; + form.$valid = true; + form.$invalid = false; + + parentForm.$addControl(form); + + // Setup initial state of the control + element.addClass(PRISTINE_CLASS); + toggleValidCss(true); + + // convenience method for easy toggling of classes + function toggleValidCss(isValid, validationErrorKey) { + validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; + element. + removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). + addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); + } + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$addControl + * @methodOf ng.directive:form.FormController + * + * @description + * Register a control with the form. + * + * Input elements using ngModelController do this automatically when they are linked. + */ + form.$addControl = function(control) { + // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored + // and not added to the scope. Now we throw an error. + assertNotHasOwnProperty(control.$name, 'input'); + controls.push(control); + + if (control.$name) { + form[control.$name] = control; + } + }; + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$removeControl + * @methodOf ng.directive:form.FormController + * + * @description + * Deregister a control from the form. + * + * Input elements using ngModelController do this automatically when they are destroyed. + */ + form.$removeControl = function(control) { + if (control.$name && form[control.$name] === control) { + delete form[control.$name]; + } + forEach(errors, function(queue, validationToken) { + form.$setValidity(validationToken, true, control); + }); + + arrayRemove(controls, control); + }; + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$setValidity + * @methodOf ng.directive:form.FormController + * + * @description + * Sets the validity of a form control. + * + * This method will also propagate to parent forms. + */ + form.$setValidity = function(validationToken, isValid, control) { + var queue = errors[validationToken]; + + if (isValid) { + if (queue) { + arrayRemove(queue, control); + if (!queue.length) { + invalidCount--; + if (!invalidCount) { + toggleValidCss(isValid); + form.$valid = true; + form.$invalid = false; + } + errors[validationToken] = false; + toggleValidCss(true, validationToken); + parentForm.$setValidity(validationToken, true, form); + } + } + + } else { + if (!invalidCount) { + toggleValidCss(isValid); + } + if (queue) { + if (includes(queue, control)) return; + } else { + errors[validationToken] = queue = []; + invalidCount++; + toggleValidCss(false, validationToken); + parentForm.$setValidity(validationToken, false, form); + } + queue.push(control); + + form.$valid = false; + form.$invalid = true; + } + }; + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$setDirty + * @methodOf ng.directive:form.FormController + * + * @description + * Sets the form to a dirty state. + * + * This method can be called to add the 'ng-dirty' class and set the form to a dirty + * state (ng-dirty class). This method will also propagate to parent forms. + */ + form.$setDirty = function() { + element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); + form.$dirty = true; + form.$pristine = false; + parentForm.$setDirty(); + }; + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$setPristine + * @methodOf ng.directive:form.FormController + * + * @description + * Sets the form to its pristine state. + * + * This method can be called to remove the 'ng-dirty' class and set the form to its pristine + * state (ng-pristine class). This method will also propagate to all the controls contained + * in this form. + * + * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after + * saving or resetting it. + */ + form.$setPristine = function () { + element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); + form.$dirty = false; + form.$pristine = true; + forEach(controls, function(control) { + control.$setPristine(); + }); + }; +} + + +/** + * @ngdoc directive + * @name ng.directive:ngForm + * @restrict EAC + * + * @description + * Nestable alias of {@link ng.directive:form `form`} directive. HTML + * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a + * sub-group of controls needs to be determined. + * + * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into + * related scope, under this name. + * + */ + + /** + * @ngdoc directive + * @name ng.directive:form + * @restrict E + * + * @description + * Directive that instantiates + * {@link ng.directive:form.FormController FormController}. + * + * If the `name` attribute is specified, the form controller is published onto the current scope under + * this name. + * + * # Alias: {@link ng.directive:ngForm `ngForm`} + * + * In Angular forms can be nested. This means that the outer form is valid when all of the child + * forms are valid as well. However, browsers do not allow nesting of `
      ` elements, so + * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to + * `` but can be nested. This allows you to have nested forms, which is very useful when + * using Angular validation directives in forms that are dynamically generated using the + * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` + * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an + * `ngForm` directive and nest these in an outer `form` element. + * + * + * # CSS classes + * - `ng-valid` is set if the form is valid. + * - `ng-invalid` is set if the form is invalid. + * - `ng-pristine` is set if the form is pristine. + * - `ng-dirty` is set if the form is dirty. + * + * + * # Submitting a form and preventing the default action + * + * Since the role of forms in client-side Angular applications is different than in classical + * roundtrip apps, it is desirable for the browser not to translate the form submission into a full + * page reload that sends the data to the server. Instead some javascript logic should be triggered + * to handle the form submission in an application-specific way. + * + * For this reason, Angular prevents the default action (form submission to the server) unless the + * `` element has an `action` attribute specified. + * + * You can use one of the following two ways to specify what javascript method should be called when + * a form is submitted: + * + * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element + * - {@link ng.directive:ngClick ngClick} directive on the first + * button or input field of type submit (input[type=submit]) + * + * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} + * or {@link ng.directive:ngClick ngClick} directives. + * This is because of the following form submission rules in the HTML specification: + * + * - If a form has only one input field then hitting enter in this field triggers form submit + * (`ngSubmit`) + * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter + * doesn't trigger submit + * - if a form has one or more input fields and one or more buttons or input[type=submit] then + * hitting enter in any of the input fields will trigger the click handler on the *first* button or + * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) + * + * @param {string=} name Name of the form. If specified, the form controller will be published into + * related scope, under this name. + * + * @example + + + + + userType: + Required!
      + userType = {{userType}}
      + myForm.input.$valid = {{myForm.input.$valid}}
      + myForm.input.$error = {{myForm.input.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      + +
      + + it('should initialize to model', function() { + expect(binding('userType')).toEqual('guest'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('userType').enter(''); + expect(binding('userType')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
      + */ +var formDirectiveFactory = function(isNgForm) { + return ['$timeout', function($timeout) { + var formDirective = { + name: 'form', + restrict: isNgForm ? 'EAC' : 'E', + controller: FormController, + compile: function() { + return { + pre: function(scope, formElement, attr, controller) { + if (!attr.action) { + // we can't use jq events because if a form is destroyed during submission the default + // action is not prevented. see #1238 + // + // IE 9 is not affected because it doesn't fire a submit event and try to do a full + // page reload if the form was destroyed by submission of the form via a click handler + // on a button in the form. Looks like an IE9 specific bug. + var preventDefaultListener = function(event) { + event.preventDefault + ? event.preventDefault() + : event.returnValue = false; // IE + }; + + addEventListenerFn(formElement[0], 'submit', preventDefaultListener); + + // unregister the preventDefault listener so that we don't not leak memory but in a + // way that will achieve the prevention of the default action. + formElement.on('$destroy', function() { + $timeout(function() { + removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); + }, 0, false); + }); + } + + var parentFormCtrl = formElement.parent().controller('form'), + alias = attr.name || attr.ngForm; + + if (alias) { + setter(scope, alias, controller, alias); + } + if (parentFormCtrl) { + formElement.on('$destroy', function() { + parentFormCtrl.$removeControl(controller); + if (alias) { + setter(scope, alias, undefined, alias); + } + extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards + }); + } + } + }; + } + }; + + return formDirective; + }]; +}; + +var formDirective = formDirectiveFactory(); +var ngFormDirective = formDirectiveFactory(true); + +/* global + + -VALID_CLASS, + -INVALID_CLASS, + -PRISTINE_CLASS, + -DIRTY_CLASS +*/ + +var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; +var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/; +var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; + +var inputType = { + + /** + * @ngdoc inputType + * @name ng.directive:input.text + * + * @description + * Standard HTML text input with angular data binding. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Adds `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. + * + * @example + + + +
      + Single word: + + Required! + + Single word only! + + text = {{text}}
      + myForm.input.$valid = {{myForm.input.$valid}}
      + myForm.input.$error = {{myForm.input.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('text')).toEqual('guest'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('text').enter(''); + expect(binding('text')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if multi word', function() { + input('text').enter('hello world'); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should not be trimmed', function() { + input('text').enter('untrimmed '); + expect(binding('text')).toEqual('untrimmed '); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + +
      + */ + 'text': textInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.number + * + * @description + * Text input with number validation and transformation. Sets the `number` validation + * error if not a valid number. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
      + Number: + + Required! + + Not valid number! + value = {{value}}
      + myForm.input.$valid = {{myForm.input.$valid}}
      + myForm.input.$error = {{myForm.input.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('value')).toEqual('12'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('value').enter(''); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if over max', function() { + input('value').enter('123'); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
      + */ + 'number': numberInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.url + * + * @description + * Text input with URL validation. Sets the `url` validation error key if the content is not a + * valid URL. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
      + URL: + + Required! + + Not valid url! + text = {{text}}
      + myForm.input.$valid = {{myForm.input.$valid}}
      + myForm.input.$error = {{myForm.input.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      + myForm.$error.url = {{!!myForm.$error.url}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('text')).toEqual('http://google.com'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('text').enter(''); + expect(binding('text')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if not url', function() { + input('text').enter('xxx'); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
      + */ + 'url': urlInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.email + * + * @description + * Text input with email validation. Sets the `email` validation error key if not a valid email + * address. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
      + Email: + + Required! + + Not valid email! + text = {{text}}
      + myForm.input.$valid = {{myForm.input.$valid}}
      + myForm.input.$error = {{myForm.input.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      + myForm.$error.email = {{!!myForm.$error.email}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('text')).toEqual('me@example.com'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('text').enter(''); + expect(binding('text')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if not email', function() { + input('text').enter('xxx'); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
      + */ + 'email': emailInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.radio + * + * @description + * HTML radio button. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} value The value to which the expression should be set when selected. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
      + Red
      + Green
      + Blue
      + color = {{color}}
      +
      +
      + + it('should change state', function() { + expect(binding('color')).toEqual('blue'); + + input('color').select('red'); + expect(binding('color')).toEqual('red'); + }); + +
      + */ + 'radio': radioInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.checkbox + * + * @description + * HTML checkbox. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} ngTrueValue The value to which the expression should be set when selected. + * @param {string=} ngFalseValue The value to which the expression should be set when not selected. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
      + Value1:
      + Value2:
      + value1 = {{value1}}
      + value2 = {{value2}}
      +
      +
      + + it('should change state', function() { + expect(binding('value1')).toEqual('true'); + expect(binding('value2')).toEqual('YES'); + + input('value1').check(); + input('value2').check(); + expect(binding('value1')).toEqual('false'); + expect(binding('value2')).toEqual('NO'); + }); + +
      + */ + 'checkbox': checkboxInputType, + + 'hidden': noop, + 'button': noop, + 'submit': noop, + 'reset': noop +}; + +// A helper function to call $setValidity and return the value / undefined, +// a pattern that is repeated a lot in the input validation logic. +function validate(ctrl, validatorName, validity, value){ + ctrl.$setValidity(validatorName, validity); + return validity ? value : undefined; +} + +function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { + // In composition mode, users are still inputing intermediate text buffer, + // hold the listener until composition is done. + // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent + if (!$sniffer.android) { + var composing = false; + + element.on('compositionstart', function(data) { + composing = true; + }); + + element.on('compositionend', function() { + composing = false; + }); + } + + var listener = function() { + if (composing) return; + var value = element.val(); + + // By default we will trim the value + // If the attribute ng-trim exists we will avoid trimming + // e.g. + if (toBoolean(attr.ngTrim || 'T')) { + value = trim(value); + } + + if (ctrl.$viewValue !== value) { + if (scope.$$phase) { + ctrl.$setViewValue(value); + } else { + scope.$apply(function() { + ctrl.$setViewValue(value); + }); + } + } + }; + + // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the + // input event on backspace, delete or cut + if ($sniffer.hasEvent('input')) { + element.on('input', listener); + } else { + var timeout; + + var deferListener = function() { + if (!timeout) { + timeout = $browser.defer(function() { + listener(); + timeout = null; + }); + } + }; + + element.on('keydown', function(event) { + var key = event.keyCode; + + // ignore + // command modifiers arrows + if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; + + deferListener(); + }); + + // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it + if ($sniffer.hasEvent('paste')) { + element.on('paste cut', deferListener); + } + } + + // if user paste into input using mouse on older browser + // or form autocomplete on newer browser, we need "change" event to catch it + element.on('change', listener); + + ctrl.$render = function() { + element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); + }; + + // pattern validator + var pattern = attr.ngPattern, + patternValidator, + match; + + if (pattern) { + var validateRegex = function(regexp, value) { + return validate(ctrl, 'pattern', ctrl.$isEmpty(value) || regexp.test(value), value); + }; + match = pattern.match(/^\/(.*)\/([gim]*)$/); + if (match) { + pattern = new RegExp(match[1], match[2]); + patternValidator = function(value) { + return validateRegex(pattern, value); + }; + } else { + patternValidator = function(value) { + var patternObj = scope.$eval(pattern); + + if (!patternObj || !patternObj.test) { + throw minErr('ngPattern')('noregexp', + 'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern, + patternObj, startingTag(element)); + } + return validateRegex(patternObj, value); + }; + } + + ctrl.$formatters.push(patternValidator); + ctrl.$parsers.push(patternValidator); + } + + // min length validator + if (attr.ngMinlength) { + var minlength = int(attr.ngMinlength); + var minLengthValidator = function(value) { + return validate(ctrl, 'minlength', ctrl.$isEmpty(value) || value.length >= minlength, value); + }; + + ctrl.$parsers.push(minLengthValidator); + ctrl.$formatters.push(minLengthValidator); + } + + // max length validator + if (attr.ngMaxlength) { + var maxlength = int(attr.ngMaxlength); + var maxLengthValidator = function(value) { + return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value); + }; + + ctrl.$parsers.push(maxLengthValidator); + ctrl.$formatters.push(maxLengthValidator); + } +} + +function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + ctrl.$parsers.push(function(value) { + var empty = ctrl.$isEmpty(value); + if (empty || NUMBER_REGEXP.test(value)) { + ctrl.$setValidity('number', true); + return value === '' ? null : (empty ? value : parseFloat(value)); + } else { + ctrl.$setValidity('number', false); + return undefined; + } + }); + + ctrl.$formatters.push(function(value) { + return ctrl.$isEmpty(value) ? '' : '' + value; + }); + + if (attr.min) { + var minValidator = function(value) { + var min = parseFloat(attr.min); + return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value); + }; + + ctrl.$parsers.push(minValidator); + ctrl.$formatters.push(minValidator); + } + + if (attr.max) { + var maxValidator = function(value) { + var max = parseFloat(attr.max); + return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value); + }; + + ctrl.$parsers.push(maxValidator); + ctrl.$formatters.push(maxValidator); + } + + ctrl.$formatters.push(function(value) { + return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value); + }); +} + +function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + var urlValidator = function(value) { + return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value); + }; + + ctrl.$formatters.push(urlValidator); + ctrl.$parsers.push(urlValidator); +} + +function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + var emailValidator = function(value) { + return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value); + }; + + ctrl.$formatters.push(emailValidator); + ctrl.$parsers.push(emailValidator); +} + +function radioInputType(scope, element, attr, ctrl) { + // make the name unique, if not defined + if (isUndefined(attr.name)) { + element.attr('name', nextUid()); + } + + element.on('click', function() { + if (element[0].checked) { + scope.$apply(function() { + ctrl.$setViewValue(attr.value); + }); + } + }); + + ctrl.$render = function() { + var value = attr.value; + element[0].checked = (value == ctrl.$viewValue); + }; + + attr.$observe('value', ctrl.$render); +} + +function checkboxInputType(scope, element, attr, ctrl) { + var trueValue = attr.ngTrueValue, + falseValue = attr.ngFalseValue; + + if (!isString(trueValue)) trueValue = true; + if (!isString(falseValue)) falseValue = false; + + element.on('click', function() { + scope.$apply(function() { + ctrl.$setViewValue(element[0].checked); + }); + }); + + ctrl.$render = function() { + element[0].checked = ctrl.$viewValue; + }; + + // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. + ctrl.$isEmpty = function(value) { + return value !== trueValue; + }; + + ctrl.$formatters.push(function(value) { + return value === trueValue; + }); + + ctrl.$parsers.push(function(value) { + return value ? trueValue : falseValue; + }); +} + + +/** + * @ngdoc directive + * @name ng.directive:textarea + * @restrict E + * + * @description + * HTML textarea element control with angular data-binding. The data-binding and validation + * properties of this element are exactly the same as those of the + * {@link ng.directive:input input element}. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + */ + + +/** + * @ngdoc directive + * @name ng.directive:input + * @restrict E + * + * @description + * HTML input element control with angular data-binding. Input control follows HTML5 input types + * and polyfills the HTML5 validation behavior for older browsers. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {boolean=} ngRequired Sets `required` attribute if set to true + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
      +
      + User name: + + Required!
      + Last name: + + Too short! + + Too long!
      +
      +
      + user = {{user}}
      + myForm.userName.$valid = {{myForm.userName.$valid}}
      + myForm.userName.$error = {{myForm.userName.$error}}
      + myForm.lastName.$valid = {{myForm.lastName.$valid}}
      + myForm.lastName.$error = {{myForm.lastName.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      + myForm.$error.minlength = {{!!myForm.$error.minlength}}
      + myForm.$error.maxlength = {{!!myForm.$error.maxlength}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}'); + expect(binding('myForm.userName.$valid')).toEqual('true'); + expect(binding('myForm.$valid')).toEqual('true'); + }); + + it('should be invalid if empty when required', function() { + input('user.name').enter(''); + expect(binding('user')).toEqual('{"last":"visitor"}'); + expect(binding('myForm.userName.$valid')).toEqual('false'); + expect(binding('myForm.$valid')).toEqual('false'); + }); + + it('should be valid if empty when min length is set', function() { + input('user.last').enter(''); + expect(binding('user')).toEqual('{"name":"guest","last":""}'); + expect(binding('myForm.lastName.$valid')).toEqual('true'); + expect(binding('myForm.$valid')).toEqual('true'); + }); + + it('should be invalid if less than required min length', function() { + input('user.last').enter('xx'); + expect(binding('user')).toEqual('{"name":"guest"}'); + expect(binding('myForm.lastName.$valid')).toEqual('false'); + expect(binding('myForm.lastName.$error')).toMatch(/minlength/); + expect(binding('myForm.$valid')).toEqual('false'); + }); + + it('should be invalid if longer than max length', function() { + input('user.last').enter('some ridiculously long name'); + expect(binding('user')) + .toEqual('{"name":"guest"}'); + expect(binding('myForm.lastName.$valid')).toEqual('false'); + expect(binding('myForm.lastName.$error')).toMatch(/maxlength/); + expect(binding('myForm.$valid')).toEqual('false'); + }); + +
      + */ +var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) { + return { + restrict: 'E', + require: '?ngModel', + link: function(scope, element, attr, ctrl) { + if (ctrl) { + (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, + $browser); + } + } + }; +}]; + +var VALID_CLASS = 'ng-valid', + INVALID_CLASS = 'ng-invalid', + PRISTINE_CLASS = 'ng-pristine', + DIRTY_CLASS = 'ng-dirty'; + +/** + * @ngdoc object + * @name ng.directive:ngModel.NgModelController + * + * @property {string} $viewValue Actual string value in the view. + * @property {*} $modelValue The value in the model, that the control is bound to. + * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever + the control reads value from the DOM. Each function is called, in turn, passing the value + through to the next. Used to sanitize / convert the value as well as validation. + For validation, the parsers should update the validity state using + {@link ng.directive:ngModel.NgModelController#methods_$setValidity $setValidity()}, + and return `undefined` for invalid values. + + * + * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever + the model value changes. Each function is called, in turn, passing the value through to the + next. Used to format / convert values for display in the control and validation. + *
      + *      function formatter(value) {
      + *        if (value) {
      + *          return value.toUpperCase();
      + *        }
      + *      }
      + *      ngModel.$formatters.push(formatter);
      + *      
      + * + * @property {Array.} $viewChangeListeners Array of functions to execute whenever the + * view value has changed. It is called with no arguments, and its return value is ignored. + * This can be used in place of additional $watches against the model value. + * + * @property {Object} $error An object hash with all errors as keys. + * + * @property {boolean} $pristine True if user has not interacted with the control yet. + * @property {boolean} $dirty True if user has already interacted with the control. + * @property {boolean} $valid True if there is no error. + * @property {boolean} $invalid True if at least one error on the control. + * + * @description + * + * `NgModelController` provides API for the `ng-model` directive. The controller contains + * services for data-binding, validation, CSS updates, and value formatting and parsing. It + * purposefully does not contain any logic which deals with DOM rendering or listening to + * DOM events. Such DOM related logic should be provided by other directives which make use of + * `NgModelController` for data-binding. + * + * ## Custom Control Example + * This example shows how to use `NgModelController` with a custom control to achieve + * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) + * collaborate together to achieve the desired result. + * + * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element + * contents be edited in place by the user. This will not work on older browsers. + * + * + + [contenteditable] { + border: 1px solid black; + background-color: white; + min-height: 20px; + } + + .ng-invalid { + border: 1px solid red; + } + + + + angular.module('customControl', []). + directive('contenteditable', function() { + return { + restrict: 'A', // only activate on element attribute + require: '?ngModel', // get a hold of NgModelController + link: function(scope, element, attrs, ngModel) { + if(!ngModel) return; // do nothing if no ng-model + + // Specify how UI should be updated + ngModel.$render = function() { + element.html(ngModel.$viewValue || ''); + }; + + // Listen for change events to enable binding + element.on('blur keyup change', function() { + scope.$apply(read); + }); + read(); // initialize + + // Write data to the model + function read() { + var html = element.html(); + // When we clear the content editable the browser leaves a
      behind + // If strip-br attribute is provided then we strip this out + if( attrs.stripBr && html == '
      ' ) { + html = ''; + } + ngModel.$setViewValue(html); + } + } + }; + }); +
      + +
      +
      Change me!
      + Required! +
      + +
      +
      + + it('should data-bind and become invalid', function() { + var contentEditable = element('[contenteditable]'); + + expect(contentEditable.text()).toEqual('Change me!'); + input('userContent').enter(''); + expect(contentEditable.text()).toEqual(''); + expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/); + }); + + *
      + * + * + */ +var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', + function($scope, $exceptionHandler, $attr, $element, $parse) { + this.$viewValue = Number.NaN; + this.$modelValue = Number.NaN; + this.$parsers = []; + this.$formatters = []; + this.$viewChangeListeners = []; + this.$pristine = true; + this.$dirty = false; + this.$valid = true; + this.$invalid = false; + this.$name = $attr.name; + + var ngModelGet = $parse($attr.ngModel), + ngModelSet = ngModelGet.assign; + + if (!ngModelSet) { + throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", + $attr.ngModel, startingTag($element)); + } + + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$render + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Called when the view needs to be updated. It is expected that the user of the ng-model + * directive will implement this method. + */ + this.$render = noop; + + /** + * @ngdoc function + * @name { ng.directive:ngModel.NgModelController#$isEmpty + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * This is called when we need to determine if the value of the input is empty. + * + * For instance, the required directive does this to work out if the input has data or not. + * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. + * + * You can override this for input directives whose concept of being empty is different to the + * default. The `checkboxInputType` directive does this because in its case a value of `false` + * implies empty. + */ + this.$isEmpty = function(value) { + return isUndefined(value) || value === '' || value === null || value !== value; + }; + + var parentForm = $element.inheritedData('$formController') || nullFormCtrl, + invalidCount = 0, // used to easily determine if we are valid + $error = this.$error = {}; // keep invalid keys here + + + // Setup initial state of the control + $element.addClass(PRISTINE_CLASS); + toggleValidCss(true); + + // convenience method for easy toggling of classes + function toggleValidCss(isValid, validationErrorKey) { + validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; + $element. + removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). + addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); + } + + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$setValidity + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Change the validity state, and notifies the form when the control changes validity. (i.e. it + * does not notify form if given validator is already marked as invalid). + * + * This method should be called by validators - i.e. the parser or formatter functions. + * + * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign + * to `$error[validationErrorKey]=isValid` so that it is available for data-binding. + * The `validationErrorKey` should be in camelCase and will get converted into dash-case + * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` + * class and can be bound to as `{{someForm.someControl.$error.myError}}` . + * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). + */ + this.$setValidity = function(validationErrorKey, isValid) { + // Purposeful use of ! here to cast isValid to boolean in case it is undefined + // jshint -W018 + if ($error[validationErrorKey] === !isValid) return; + // jshint +W018 + + if (isValid) { + if ($error[validationErrorKey]) invalidCount--; + if (!invalidCount) { + toggleValidCss(true); + this.$valid = true; + this.$invalid = false; + } + } else { + toggleValidCss(false); + this.$invalid = true; + this.$valid = false; + invalidCount++; + } + + $error[validationErrorKey] = !isValid; + toggleValidCss(isValid, validationErrorKey); + + parentForm.$setValidity(validationErrorKey, isValid, this); + }; + + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$setPristine + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Sets the control to its pristine state. + * + * This method can be called to remove the 'ng-dirty' class and set the control to its pristine + * state (ng-pristine class). + */ + this.$setPristine = function () { + this.$dirty = false; + this.$pristine = true; + $element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); + }; + + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$setViewValue + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Update the view value. + * + * This method should be called when the view value changes, typically from within a DOM event handler. + * For example {@link ng.directive:input input} and + * {@link ng.directive:select select} directives call it. + * + * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, + * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to + * `$modelValue` and the **expression** specified in the `ng-model` attribute. + * + * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. + * + * Note that calling this function does not trigger a `$digest`. + * + * @param {string} value Value from the view. + */ + this.$setViewValue = function(value) { + this.$viewValue = value; + + // change to dirty + if (this.$pristine) { + this.$dirty = true; + this.$pristine = false; + $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); + parentForm.$setDirty(); + } + + forEach(this.$parsers, function(fn) { + value = fn(value); + }); + + if (this.$modelValue !== value) { + this.$modelValue = value; + ngModelSet($scope, value); + forEach(this.$viewChangeListeners, function(listener) { + try { + listener(); + } catch(e) { + $exceptionHandler(e); + } + }); + } + }; + + // model -> value + var ctrl = this; + + $scope.$watch(function ngModelWatch() { + var value = ngModelGet($scope); + + // if scope model value and ngModel value are out of sync + if (ctrl.$modelValue !== value) { + + var formatters = ctrl.$formatters, + idx = formatters.length; + + ctrl.$modelValue = value; + while(idx--) { + value = formatters[idx](value); + } + + if (ctrl.$viewValue !== value) { + ctrl.$viewValue = value; + ctrl.$render(); + } + } + + return value; + }); +}]; + + +/** + * @ngdoc directive + * @name ng.directive:ngModel + * + * @element input + * + * @description + * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a + * property on the scope using {@link ng.directive:ngModel.NgModelController NgModelController}, + * which is created and exposed by this directive. + * + * `ngModel` is responsible for: + * + * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` + * require. + * - Providing validation behavior (i.e. required, number, email, url). + * - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors). + * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`). + * - Registering the control with its parent {@link ng.directive:form form}. + * + * Note: `ngModel` will try to bind to the property given by evaluating the expression on the + * current scope. If the property doesn't already exist on this scope, it will be created + * implicitly and added to the scope. + * + * For best practices on using `ngModel`, see: + * + * - {@link https://github.com/angular/angular.js/wiki/Understanding-Scopes} + * + * For basic examples, how to use `ngModel`, see: + * + * - {@link ng.directive:input input} + * - {@link ng.directive:input.text text} + * - {@link ng.directive:input.checkbox checkbox} + * - {@link ng.directive:input.radio radio} + * - {@link ng.directive:input.number number} + * - {@link ng.directive:input.email email} + * - {@link ng.directive:input.url url} + * - {@link ng.directive:select select} + * - {@link ng.directive:textarea textarea} + * + */ +var ngModelDirective = function() { + return { + require: ['ngModel', '^?form'], + controller: NgModelController, + link: function(scope, element, attr, ctrls) { + // notify others, especially parent forms + + var modelCtrl = ctrls[0], + formCtrl = ctrls[1] || nullFormCtrl; + + formCtrl.$addControl(modelCtrl); + + scope.$on('$destroy', function() { + formCtrl.$removeControl(modelCtrl); + }); + } + }; +}; + + +/** + * @ngdoc directive + * @name ng.directive:ngChange + * + * @description + * Evaluate given expression when user changes the input. + * The expression is not evaluated when the value change is coming from the model. + * + * Note, this directive requires `ngModel` to be present. + * + * @element input + * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change + * in input value. + * + * @example + * + * + * + *
      + * + * + *
      + * debug = {{confirmed}}
      + * counter = {{counter}} + *
      + *
      + * + * it('should evaluate the expression if changing from view', function() { + * expect(binding('counter')).toEqual('0'); + * element('#ng-change-example1').click(); + * expect(binding('counter')).toEqual('1'); + * expect(binding('confirmed')).toEqual('true'); + * }); + * + * it('should not evaluate the expression if changing from model', function() { + * element('#ng-change-example2').click(); + * expect(binding('counter')).toEqual('0'); + * expect(binding('confirmed')).toEqual('true'); + * }); + * + *
      + */ +var ngChangeDirective = valueFn({ + require: 'ngModel', + link: function(scope, element, attr, ctrl) { + ctrl.$viewChangeListeners.push(function() { + scope.$eval(attr.ngChange); + }); + } +}); + + +var requiredDirective = function() { + return { + require: '?ngModel', + link: function(scope, elm, attr, ctrl) { + if (!ctrl) return; + attr.required = true; // force truthy in case we are on non input element + + var validator = function(value) { + if (attr.required && ctrl.$isEmpty(value)) { + ctrl.$setValidity('required', false); + return; + } else { + ctrl.$setValidity('required', true); + return value; + } + }; + + ctrl.$formatters.push(validator); + ctrl.$parsers.unshift(validator); + + attr.$observe('required', function() { + validator(ctrl.$viewValue); + }); + } + }; +}; + + +/** + * @ngdoc directive + * @name ng.directive:ngList + * + * @description + * Text input that converts between a delimited string and an array of strings. The delimiter + * can be a fixed string (by default a comma) or a regular expression. + * + * @element input + * @param {string=} ngList optional delimiter that should be used to split the value. If + * specified in form `/something/` then the value will be converted into a regular expression. + * + * @example + + + +
      + List: + + Required! +
      + names = {{names}}
      + myForm.namesInput.$valid = {{myForm.namesInput.$valid}}
      + myForm.namesInput.$error = {{myForm.namesInput.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('names')).toEqual('["igor","misko","vojta"]'); + expect(binding('myForm.namesInput.$valid')).toEqual('true'); + expect(element('span.error').css('display')).toBe('none'); + }); + + it('should be invalid if empty', function() { + input('names').enter(''); + expect(binding('names')).toEqual(''); + expect(binding('myForm.namesInput.$valid')).toEqual('false'); + expect(element('span.error').css('display')).not().toBe('none'); + }); + +
      + */ +var ngListDirective = function() { + return { + require: 'ngModel', + link: function(scope, element, attr, ctrl) { + var match = /\/(.*)\//.exec(attr.ngList), + separator = match && new RegExp(match[1]) || attr.ngList || ','; + + var parse = function(viewValue) { + // If the viewValue is invalid (say required but empty) it will be `undefined` + if (isUndefined(viewValue)) return; + + var list = []; + + if (viewValue) { + forEach(viewValue.split(separator), function(value) { + if (value) list.push(trim(value)); + }); + } + + return list; + }; + + ctrl.$parsers.push(parse); + ctrl.$formatters.push(function(value) { + if (isArray(value)) { + return value.join(', '); + } + + return undefined; + }); + + // Override the standard $isEmpty because an empty array means the input is empty. + ctrl.$isEmpty = function(value) { + return !value || !value.length; + }; + } + }; +}; + + +var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; +/** + * @ngdoc directive + * @name ng.directive:ngValue + * + * @description + * Binds the given expression to the value of `input[select]` or `input[radio]`, so + * that when the element is selected, the `ngModel` of that element is set to the + * bound value. + * + * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as + * shown below. + * + * @element input + * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute + * of the `input` element + * + * @example + + + +
      +

      Which is your favorite?

      + +
      You chose {{my.favorite}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('my.favorite')).toEqual('unicorns'); + }); + it('should bind the values to the inputs', function() { + input('my.favorite').select('pizza'); + expect(binding('my.favorite')).toEqual('pizza'); + }); + +
      + */ +var ngValueDirective = function() { + return { + priority: 100, + compile: function(tpl, tplAttr) { + if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { + return function ngValueConstantLink(scope, elm, attr) { + attr.$set('value', scope.$eval(attr.ngValue)); + }; + } else { + return function ngValueLink(scope, elm, attr) { + scope.$watch(attr.ngValue, function valueWatchAction(value) { + attr.$set('value', value); + }); + }; + } + } + }; +}; + +/** + * @ngdoc directive + * @name ng.directive:ngBind + * @restrict AC + * + * @description + * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element + * with the value of a given expression, and to update the text content when the value of that + * expression changes. + * + * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like + * `{{ expression }}` which is similar but less verbose. + * + * It is preferrable to use `ngBind` instead of `{{ expression }}` when a template is momentarily + * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an + * element attribute, it makes the bindings invisible to the user while the page is loading. + * + * An alternative solution to this problem would be using the + * {@link ng.directive:ngCloak ngCloak} directive. + * + * + * @element ANY + * @param {expression} ngBind {@link guide/expression Expression} to evaluate. + * + * @example + * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. + + + +
      + Enter name:
      + Hello ! +
      +
      + + it('should check ng-bind', function() { + expect(using('.doc-example-live').binding('name')).toBe('Whirled'); + using('.doc-example-live').input('name').enter('world'); + expect(using('.doc-example-live').binding('name')).toBe('world'); + }); + +
      + */ +var ngBindDirective = ngDirective(function(scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.ngBind); + scope.$watch(attr.ngBind, function ngBindWatchAction(value) { + // We are purposefully using == here rather than === because we want to + // catch when value is "null or undefined" + // jshint -W041 + element.text(value == undefined ? '' : value); + }); +}); + + +/** + * @ngdoc directive + * @name ng.directive:ngBindTemplate + * + * @description + * The `ngBindTemplate` directive specifies that the element + * text content should be replaced with the interpolation of the template + * in the `ngBindTemplate` attribute. + * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` + * expressions. This directive is needed since some HTML elements + * (such as TITLE and OPTION) cannot contain SPAN elements. + * + * @element ANY + * @param {string} ngBindTemplate template of form + * {{ expression }} to eval. + * + * @example + * Try it here: enter text in text box and watch the greeting change. + + + +
      + Salutation:
      + Name:
      +
      
      +       
      +
      + + it('should check ng-bind', function() { + expect(using('.doc-example-live').binding('salutation')). + toBe('Hello'); + expect(using('.doc-example-live').binding('name')). + toBe('World'); + using('.doc-example-live').input('salutation').enter('Greetings'); + using('.doc-example-live').input('name').enter('user'); + expect(using('.doc-example-live').binding('salutation')). + toBe('Greetings'); + expect(using('.doc-example-live').binding('name')). + toBe('user'); + }); + +
      + */ +var ngBindTemplateDirective = ['$interpolate', function($interpolate) { + return function(scope, element, attr) { + // TODO: move this to scenario runner + var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); + element.addClass('ng-binding').data('$binding', interpolateFn); + attr.$observe('ngBindTemplate', function(value) { + element.text(value); + }); + }; +}]; + + +/** + * @ngdoc directive + * @name ng.directive:ngBindHtml + * + * @description + * Creates a binding that will innerHTML the result of evaluating the `expression` into the current + * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link + * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` + * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in + * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to + * an explicitly trusted value via {@link ng.$sce#methods_trustAsHtml $sce.trustAsHtml}. See the example + * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. + * + * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you + * will have an exception (instead of an exploit.) + * + * @element ANY + * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. + * + * @example + Try it here: enter text in text box and watch the greeting change. + + + +
      +

      +
      +
      + + + angular.module('ngBindHtmlExample', ['ngSanitize']) + + .controller('ngBindHtmlCtrl', ['$scope', function ngBindHtmlCtrl($scope) { + $scope.myHTML = + 'I am an HTMLstring with links! and other stuff'; + }]); + + + + it('should check ng-bind-html', function() { + expect(using('.doc-example-live').binding('myHTML')). + toBe( + 'I am an HTMLstring with links! and other stuff' + ); + }); + +
      + */ +var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) { + return function(scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.ngBindHtml); + + var parsed = $parse(attr.ngBindHtml); + function getStringValue() { return (parsed(scope) || '').toString(); } + + scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) { + element.html($sce.getTrustedHtml(parsed(scope)) || ''); + }); + }; +}]; + +function classDirective(name, selector) { + name = 'ngClass' + name; + return function() { + return { + restrict: 'AC', + link: function(scope, element, attr) { + var oldVal; + + scope.$watch(attr[name], ngClassWatchAction, true); + + attr.$observe('class', function(value) { + ngClassWatchAction(scope.$eval(attr[name])); + }); + + + if (name !== 'ngClass') { + scope.$watch('$index', function($index, old$index) { + // jshint bitwise: false + var mod = $index & 1; + if (mod !== old$index & 1) { + var classes = flattenClasses(scope.$eval(attr[name])); + mod === selector ? + attr.$addClass(classes) : + attr.$removeClass(classes); + } + }); + } + + + function ngClassWatchAction(newVal) { + if (selector === true || scope.$index % 2 === selector) { + var newClasses = flattenClasses(newVal || ''); + if(!oldVal) { + attr.$addClass(newClasses); + } else if(!equals(newVal,oldVal)) { + attr.$updateClass(newClasses, flattenClasses(oldVal)); + } + } + oldVal = copy(newVal); + } + + + function flattenClasses(classVal) { + if(isArray(classVal)) { + return classVal.join(' '); + } else if (isObject(classVal)) { + var classes = [], i = 0; + forEach(classVal, function(v, k) { + if (v) { + classes.push(k); + } + }); + return classes.join(' '); + } + + return classVal; + } + } + }; + }; +} + +/** + * @ngdoc directive + * @name ng.directive:ngClass + * @restrict AC + * + * @description + * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding + * an expression that represents all classes to be added. + * + * The directive won't add duplicate classes if a particular class was already set. + * + * When the expression changes, the previously added classes are removed and only then the + * new classes are added. + * + * @animations + * add - happens just before the class is applied to the element + * remove - happens just before the class is removed from the element + * + * @element ANY + * @param {expression} ngClass {@link guide/expression Expression} to eval. The result + * of the evaluation can be a string representing space delimited class + * names, an array, or a map of class names to boolean values. In the case of a map, the + * names of the properties whose values are truthy will be added as css classes to the + * element. + * + * @example Example that demonstrates basic bindings via ngClass directive. + + +

      Map Syntax Example

      + deleted (apply "strike" class)
      + important (apply "bold" class)
      + error (apply "red" class) +
      +

      Using String Syntax

      + +
      +

      Using Array Syntax

      +
      +
      +
      +
      + + .strike { + text-decoration: line-through; + } + .bold { + font-weight: bold; + } + .red { + color: red; + } + + + it('should let you toggle the class', function() { + + expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/bold/); + expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/red/); + + input('important').check(); + expect(element('.doc-example-live p:first').prop('className')).toMatch(/bold/); + + input('error').check(); + expect(element('.doc-example-live p:first').prop('className')).toMatch(/red/); + }); + + it('should let you toggle string example', function() { + expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe(''); + input('style').enter('red'); + expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe('red'); + }); + + it('array example should have 3 classes', function() { + expect(element('.doc-example-live p:last').prop('className')).toBe(''); + input('style1').enter('bold'); + input('style2').enter('strike'); + input('style3').enter('red'); + expect(element('.doc-example-live p:last').prop('className')).toBe('bold strike red'); + }); + +
      + + ## Animations + + The example below demonstrates how to perform animations using ngClass. + + + + + +
      + Sample Text +
      + + .base-class { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + } + + .base-class.my-class { + color: red; + font-size:3em; + } + + + it('should check ng-class', function() { + expect(element('.doc-example-live span').prop('className')).not(). + toMatch(/my-class/); + + using('.doc-example-live').element(':button:first').click(); + + expect(element('.doc-example-live span').prop('className')). + toMatch(/my-class/); + + using('.doc-example-live').element(':button:last').click(); + + expect(element('.doc-example-live span').prop('className')).not(). + toMatch(/my-class/); + }); + +
      + + + ## ngClass and pre-existing CSS3 Transitions/Animations + The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. + Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder + any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure + to view the step by step details of {@link ngAnimate.$animate#methods_addclass $animate.addClass} and + {@link ngAnimate.$animate#methods_removeclass $animate.removeClass}. + */ +var ngClassDirective = classDirective('', true); + +/** + * @ngdoc directive + * @name ng.directive:ngClassOdd + * @restrict AC + * + * @description + * The `ngClassOdd` and `ngClassEven` directives work exactly as + * {@link ng.directive:ngClass ngClass}, except they work in + * conjunction with `ngRepeat` and take effect only on odd (even) rows. + * + * This directive can be applied only within the scope of an + * {@link ng.directive:ngRepeat ngRepeat}. + * + * @element ANY + * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result + * of the evaluation can be a string representing space delimited class names or an array. + * + * @example + + +
        +
      1. + + {{name}} + +
      2. +
      +
      + + .odd { + color: red; + } + .even { + color: blue; + } + + + it('should check ng-class-odd and ng-class-even', function() { + expect(element('.doc-example-live li:first span').prop('className')). + toMatch(/odd/); + expect(element('.doc-example-live li:last span').prop('className')). + toMatch(/even/); + }); + +
      + */ +var ngClassOddDirective = classDirective('Odd', 0); + +/** + * @ngdoc directive + * @name ng.directive:ngClassEven + * @restrict AC + * + * @description + * The `ngClassOdd` and `ngClassEven` directives work exactly as + * {@link ng.directive:ngClass ngClass}, except they work in + * conjunction with `ngRepeat` and take effect only on odd (even) rows. + * + * This directive can be applied only within the scope of an + * {@link ng.directive:ngRepeat ngRepeat}. + * + * @element ANY + * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The + * result of the evaluation can be a string representing space delimited class names or an array. + * + * @example + + +
        +
      1. + + {{name}}       + +
      2. +
      +
      + + .odd { + color: red; + } + .even { + color: blue; + } + + + it('should check ng-class-odd and ng-class-even', function() { + expect(element('.doc-example-live li:first span').prop('className')). + toMatch(/odd/); + expect(element('.doc-example-live li:last span').prop('className')). + toMatch(/even/); + }); + +
      + */ +var ngClassEvenDirective = classDirective('Even', 1); + +/** + * @ngdoc directive + * @name ng.directive:ngCloak + * @restrict AC + * + * @description + * The `ngCloak` directive is used to prevent the Angular html template from being briefly + * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this + * directive to avoid the undesirable flicker effect caused by the html template display. + * + * The directive can be applied to the `` element, but the preferred usage is to apply + * multiple `ngCloak` directives to small portions of the page to permit progressive rendering + * of the browser view. + * + * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and + * `angular.min.js`. + * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). + * + *
      + * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
      + *   display: none !important;
      + * }
      + * 
      + * + * When this css rule is loaded by the browser, all html elements (including their children) that + * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive + * during the compilation of the template it deletes the `ngCloak` element attribute, making + * the compiled element visible. + * + * For the best result, the `angular.js` script must be loaded in the head section of the html + * document; alternatively, the css rule above must be included in the external stylesheet of the + * application. + * + * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they + * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css + * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below. + * + * @element ANY + * + * @example + + +
      {{ 'hello' }}
      +
      {{ 'hello IE7' }}
      +
      + + it('should remove the template directive and css class', function() { + expect(element('.doc-example-live #template1').attr('ng-cloak')). + not().toBeDefined(); + expect(element('.doc-example-live #template2').attr('ng-cloak')). + not().toBeDefined(); + }); + +
      + * + */ +var ngCloakDirective = ngDirective({ + compile: function(element, attr) { + attr.$set('ngCloak', undefined); + element.removeClass('ng-cloak'); + } +}); + +/** + * @ngdoc directive + * @name ng.directive:ngController + * + * @description + * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular + * supports the principles behind the Model-View-Controller design pattern. + * + * MVC components in angular: + * + * * Model — The Model is scope properties; scopes are attached to the DOM where scope properties + * are accessed through bindings. + * * View — The template (HTML with data bindings) that is rendered into the View. + * * Controller — The `ngController` directive specifies a Controller class; the class contains business + * logic behind the application to decorate the scope with functions and values + * + * Note that you can also attach controllers to the DOM by declaring it in a route definition + * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller + * again using `ng-controller` in the template itself. This will cause the controller to be attached + * and executed twice. + * + * @element ANY + * @scope + * @param {expression} ngController Name of a globally accessible constructor function or an + * {@link guide/expression expression} that on the current scope evaluates to a + * constructor function. The controller instance can be published into a scope property + * by specifying `as propertyName`. + * + * @example + * Here is a simple form for editing user contact information. Adding, removing, clearing, and + * greeting are methods declared on the controller (see source tab). These methods can + * easily be called from the angular markup. Notice that the scope becomes the `this` for the + * controller's instance. This allows for easy access to the view data from the controller. Also + * notice that any changes to the data are automatically reflected in the View without the need + * for a manual update. The example is shown in two different declaration styles you may use + * according to preference. + + + +
      + Name: + [ greet ]
      + Contact: +
        +
      • + + + [ clear + | X ] +
      • +
      • [ add ]
      • +
      +
      +
      + + it('should check controller as', function() { + expect(element('#ctrl-as-exmpl>:input').val()).toBe('John Smith'); + expect(element('#ctrl-as-exmpl li:nth-child(1) input').val()) + .toBe('408 555 1212'); + expect(element('#ctrl-as-exmpl li:nth-child(2) input').val()) + .toBe('john.smith@example.org'); + + element('#ctrl-as-exmpl li:first a:contains("clear")').click(); + expect(element('#ctrl-as-exmpl li:first input').val()).toBe(''); + + element('#ctrl-as-exmpl li:last a:contains("add")').click(); + expect(element('#ctrl-as-exmpl li:nth-child(3) input').val()) + .toBe('yourname@example.org'); + }); + +
      + + + +
      + Name: + [ greet ]
      + Contact: +
        +
      • + + + [ clear + | X ] +
      • +
      • [ add ]
      • +
      +
      +
      + + it('should check controller', function() { + expect(element('#ctrl-exmpl>:input').val()).toBe('John Smith'); + expect(element('#ctrl-exmpl li:nth-child(1) input').val()) + .toBe('408 555 1212'); + expect(element('#ctrl-exmpl li:nth-child(2) input').val()) + .toBe('john.smith@example.org'); + + element('#ctrl-exmpl li:first a:contains("clear")').click(); + expect(element('#ctrl-exmpl li:first input').val()).toBe(''); + + element('#ctrl-exmpl li:last a:contains("add")').click(); + expect(element('#ctrl-exmpl li:nth-child(3) input').val()) + .toBe('yourname@example.org'); + }); + +
      + + */ +var ngControllerDirective = [function() { + return { + scope: true, + controller: '@', + priority: 500 + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngCsp + * + * @element html + * @description + * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. + * + * This is necessary when developing things like Google Chrome Extensions. + * + * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). + * For us to be compatible, we just need to implement the "getterFn" in $parse without violating + * any of these restrictions. + * + * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` + * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will + * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will + * be raised. + * + * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically + * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). + * To make those directives work in CSP mode, include the `angular-csp.css` manually. + * + * In order to use this feature put the `ngCsp` directive on the root element of the application. + * + * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* + * + * @example + * This example shows how to apply the `ngCsp` directive to the `html` tag. +
      +     
      +     
      +     ...
      +     ...
      +     
      +   
      + */ + +// ngCsp is not implemented as a proper directive any more, because we need it be processed while we bootstrap +// the system (before $parse is instantiated), for this reason we just have a csp() fn that looks for ng-csp attribute +// anywhere in the current doc + +/** + * @ngdoc directive + * @name ng.directive:ngClick + * + * @description + * The ngClick directive allows you to specify custom behavior when + * an element is clicked. + * + * @element ANY + * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon + * click. (Event object is available as `$event`) + * + * @example + + + + count: {{count}} + + + it('should check ng-click', function() { + expect(element(by.binding('count')).getText()).toMatch('0'); + element(by.css('.doc-example-live button')).click(); + expect(element(by.binding('count')).getText()).toMatch('1'); + }); + + + */ +/* + * A directive that allows creation of custom onclick handlers that are defined as angular + * expressions and are compiled and executed within the current scope. + * + * Events that are handled via these handler are always configured not to propagate further. + */ +var ngEventDirectives = {}; +forEach( + 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), + function(name) { + var directiveName = directiveNormalize('ng-' + name); + ngEventDirectives[directiveName] = ['$parse', function($parse) { + return { + compile: function($element, attr) { + var fn = $parse(attr[directiveName]); + return function(scope, element, attr) { + element.on(lowercase(name), function(event) { + scope.$apply(function() { + fn(scope, {$event:event}); + }); + }); + }; + } + }; + }]; + } +); + +/** + * @ngdoc directive + * @name ng.directive:ngDblclick + * + * @description + * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. + * + * @element ANY + * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon + * a dblclick. (The Event object is available as `$event`) + * + * @example + + + + count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMousedown + * + * @description + * The ngMousedown directive allows you to specify custom behavior on mousedown event. + * + * @element ANY + * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon + * mousedown. (Event object is available as `$event`) + * + * @example + + + + count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMouseup + * + * @description + * Specify custom behavior on mouseup event. + * + * @element ANY + * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon + * mouseup. (Event object is available as `$event`) + * + * @example + + + + count: {{count}} + + + */ + +/** + * @ngdoc directive + * @name ng.directive:ngMouseover + * + * @description + * Specify custom behavior on mouseover event. + * + * @element ANY + * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon + * mouseover. (Event object is available as `$event`) + * + * @example + + + + count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMouseenter + * + * @description + * Specify custom behavior on mouseenter event. + * + * @element ANY + * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon + * mouseenter. (Event object is available as `$event`) + * + * @example + + + + count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMouseleave + * + * @description + * Specify custom behavior on mouseleave event. + * + * @element ANY + * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon + * mouseleave. (Event object is available as `$event`) + * + * @example + + + + count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMousemove + * + * @description + * Specify custom behavior on mousemove event. + * + * @element ANY + * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon + * mousemove. (Event object is available as `$event`) + * + * @example + + + + count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngKeydown + * + * @description + * Specify custom behavior on keydown event. + * + * @element ANY + * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon + * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) + * + * @example + + + + key down count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngKeyup + * + * @description + * Specify custom behavior on keyup event. + * + * @element ANY + * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon + * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) + * + * @example + + + + key up count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngKeypress + * + * @description + * Specify custom behavior on keypress event. + * + * @element ANY + * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon + * keypress. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) + * + * @example + + + + key press count: {{count}} + + + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngSubmit + * + * @description + * Enables binding angular expressions to onsubmit events. + * + * Additionally it prevents the default action (which for form means sending the request to the + * server and reloading the current page) **but only if the form does not contain an `action` + * attribute**. + * + * @element form + * @param {expression} ngSubmit {@link guide/expression Expression} to eval. (Event object is available as `$event`) + * + * @example + + + +
      + Enter text and hit enter: + + +
      list={{list}}
      +
      +
      + + it('should check ng-submit', function() { + expect(binding('list')).toBe('[]'); + element('.doc-example-live #submit').click(); + expect(binding('list')).toBe('["hello"]'); + expect(input('text').val()).toBe(''); + }); + it('should ignore empty strings', function() { + expect(binding('list')).toBe('[]'); + element('.doc-example-live #submit').click(); + element('.doc-example-live #submit').click(); + expect(binding('list')).toBe('["hello"]'); + }); + +
      + */ + +/** + * @ngdoc directive + * @name ng.directive:ngFocus + * + * @description + * Specify custom behavior on focus event. + * + * @element window, input, select, textarea, a + * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon + * focus. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + +/** + * @ngdoc directive + * @name ng.directive:ngBlur + * + * @description + * Specify custom behavior on blur event. + * + * @element window, input, select, textarea, a + * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon + * blur. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + +/** + * @ngdoc directive + * @name ng.directive:ngCopy + * + * @description + * Specify custom behavior on copy event. + * + * @element window, input, select, textarea, a + * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon + * copy. (Event object is available as `$event`) + * + * @example + + + + copied: {{copied}} + + + */ + +/** + * @ngdoc directive + * @name ng.directive:ngCut + * + * @description + * Specify custom behavior on cut event. + * + * @element window, input, select, textarea, a + * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon + * cut. (Event object is available as `$event`) + * + * @example + + + + cut: {{cut}} + + + */ + +/** + * @ngdoc directive + * @name ng.directive:ngPaste + * + * @description + * Specify custom behavior on paste event. + * + * @element window, input, select, textarea, a + * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon + * paste. (Event object is available as `$event`) + * + * @example + + + + pasted: {{paste}} + + + */ + +/** + * @ngdoc directive + * @name ng.directive:ngIf + * @restrict A + * + * @description + * The `ngIf` directive removes or recreates a portion of the DOM tree based on an + * {expression}. If the expression assigned to `ngIf` evaluates to a false + * value then the element is removed from the DOM, otherwise a clone of the + * element is reinserted into the DOM. + * + * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the + * element in the DOM rather than changing its visibility via the `display` css property. A common + * case when this difference is significant is when using css selectors that rely on an element's + * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes. + * + * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope + * is created when the element is restored. The scope created within `ngIf` inherits from + * its parent scope using + * {@link https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance prototypal inheritance}. + * An important implication of this is if `ngModel` is used within `ngIf` to bind to + * a javascript primitive defined in the parent scope. In this case any modifications made to the + * variable within the child scope will override (hide) the value in the parent scope. + * + * Also, `ngIf` recreates elements using their compiled state. An example of this behavior + * is if an element's class attribute is directly modified after it's compiled, using something like + * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element + * the added class will be lost because the original compiled state is used to regenerate the element. + * + * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` + * and `leave` effects. + * + * @animations + * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container + * leave - happens just before the ngIf contents are removed from the DOM + * + * @element ANY + * @scope + * @priority 600 + * @param {expression} ngIf If the {@link guide/expression expression} is falsy then + * the element is removed from the DOM tree. If it is truthy a copy of the compiled + * element is added to the DOM tree. + * + * @example + + + Click me:
      + Show when checked: + + I'm removed when the checkbox is unchecked. + +
      + + .animate-if { + background:white; + border:1px solid black; + padding:10px; + } + + .animate-if.ng-enter, .animate-if.ng-leave { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + } + + .animate-if.ng-enter, + .animate-if.ng-leave.ng-leave-active { + opacity:0; + } + + .animate-if.ng-leave, + .animate-if.ng-enter.ng-enter-active { + opacity:1; + } + +
      + */ +var ngIfDirective = ['$animate', function($animate) { + return { + transclude: 'element', + priority: 600, + terminal: true, + restrict: 'A', + $$tlb: true, + link: function ($scope, $element, $attr, ctrl, $transclude) { + var block, childScope; + $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { + + if (toBoolean(value)) { + if (!childScope) { + childScope = $scope.$new(); + $transclude(childScope, function (clone) { + clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); + // Note: We only need the first/last node of the cloned nodes. + // However, we need to keep the reference to the jqlite wrapper as it might be changed later + // by a directive with templateUrl when it's template arrives. + block = { + clone: clone + }; + $animate.enter(clone, $element.parent(), $element); + }); + } + } else { + + if (childScope) { + childScope.$destroy(); + childScope = null; + } + + if (block) { + $animate.leave(getBlockElements(block.clone)); + block = null; + } + } + }); + } + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngInclude + * @restrict ECA + * + * @description + * Fetches, compiles and includes an external HTML fragment. + * + * By default, the template URL is restricted to the same domain and protocol as the + * application document. This is done by calling {@link ng.$sce#methods_getTrustedResourceUrl + * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols + * you may either {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelist them} or + * {@link ng.$sce#methods_trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link + * ng.$sce Strict Contextual Escaping}. + * + * In addition, the browser's + * {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest + * Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing + * (CORS)} policy may further restrict whether the template is successfully loaded. + * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` + * access on some browsers. + * + * @animations + * enter - animation is used to bring new content into the browser. + * leave - animation is used to animate existing content away. + * + * The enter and leave animation occur concurrently. + * + * @scope + * @priority 400 + * + * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, + * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`. + * @param {string=} onload Expression to evaluate when a new partial is loaded. + * + * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll + * $anchorScroll} to scroll the viewport after the content is loaded. + * + * - If the attribute is not set, disable scrolling. + * - If the attribute is set without value, enable scrolling. + * - Otherwise enable scrolling only if the expression evaluates to truthy value. + * + * @example + + +
      + + url of the template: {{template.url}} +
      +
      +
      +
      +
      +
      + + function Ctrl($scope) { + $scope.templates = + [ { name: 'template1.html', url: 'template1.html'} + , { name: 'template2.html', url: 'template2.html'} ]; + $scope.template = $scope.templates[0]; + } + + + Content of template1.html + + + Content of template2.html + + + .slide-animate-container { + position:relative; + background:white; + border:1px solid black; + height:40px; + overflow:hidden; + } + + .slide-animate { + padding:10px; + } + + .slide-animate.ng-enter, .slide-animate.ng-leave { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + display:block; + padding:10px; + } + + .slide-animate.ng-enter { + top:-50px; + } + .slide-animate.ng-enter.ng-enter-active { + top:0; + } + + .slide-animate.ng-leave { + top:0; + } + .slide-animate.ng-leave.ng-leave-active { + top:50px; + } + + + it('should load template1.html', function() { + expect(element('.doc-example-live [ng-include]').text()). + toMatch(/Content of template1.html/); + }); + it('should load template2.html', function() { + select('template').option('1'); + expect(element('.doc-example-live [ng-include]').text()). + toMatch(/Content of template2.html/); + }); + it('should change to blank', function() { + select('template').option(''); + expect(element('.doc-example-live [ng-include]')).toBe(undefined); + }); + +
      + */ + + +/** + * @ngdoc event + * @name ng.directive:ngInclude#$includeContentRequested + * @eventOf ng.directive:ngInclude + * @eventType emit on the scope ngInclude was declared in + * @description + * Emitted every time the ngInclude content is requested. + */ + + +/** + * @ngdoc event + * @name ng.directive:ngInclude#$includeContentLoaded + * @eventOf ng.directive:ngInclude + * @eventType emit on the current ngInclude scope + * @description + * Emitted every time the ngInclude content is reloaded. + */ +var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate', '$sce', + function($http, $templateCache, $anchorScroll, $animate, $sce) { + return { + restrict: 'ECA', + priority: 400, + terminal: true, + transclude: 'element', + controller: angular.noop, + compile: function(element, attr) { + var srcExp = attr.ngInclude || attr.src, + onloadExp = attr.onload || '', + autoScrollExp = attr.autoscroll; + + return function(scope, $element, $attr, ctrl, $transclude) { + var changeCounter = 0, + currentScope, + currentElement; + + var cleanupLastIncludeContent = function() { + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + if(currentElement) { + $animate.leave(currentElement); + currentElement = null; + } + }; + + scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { + var afterAnimation = function() { + if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { + $anchorScroll(); + } + }; + var thisChangeId = ++changeCounter; + + if (src) { + $http.get(src, {cache: $templateCache}).success(function(response) { + if (thisChangeId !== changeCounter) return; + var newScope = scope.$new(); + ctrl.template = response; + + // Note: This will also link all children of ng-include that were contained in the original + // html. If that content contains controllers, ... they could pollute/change the scope. + // However, using ng-include on an element with additional content does not make sense... + // Note: We can't remove them in the cloneAttchFn of $transclude as that + // function is called before linking the content, which would apply child + // directives to non existing elements. + var clone = $transclude(newScope, function(clone) { + cleanupLastIncludeContent(); + $animate.enter(clone, null, $element, afterAnimation); + }); + + currentScope = newScope; + currentElement = clone; + + currentScope.$emit('$includeContentLoaded'); + scope.$eval(onloadExp); + }).error(function() { + if (thisChangeId === changeCounter) cleanupLastIncludeContent(); + }); + scope.$emit('$includeContentRequested'); + } else { + cleanupLastIncludeContent(); + ctrl.template = null; + } + }); + }; + } + }; +}]; + +// This directive is called during the $transclude call of the first `ngInclude` directive. +// It will replace and compile the content of the element with the loaded template. +// We need this directive so that the element content is already filled when +// the link function of another directive on the same element as ngInclude +// is called. +var ngIncludeFillContentDirective = ['$compile', + function($compile) { + return { + restrict: 'ECA', + priority: -400, + require: 'ngInclude', + link: function(scope, $element, $attr, ctrl) { + $element.html(ctrl.template); + $compile($element.contents())(scope); + } + }; + }]; + +/** + * @ngdoc directive + * @name ng.directive:ngInit + * @restrict AC + * + * @description + * The `ngInit` directive allows you to evaluate an expression in the + * current scope. + * + *
      + * The only appropriate use of `ngInit` is for aliasing special properties of + * {@link api/ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you + * should use {@link guide/controller controllers} rather than `ngInit` + * to initialize values on a scope. + *
      + * + * @priority 450 + * + * @element ANY + * @param {expression} ngInit {@link guide/expression Expression} to eval. + * + * @example + + + +
      +
      +
      + list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}}; +
      +
      +
      +
      + + it('should alias index positions', function() { + expect(element('.example-init').text()) + .toBe('list[ 0 ][ 0 ] = a;' + + 'list[ 0 ][ 1 ] = b;' + + 'list[ 1 ][ 0 ] = c;' + + 'list[ 1 ][ 1 ] = d;'); + }); + +
      + */ +var ngInitDirective = ngDirective({ + priority: 450, + compile: function() { + return { + pre: function(scope, element, attrs) { + scope.$eval(attrs.ngInit); + } + }; + } +}); + +/** + * @ngdoc directive + * @name ng.directive:ngNonBindable + * @restrict AC + * @priority 1000 + * + * @description + * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current + * DOM element. This is useful if the element contains what appears to be Angular directives and + * bindings but which should be ignored by Angular. This could be the case if you have a site that + * displays snippets of code, for instance. + * + * @element ANY + * + * @example + * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, + * but the one wrapped in `ngNonBindable` is left alone. + * + * @example + + +
      Normal: {{1 + 2}}
      +
      Ignored: {{1 + 2}}
      +
      + + it('should check ng-non-bindable', function() { + expect(using('.doc-example-live').binding('1 + 2')).toBe('3'); + expect(using('.doc-example-live').element('div:last').text()). + toMatch(/1 \+ 2/); + }); + +
      + */ +var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); + +/** + * @ngdoc directive + * @name ng.directive:ngPluralize + * @restrict EA + * + * @description + * # Overview + * `ngPluralize` is a directive that displays messages according to en-US localization rules. + * These rules are bundled with angular.js, but can be overridden + * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive + * by specifying the mappings between + * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * plural categories} and the strings to be displayed. + * + * # Plural categories and explicit number rules + * There are two + * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * plural categories} in Angular's default en-US locale: "one" and "other". + * + * While a plural category may match many numbers (for example, in en-US locale, "other" can match + * any number that is not 1), an explicit number rule can only match one number. For example, the + * explicit number rule for "3" matches the number 3. There are examples of plural categories + * and explicit number rules throughout the rest of this documentation. + * + * # Configuring ngPluralize + * You configure ngPluralize by providing 2 attributes: `count` and `when`. + * You can also provide an optional attribute, `offset`. + * + * The value of the `count` attribute can be either a string or an {@link guide/expression + * Angular expression}; these are evaluated on the current scope for its bound value. + * + * The `when` attribute specifies the mappings between plural categories and the actual + * string to be displayed. The value of the attribute should be a JSON object. + * + * The following example shows how to configure ngPluralize: + * + *
      + * 
      + * 
      + *
      + * + * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not + * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" + * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for + * other numbers, for example 12, so that instead of showing "12 people are viewing", you can + * show "a dozen people are viewing". + * + * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted + * into pluralized strings. In the previous example, Angular will replace `{}` with + * `{{personCount}}`. The closed braces `{}` is a placeholder + * for {{numberExpression}}. + * + * # Configuring ngPluralize with offset + * The `offset` attribute allows further customization of pluralized text, which can result in + * a better user experience. For example, instead of the message "4 people are viewing this document", + * you might display "John, Kate and 2 others are viewing this document". + * The offset attribute allows you to offset a number by any desired value. + * Let's take a look at an example: + * + *
      + * 
      + * 
      + * 
      + * + * Notice that we are still using two plural categories(one, other), but we added + * three explicit number rules 0, 1 and 2. + * When one person, perhaps John, views the document, "John is viewing" will be shown. + * When three people view the document, no explicit number rule is found, so + * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. + * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" + * is shown. + * + * Note that when you specify offsets, you must provide explicit number rules for + * numbers from 0 up to and including the offset. If you use an offset of 3, for example, + * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for + * plural categories "one" and "other". + * + * @param {string|expression} count The variable to be bounded to. + * @param {string} when The mapping between plural category to its corresponding strings. + * @param {number=} offset Offset to deduct from the total number. + * + * @example + + + +
      + Person 1:
      + Person 2:
      + Number of People:
      + + + Without Offset: + +
      + + + With Offset(2): + + +
      +
      + + it('should show correct pluralized string', function() { + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('1 person is viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor is viewing.'); + + using('.doc-example-live').input('personCount').enter('0'); + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('Nobody is viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Nobody is viewing.'); + + using('.doc-example-live').input('personCount').enter('2'); + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('2 people are viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor and Misko are viewing.'); + + using('.doc-example-live').input('personCount').enter('3'); + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('3 people are viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor, Misko and one other person are viewing.'); + + using('.doc-example-live').input('personCount').enter('4'); + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('4 people are viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor, Misko and 2 other people are viewing.'); + }); + + it('should show data-binded names', function() { + using('.doc-example-live').input('personCount').enter('4'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor, Misko and 2 other people are viewing.'); + + using('.doc-example-live').input('person1').enter('Di'); + using('.doc-example-live').input('person2').enter('Vojta'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Di, Vojta and 2 other people are viewing.'); + }); + +
      + */ +var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { + var BRACE = /{}/g; + return { + restrict: 'EA', + link: function(scope, element, attr) { + var numberExp = attr.count, + whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs + offset = attr.offset || 0, + whens = scope.$eval(whenExp) || {}, + whensExpFns = {}, + startSymbol = $interpolate.startSymbol(), + endSymbol = $interpolate.endSymbol(), + isWhen = /^when(Minus)?(.+)$/; + + forEach(attr, function(expression, attributeName) { + if (isWhen.test(attributeName)) { + whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = + element.attr(attr.$attr[attributeName]); + } + }); + forEach(whens, function(expression, key) { + whensExpFns[key] = + $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + + offset + endSymbol)); + }); + + scope.$watch(function ngPluralizeWatch() { + var value = parseFloat(scope.$eval(numberExp)); + + if (!isNaN(value)) { + //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, + //check it against pluralization rules in $locale service + if (!(value in whens)) value = $locale.pluralCat(value - offset); + return whensExpFns[value](scope, element, true); + } else { + return ''; + } + }, function ngPluralizeWatchAction(newVal) { + element.text(newVal); + }); + } + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngRepeat + * + * @description + * The `ngRepeat` directive instantiates a template once per item from a collection. Each template + * instance gets its own scope, where the given loop variable is set to the current collection item, + * and `$index` is set to the item index or key. + * + * Special properties are exposed on the local scope of each template instance, including: + * + * | Variable | Type | Details | + * |-----------|-----------------|-----------------------------------------------------------------------------| + * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | + * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | + * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | + * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | + * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | + * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | + * + * Creating aliases for these properties is possible with {@link api/ng.directive:ngInit `ngInit`}. + * This may be useful when, for instance, nesting ngRepeats. + * + * # Special repeat start and end points + * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending + * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. + * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on) + * up to and including the ending HTML tag where **ng-repeat-end** is placed. + * + * The example below makes use of this feature: + *
      + *   
      + * Header {{ item }} + *
      + *
      + * Body {{ item }} + *
      + *
      + * Footer {{ item }} + *
      + *
      + * + * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: + *
      + *   
      + * Header A + *
      + *
      + * Body A + *
      + *
      + * Footer A + *
      + *
      + * Header B + *
      + *
      + * Body B + *
      + *
      + * Footer B + *
      + *
      + * + * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such + * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). + * + * @animations + * enter - when a new item is added to the list or when an item is revealed after a filter + * leave - when an item is removed from the list or when an item is filtered out + * move - when an adjacent item is filtered out causing a reorder or when the item contents are reordered + * + * @element ANY + * @scope + * @priority 1000 + * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These + * formats are currently supported: + * + * * `variable in expression` – where variable is the user defined loop variable and `expression` + * is a scope expression giving the collection to enumerate. + * + * For example: `album in artist.albums`. + * + * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, + * and `expression` is the scope expression giving the collection to enumerate. + * + * For example: `(name, age) in {'adam':10, 'amalie':12}`. + * + * * `variable in expression track by tracking_expression` – You can also provide an optional tracking function + * which can be used to associate the objects in the collection with the DOM elements. If no tracking function + * is specified the ng-repeat associates elements by identity in the collection. It is an error to have + * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are + * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, + * before specifying a tracking expression. + * + * For example: `item in items` is equivalent to `item in items track by $id(item)'. This implies that the DOM elements + * will be associated by item identity in the array. + * + * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique + * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements + * with the corresponding item in the array by identity. Moving the same object in array would move the DOM + * element in the same way in the DOM. + * + * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this + * case the object identity does not matter. Two objects are considered equivalent as long as their `id` + * property is same. + * + * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter + * to items in conjunction with a tracking expression. + * + * @example + * This example initializes the scope to a list of names and + * then uses `ngRepeat` to display every person: + + +
      + I have {{friends.length}} friends. They are: + +
        +
      • + [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. +
      • +
      +
      +
      + + .example-animate-container { + background:white; + border:1px solid black; + list-style:none; + margin:0; + padding:0 10px; + } + + .animate-repeat { + line-height:40px; + list-style:none; + box-sizing:border-box; + } + + .animate-repeat.ng-move, + .animate-repeat.ng-enter, + .animate-repeat.ng-leave { + -webkit-transition:all linear 0.5s; + transition:all linear 0.5s; + } + + .animate-repeat.ng-leave.ng-leave-active, + .animate-repeat.ng-move, + .animate-repeat.ng-enter { + opacity:0; + max-height:0; + } + + .animate-repeat.ng-leave, + .animate-repeat.ng-move.ng-move-active, + .animate-repeat.ng-enter.ng-enter-active { + opacity:1; + max-height:40px; + } + + + it('should render initial data set', function() { + var r = using('.doc-example-live').repeater('ul li'); + expect(r.count()).toBe(10); + expect(r.row(0)).toEqual(["1","John","25"]); + expect(r.row(1)).toEqual(["2","Jessie","30"]); + expect(r.row(9)).toEqual(["10","Samantha","60"]); + expect(binding('friends.length')).toBe("10"); + }); + + it('should update repeater when filter predicate changes', function() { + var r = using('.doc-example-live').repeater('ul li'); + expect(r.count()).toBe(10); + + input('q').enter('ma'); + + expect(r.count()).toBe(2); + expect(r.row(0)).toEqual(["1","Mary","28"]); + expect(r.row(1)).toEqual(["2","Samantha","60"]); + }); + +
      + */ +var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { + var NG_REMOVED = '$$NG_REMOVED'; + var ngRepeatMinErr = minErr('ngRepeat'); + return { + transclude: 'element', + priority: 1000, + terminal: true, + $$tlb: true, + link: function($scope, $element, $attr, ctrl, $transclude){ + var expression = $attr.ngRepeat; + var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), + trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, + lhs, rhs, valueIdentifier, keyIdentifier, + hashFnLocals = {$id: hashKey}; + + if (!match) { + throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", + expression); + } + + lhs = match[1]; + rhs = match[2]; + trackByExp = match[3]; + + if (trackByExp) { + trackByExpGetter = $parse(trackByExp); + trackByIdExpFn = function(key, value, index) { + // assign key, value, and $index to the locals so that they can be used in hash functions + if (keyIdentifier) hashFnLocals[keyIdentifier] = key; + hashFnLocals[valueIdentifier] = value; + hashFnLocals.$index = index; + return trackByExpGetter($scope, hashFnLocals); + }; + } else { + trackByIdArrayFn = function(key, value) { + return hashKey(value); + }; + trackByIdObjFn = function(key) { + return key; + }; + } + + match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); + if (!match) { + throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", + lhs); + } + valueIdentifier = match[3] || match[1]; + keyIdentifier = match[2]; + + // Store a list of elements from previous run. This is a hash where key is the item from the + // iterator, and the value is objects with following properties. + // - scope: bound scope + // - element: previous element. + // - index: position + var lastBlockMap = {}; + + //watch props + $scope.$watchCollection(rhs, function ngRepeatAction(collection){ + var index, length, + previousNode = $element[0], // current position of the node + nextNode, + // Same as lastBlockMap but it has the current state. It will become the + // lastBlockMap on the next iteration. + nextBlockMap = {}, + arrayLength, + childScope, + key, value, // key/value of iteration + trackById, + trackByIdFn, + collectionKeys, + block, // last object information {scope, element, id} + nextBlockOrder = [], + elementsToRemove; + + + if (isArrayLike(collection)) { + collectionKeys = collection; + trackByIdFn = trackByIdExpFn || trackByIdArrayFn; + } else { + trackByIdFn = trackByIdExpFn || trackByIdObjFn; + // if object, extract keys, sort them and use to determine order of iteration over obj props + collectionKeys = []; + for (key in collection) { + if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { + collectionKeys.push(key); + } + } + collectionKeys.sort(); + } + + arrayLength = collectionKeys.length; + + // locate existing items + length = nextBlockOrder.length = collectionKeys.length; + for(index = 0; index < length; index++) { + key = (collection === collectionKeys) ? index : collectionKeys[index]; + value = collection[key]; + trackById = trackByIdFn(key, value, index); + assertNotHasOwnProperty(trackById, '`track by` id'); + if(lastBlockMap.hasOwnProperty(trackById)) { + block = lastBlockMap[trackById]; + delete lastBlockMap[trackById]; + nextBlockMap[trackById] = block; + nextBlockOrder[index] = block; + } else if (nextBlockMap.hasOwnProperty(trackById)) { + // restore lastBlockMap + forEach(nextBlockOrder, function(block) { + if (block && block.scope) lastBlockMap[block.id] = block; + }); + // This is a duplicate and we need to throw an error + throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}", + expression, trackById); + } else { + // new never before seen block + nextBlockOrder[index] = { id: trackById }; + nextBlockMap[trackById] = false; + } + } + + // remove existing items + for (key in lastBlockMap) { + // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn + if (lastBlockMap.hasOwnProperty(key)) { + block = lastBlockMap[key]; + elementsToRemove = getBlockElements(block.clone); + $animate.leave(elementsToRemove); + forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; }); + block.scope.$destroy(); + } + } + + // we are not using forEach for perf reasons (trying to avoid #call) + for (index = 0, length = collectionKeys.length; index < length; index++) { + key = (collection === collectionKeys) ? index : collectionKeys[index]; + value = collection[key]; + block = nextBlockOrder[index]; + if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]); + + if (block.scope) { + // if we have already seen this object, then we need to reuse the + // associated scope/element + childScope = block.scope; + + nextNode = previousNode; + do { + nextNode = nextNode.nextSibling; + } while(nextNode && nextNode[NG_REMOVED]); + + if (getBlockStart(block) != nextNode) { + // existing item which got moved + $animate.move(getBlockElements(block.clone), null, jqLite(previousNode)); + } + previousNode = getBlockEnd(block); + } else { + // new item which we don't know about + childScope = $scope.$new(); + } + + childScope[valueIdentifier] = value; + if (keyIdentifier) childScope[keyIdentifier] = key; + childScope.$index = index; + childScope.$first = (index === 0); + childScope.$last = (index === (arrayLength - 1)); + childScope.$middle = !(childScope.$first || childScope.$last); + // jshint bitwise: false + childScope.$odd = !(childScope.$even = (index&1) === 0); + // jshint bitwise: true + + if (!block.scope) { + $transclude(childScope, function(clone) { + clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); + $animate.enter(clone, null, jqLite(previousNode)); + previousNode = clone; + block.scope = childScope; + // Note: We only need the first/last node of the cloned nodes. + // However, we need to keep the reference to the jqlite wrapper as it might be changed later + // by a directive with templateUrl when it's template arrives. + block.clone = clone; + nextBlockMap[block.id] = block; + }); + } + } + lastBlockMap = nextBlockMap; + }); + } + }; + + function getBlockStart(block) { + return block.clone[0]; + } + + function getBlockEnd(block) { + return block.clone[block.clone.length - 1]; + } +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngShow + * + * @description + * The `ngShow` directive shows or hides the given HTML element based on the expression + * provided to the ngShow attribute. The element is shown or hidden by removing or adding + * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined + * in AngularJS and sets the display style to none (using an !important flag). + * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). + * + *
      + * 
      + * 
      + * + * + *
      + *
      + * + * When the ngShow expression evaluates to false then the ng-hide CSS class is added to the class attribute + * on the element causing it to become hidden. When true, the ng-hide CSS class is removed + * from the element causing the element not to appear hidden. + * + * ## Why is !important used? + * + * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector + * can be easily overridden by heavier selectors. For example, something as simple + * as changing the display style on a HTML list item would make hidden elements appear visible. + * This also becomes a bigger issue when dealing with CSS frameworks. + * + * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector + * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the + * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. + * + * ### Overriding .ng-hide + * + * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by + * restating the styles for the .ng-hide class in CSS: + *
      + * .ng-hide {
      + *   //!annotate CSS Specificity|Not to worry, this will override the AngularJS default...
      + *   display:block!important;
      + *
      + *   //this is just another form of hiding an element
      + *   position:absolute;
      + *   top:-9999px;
      + *   left:-9999px;
      + * }
      + * 
      + * + * Just remember to include the important flag so the CSS override will function. + * + * ## A note about animations with ngShow + * + * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression + * is true and false. This system works like the animation system present with ngClass except that + * you must also include the !important flag to override the display property + * so that you can perform an animation when the element is hidden during the time of the animation. + * + *
      + * //
      + * //a working example can be found at the bottom of this page
      + * //
      + * .my-element.ng-hide-add, .my-element.ng-hide-remove {
      + *   transition:0.5s linear all;
      + *   display:block!important;
      + * }
      + *
      + * .my-element.ng-hide-add { ... }
      + * .my-element.ng-hide-add.ng-hide-add-active { ... }
      + * .my-element.ng-hide-remove { ... }
      + * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
      + * 
      + * + * @animations + * addClass: .ng-hide - happens after the ngShow expression evaluates to a truthy value and the just before contents are set to visible + * removeClass: .ng-hide - happens after the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden + * + * @element ANY + * @param {expression} ngShow If the {@link guide/expression expression} is truthy + * then the element is shown or hidden respectively. + * + * @example + + + Click me:
      +
      + Show: +
      + I show up when your checkbox is checked. +
      +
      +
      + Hide: +
      + I hide when your checkbox is checked. +
      +
      +
      + + .animate-show { + -webkit-transition:all linear 0.5s; + transition:all linear 0.5s; + line-height:20px; + opacity:1; + padding:10px; + border:1px solid black; + background:white; + } + + .animate-show.ng-hide-add, + .animate-show.ng-hide-remove { + display:block!important; + } + + .animate-show.ng-hide { + line-height:0; + opacity:0; + padding:0 10px; + } + + .check-element { + padding:10px; + border:1px solid black; + background:white; + } + + + it('should check ng-show / ng-hide', function() { + expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); + expect(element('.doc-example-live span:last:visible').count()).toEqual(1); + + input('checked').check(); + + expect(element('.doc-example-live span:first:visible').count()).toEqual(1); + expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); + }); + +
      + */ +var ngShowDirective = ['$animate', function($animate) { + return function(scope, element, attr) { + scope.$watch(attr.ngShow, function ngShowWatchAction(value){ + $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); + }); + }; +}]; + + +/** + * @ngdoc directive + * @name ng.directive:ngHide + * + * @description + * The `ngHide` directive shows or hides the given HTML element based on the expression + * provided to the ngHide attribute. The element is shown or hidden by removing or adding + * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined + * in AngularJS and sets the display style to none (using an !important flag). + * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). + * + *
      + * 
      + * 
      + * + * + *
      + *
      + * + * When the ngHide expression evaluates to true then the .ng-hide CSS class is added to the class attribute + * on the element causing it to become hidden. When false, the ng-hide CSS class is removed + * from the element causing the element not to appear hidden. + * + * ## Why is !important used? + * + * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector + * can be easily overridden by heavier selectors. For example, something as simple + * as changing the display style on a HTML list item would make hidden elements appear visible. + * This also becomes a bigger issue when dealing with CSS frameworks. + * + * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector + * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the + * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. + * + * ### Overriding .ng-hide + * + * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by + * restating the styles for the .ng-hide class in CSS: + *
      + * .ng-hide {
      + *   //!annotate CSS Specificity|Not to worry, this will override the AngularJS default...
      + *   display:block!important;
      + *
      + *   //this is just another form of hiding an element
      + *   position:absolute;
      + *   top:-9999px;
      + *   left:-9999px;
      + * }
      + * 
      + * + * Just remember to include the important flag so the CSS override will function. + * + * ## A note about animations with ngHide + * + * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression + * is true and false. This system works like the animation system present with ngClass, except that + * you must also include the !important flag to override the display property so + * that you can perform an animation when the element is hidden during the time of the animation. + * + *
      + * //
      + * //a working example can be found at the bottom of this page
      + * //
      + * .my-element.ng-hide-add, .my-element.ng-hide-remove {
      + *   transition:0.5s linear all;
      + *   display:block!important;
      + * }
      + *
      + * .my-element.ng-hide-add { ... }
      + * .my-element.ng-hide-add.ng-hide-add-active { ... }
      + * .my-element.ng-hide-remove { ... }
      + * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
      + * 
      + * + * @animations + * removeClass: .ng-hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden + * addClass: .ng-hide - happens after the ngHide expression evaluates to a non truthy value and just before the contents are set to visible + * + * @element ANY + * @param {expression} ngHide If the {@link guide/expression expression} is truthy then + * the element is shown or hidden respectively. + * + * @example + + + Click me:
      +
      + Show: +
      + I show up when your checkbox is checked. +
      +
      +
      + Hide: +
      + I hide when your checkbox is checked. +
      +
      +
      + + .animate-hide { + -webkit-transition:all linear 0.5s; + transition:all linear 0.5s; + line-height:20px; + opacity:1; + padding:10px; + border:1px solid black; + background:white; + } + + .animate-hide.ng-hide-add, + .animate-hide.ng-hide-remove { + display:block!important; + } + + .animate-hide.ng-hide { + line-height:0; + opacity:0; + padding:0 10px; + } + + .check-element { + padding:10px; + border:1px solid black; + background:white; + } + + + it('should check ng-show / ng-hide', function() { + expect(element('.doc-example-live .check-element:first:hidden').count()).toEqual(1); + expect(element('.doc-example-live .check-element:last:visible').count()).toEqual(1); + + input('checked').check(); + + expect(element('.doc-example-live .check-element:first:visible').count()).toEqual(1); + expect(element('.doc-example-live .check-element:last:hidden').count()).toEqual(1); + }); + +
      + */ +var ngHideDirective = ['$animate', function($animate) { + return function(scope, element, attr) { + scope.$watch(attr.ngHide, function ngHideWatchAction(value){ + $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); + }); + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngStyle + * @restrict AC + * + * @description + * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. + * + * @element ANY + * @param {expression} ngStyle {@link guide/expression Expression} which evals to an + * object whose keys are CSS style names and values are corresponding values for those CSS + * keys. + * + * @example + + + + +
      + Sample Text +
      myStyle={{myStyle}}
      +
      + + span { + color: black; + } + + + it('should check ng-style', function() { + expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); + element('.doc-example-live :button[value=set]').click(); + expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)'); + element('.doc-example-live :button[value=clear]').click(); + expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); + }); + +
      + */ +var ngStyleDirective = ngDirective(function(scope, element, attr) { + scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { + if (oldStyles && (newStyles !== oldStyles)) { + forEach(oldStyles, function(val, style) { element.css(style, '');}); + } + if (newStyles) element.css(newStyles); + }, true); +}); + +/** + * @ngdoc directive + * @name ng.directive:ngSwitch + * @restrict EA + * + * @description + * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. + * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location + * as specified in the template. + * + * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it + * from the template cache), `ngSwitch` simply choses one of the nested elements and makes it visible based on which element + * matches the value obtained from the evaluated expression. In other words, you define a container element + * (where you place the directive), place an expression on the **`on="..."` attribute** + * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place + * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on + * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default + * attribute is displayed. + * + *
      + * Be aware that the attribute values to match against cannot be expressions. They are interpreted + * as literal string values to match against. + * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the + * value of the expression `$scope.someVal`. + *
      + + * @animations + * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container + * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM + * + * @usage + * + * ... + * ... + * ... + * + * + * + * @scope + * @priority 800 + * @param {*} ngSwitch|on expression to match against ng-switch-when. + * @paramDescription + * On child elements add: + * + * * `ngSwitchWhen`: the case statement to match against. If match then this + * case will be displayed. If the same match appears multiple times, all the + * elements will be displayed. + * * `ngSwitchDefault`: the default case when no other case match. If there + * are multiple default cases, all of them will be displayed when no other + * case match. + * + * + * @example + + +
      + + selection={{selection}} +
      +
      +
      Settings Div
      +
      Home Span
      +
      default
      +
      +
      +
      + + function Ctrl($scope) { + $scope.items = ['settings', 'home', 'other']; + $scope.selection = $scope.items[0]; + } + + + .animate-switch-container { + position:relative; + background:white; + border:1px solid black; + height:40px; + overflow:hidden; + } + + .animate-switch { + padding:10px; + } + + .animate-switch.ng-animate { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + } + + .animate-switch.ng-leave.ng-leave-active, + .animate-switch.ng-enter { + top:-50px; + } + .animate-switch.ng-leave, + .animate-switch.ng-enter.ng-enter-active { + top:0; + } + + + it('should start in settings', function() { + expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/); + }); + it('should change to home', function() { + select('selection').option('home'); + expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/); + }); + it('should select default', function() { + select('selection').option('other'); + expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/); + }); + +
      + */ +var ngSwitchDirective = ['$animate', function($animate) { + return { + restrict: 'EA', + require: 'ngSwitch', + + // asks for $scope to fool the BC controller module + controller: ['$scope', function ngSwitchController() { + this.cases = {}; + }], + link: function(scope, element, attr, ngSwitchController) { + var watchExpr = attr.ngSwitch || attr.on, + selectedTranscludes, + selectedElements, + selectedScopes = []; + + scope.$watch(watchExpr, function ngSwitchWatchAction(value) { + for (var i= 0, ii=selectedScopes.length; i + + +
      +
      +
      + {{text}} +
      +
      + + it('should have transcluded', function() { + input('title').enter('TITLE'); + input('text').enter('TEXT'); + expect(binding('title')).toEqual('TITLE'); + expect(binding('text')).toEqual('TEXT'); + }); + + + * + */ +var ngTranscludeDirective = ngDirective({ + controller: ['$element', '$transclude', function($element, $transclude) { + if (!$transclude) { + throw minErr('ngTransclude')('orphan', + 'Illegal use of ngTransclude directive in the template! ' + + 'No parent directive that requires a transclusion found. ' + + 'Element: {0}', + startingTag($element)); + } + + // remember the transclusion fn but call it during linking so that we don't process transclusion before directives on + // the parent element even when the transclusion replaces the current element. (we can't use priority here because + // that applies only to compile fns and not controllers + this.$transclude = $transclude; + }], + + link: function($scope, $element, $attrs, controller) { + controller.$transclude(function(clone) { + $element.empty(); + $element.append(clone); + }); + } +}); + +/** + * @ngdoc directive + * @name ng.directive:script + * @restrict E + * + * @description + * Load the content of a ` + + Load inlined template +
      + + + it('should load template defined inside script tag', function() { + element('#tpl-link').click(); + expect(element('#tpl-content').text()).toMatch(/Content of the template/); + }); + + + */ +var scriptDirective = ['$templateCache', function($templateCache) { + return { + restrict: 'E', + terminal: true, + compile: function(element, attr) { + if (attr.type == 'text/ng-template') { + var templateUrl = attr.id, + // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent + text = element[0].text; + + $templateCache.put(templateUrl, text); + } + } + }; +}]; + +var ngOptionsMinErr = minErr('ngOptions'); +/** + * @ngdoc directive + * @name ng.directive:select + * @restrict E + * + * @description + * HTML `SELECT` element with angular data-binding. + * + * # `ngOptions` + * + * The `ngOptions` attribute can be used to dynamically generate a list of `` + * DOM element. + * * `trackexpr`: Used when working with an array of objects. The result of this expression will be + * used to identify the objects in the array. The `trackexpr` will most likely refer to the + * `value` variable (e.g. `value.propertyName`). + * + * @example + + + +
      +
        +
      • + Name: + [X] +
      • +
      • + [add] +
      • +
      +
      + Color (null not allowed): +
      + + Color (null allowed): + + +
      + + Color grouped by shade: +
      + + + Select bogus.
      +
      + Currently selected: {{ {selected_color:color} }} +
      +
      +
      +
      + + it('should check ng-options', function() { + expect(binding('{selected_color:color}')).toMatch('red'); + select('color').option('0'); + expect(binding('{selected_color:color}')).toMatch('black'); + using('.nullable').select('color').option(''); + expect(binding('{selected_color:color}')).toMatch('null'); + }); + +
      + */ + +var ngOptionsDirective = valueFn({ terminal: true }); +// jshint maxlen: false +var selectDirective = ['$compile', '$parse', function($compile, $parse) { + //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 + var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/, + nullModelCtrl = {$setViewValue: noop}; +// jshint maxlen: 100 + + return { + restrict: 'E', + require: ['select', '?ngModel'], + controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { + var self = this, + optionsMap = {}, + ngModelCtrl = nullModelCtrl, + nullOption, + unknownOption; + + + self.databound = $attrs.ngModel; + + + self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { + ngModelCtrl = ngModelCtrl_; + nullOption = nullOption_; + unknownOption = unknownOption_; + }; + + + self.addOption = function(value) { + assertNotHasOwnProperty(value, '"option value"'); + optionsMap[value] = true; + + if (ngModelCtrl.$viewValue == value) { + $element.val(value); + if (unknownOption.parent()) unknownOption.remove(); + } + }; + + + self.removeOption = function(value) { + if (this.hasOption(value)) { + delete optionsMap[value]; + if (ngModelCtrl.$viewValue == value) { + this.renderUnknownOption(value); + } + } + }; + + + self.renderUnknownOption = function(val) { + var unknownVal = '? ' + hashKey(val) + ' ?'; + unknownOption.val(unknownVal); + $element.prepend(unknownOption); + $element.val(unknownVal); + unknownOption.prop('selected', true); // needed for IE + }; + + + self.hasOption = function(value) { + return optionsMap.hasOwnProperty(value); + }; + + $scope.$on('$destroy', function() { + // disable unknown option so that we don't do work when the whole select is being destroyed + self.renderUnknownOption = noop; + }); + }], + + link: function(scope, element, attr, ctrls) { + // if ngModel is not defined, we don't need to do anything + if (!ctrls[1]) return; + + var selectCtrl = ctrls[0], + ngModelCtrl = ctrls[1], + multiple = attr.multiple, + optionsExp = attr.ngOptions, + nullOption = false, // if false, user will not be able to select it (used by ngOptions) + emptyOption, + // we can't just jqLite('

      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 z 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 z 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 XRdGSg1cTiL|8Rn3on7i6JA?fXwd{~& diff --git a/lib/commons-beanutils-bean-collections-1.8.3.jar b/lib/commons-beanutils-bean-collections-1.8.3.jar deleted file mode 100644 index fdffaa1dfc2e5a193298f37cbb5410691c2fe6e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26574 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