├── .gitignore ├── README.md ├── pom.xml ├── scripts └── aggregate_metrics.py ├── src ├── main │ ├── java │ │ └── com │ │ │ └── willvuong │ │ │ └── bootstrapper │ │ │ ├── config │ │ │ ├── AppConfiguration.java │ │ │ └── MetricsConfiguration.java │ │ │ ├── controllers │ │ │ ├── ErrorHandlerController.java │ │ │ └── HomeController.java │ │ │ ├── filter │ │ │ ├── LogbackResponseServletFilter.java │ │ │ └── RequestMDCServletFilter.java │ │ │ ├── mvcconfig │ │ │ ├── GlobalDefaultExceptionHandler.java │ │ │ ├── MvcConfiguration.java │ │ │ └── ServletContextAutoConfigure.java │ │ │ └── util │ │ │ └── ThreadLocalMemoryAppender.java │ ├── resources │ │ └── logback.xml │ └── webapp │ │ ├── WEB-INF │ │ ├── decorators.xml │ │ ├── decorators │ │ │ └── decorator.jsp │ │ ├── views │ │ │ ├── angular-index.jsp │ │ │ ├── error.jsp │ │ │ └── index-async.html │ │ └── web.xml │ │ └── resources │ │ ├── css │ │ ├── .gitignore │ │ └── app.css │ │ ├── img │ │ └── .gitignore │ │ ├── index-async.html │ │ ├── index.html │ │ ├── js │ │ ├── app.js │ │ ├── controllers.js │ │ ├── directives.js │ │ ├── filters.js │ │ └── services.js │ │ ├── lib │ │ ├── angular │ │ │ ├── angular-cookies.js │ │ │ ├── angular-cookies.min.js │ │ │ ├── angular-loader.js │ │ │ ├── angular-loader.min.js │ │ │ ├── angular-resource.js │ │ │ ├── angular-resource.min.js │ │ │ ├── angular-sanitize.js │ │ │ ├── angular-sanitize.min.js │ │ │ ├── angular.js │ │ │ ├── angular.min.js │ │ │ └── version.txt │ │ └── bootstrap │ │ │ ├── css │ │ │ ├── bootstrap-theme.css │ │ │ ├── bootstrap-theme.min.css │ │ │ ├── bootstrap.css │ │ │ └── bootstrap.min.css │ │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ │ │ └── js │ │ │ ├── bootstrap.js │ │ │ └── bootstrap.min.js │ │ └── partials │ │ ├── .gitignore │ │ ├── partial1.html │ │ └── partial2.html └── test │ └── java │ └── com │ └── willvuong │ └── bootstrapper │ └── util │ └── ThreadLocalMemoryAppenderTest.java └── tools └── springloaded-1.1.4.jar /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea 3 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | spring-mvc-bootstrap-angularjs-starter 2 | ====================================== 3 | 4 | The purpose of this is to serve as a project base that has the following things built in: 5 | * Maven project structure and configuration 6 | * Spring MVC and default mappings 7 | * Bootstrap resources and starter page 8 | * AngularJS resources and starter page 9 | * Application defaults like logging, exception handling, and login. 10 | 11 | So clone it and get started. 12 | 13 | Main components 14 | ---------------------- 15 | * Build and tooling via [Maven 3.1+](http://maven.apache.org) 16 | * Servlet 3.x and [Spring Framework 3.2.x](http://docs.spring.io/spring/docs/3.2.5.RELEASE/spring-framework-reference/htmlsingle/) 17 | * [SiteMesh 2.4](http://wiki.sitemesh.org/display/sitemesh/Home) to decorate layouts 18 | * [Bootstrap 3.x](http://getbootstrap.com/) styling and [AngularJS](http://angularjs.org/) 19 | * Logging via [SLF4J](http://www.slf4j.org/) and [Logback](http://logback.qos.ch/) 20 | * Devopsy monitoring things via [Metrics](http://metrics.codahale.com/) 21 | 22 | How it is configured 23 | ---------------------- 24 | Maven is configured to compile for JDK7 so make sure that you have your `JAVA_HOME` configured correctly (you can check this also by running `mvn --version`). 25 | 26 | Let's get this thing running: 27 | 28 | ``` 29 | mvn jetty:run 30 | ``` 31 | 32 | The root of all configuration starts in the `src/main/webapp/WEB-INF/web.xml` file. The web.xml uses the `ContextLoaderListener` to initialize the Spring application context via `@Configuration` annotation scanning (the `contextClass` and `contextConfigLocation` context-params). In this case, the web.xml's `contextConfigLocation` context-param directs the `ContextLoaderListener` to load the main/parent application context in the annotated class `com.willvuong.bootstrapper.config.AppConfiguration`. 33 | 34 | ``` xml 35 | 36 | contextConfigLocation 37 | com.willvuong.bootstrapper.config.AppConfiguration 38 | 39 | ``` 40 | 41 | The `AppConfiguration` class should use `@Import` and/or `@ComponentScan` annotations to import other `@Configuration` classes or annotated `@Component`-ish classes. 42 | 43 | The Spring MVC context is configured via the `DispatcherServlet`'s `contextConfigLocation` init-param value: 44 | 45 | ``` xml 46 | 47 | appServlet 48 | org.springframework.web.servlet.DispatcherServlet 49 | 50 | contextClass 51 | org.springframework.web.context.support.AnnotationConfigWebApplicationContext 52 | 53 | 54 | contextConfigLocation 55 | com.willvuong.bootstrapper.mvcconfig.MvcConfiguration 56 | 57 | 1 58 | true 59 | 60 | ``` 61 | 62 | The `MvcConfiguration` class configures the Spring MVC framework with some "classic" defaults and already has a `@ComponentScan` annotation for mapping annotated `@Controller` classes in the `com.willvuong.bootstrapper.controllers` package. 63 | 64 | Let's talk about the "classic" defaults: 65 | * The `DispatcherServlet` is configured to map to all requests within the servlet context via `/*`. 66 | * `MvcConfiguration` is configured to route all `/resources/**` requests to static resources located in `src/main/webapp/resources`. 67 | * All unmatched requests are finally routed to the [default servlet handler](http://docs.spring.io/spring/docs/3.2.x/javadoc-api/org/springframework/web/servlet/config/annotation/WebMvcConfigurerAdapter.html#configureDefaultServletHandling(org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer)). 68 | 69 | If you paid attention to the console output during `mvn jetty:run`, you might have seen a few lines that begin with "autoconfiguring blah blah...". These log statements originate from the class `com.willvuong.bootstrapper.mvcconfig.ServletContextAutoConfigure` which does some additional configuration of the servlet context. 70 | * A utility filter `com.willvuong.bootstrapper.filter.RequestMDCServletFilter` is added to the servlet context. Its main purpose is to set details from the current `HttpServletRequest` (request URL, request URI, and generate a unique logging ID for the scope of the current request) into the SLF4J MDC. 71 | * Configure SiteMesh to decorate all responses except for static resources in `/resources/**`. SiteMesh is configured by `src/main/webapp/WEB-INF/decorators.xml`. 72 | * Configure Metrics instrumentation and reporting via Metrics servlets. 73 | 74 | Another thing to pay attention to is the Logback configuration (via `src/main/resources/logback.xml`). 75 | * Logback is started in debug mode and will refresh its configuration every 30 seconds. 76 | * It will log to the console in color for readability. (If you are running in a terminal emulator without color, you can disable colors like `mvn jetty:run -Dnocolor=true`.) 77 | * Log messages are very detailed and include the current request URI and request ID. This helps to map request handlers to log messages. 78 | 79 | Open up a web browser and navigate to `localhost:8080` and click on the currently running servlet context (if you didn't change the pom.xml artifactId yet, it will be "spring-mvc-bootstrap-angularjs-starter"). 80 | * The web page you should see is mapped by controller `com.willvuong.bootstrapper.controllers.HomeController` which all it does is forward to `/WEB-INF/views/angular-index.jsp`. 81 | * JSP `/WEB-INF/views/angular-index.jsp` is located at `src/main/webapp/WEB-INF/views/angular-index.jsp`. It references JavaScript and Angular resources in `/resources/` (`src/main/webapp/resources`). 82 | * The response is decorated by SiteMesh by `src/main/webapp/WEB-INF/decorators/default.jsp`. This JSP decorator wraps angular-index.jsp in Bootstrap CSS styling in `/resources/` (`src/main/webapp/resources`). 83 | 84 | Metrics reporting is exposed via: 85 | * Application metrics: {contextpath}/diagnostics/metrics 86 | * JVM metrics: {contextpath}/diagnostics/jvm 87 | * Thread dump: {contextpath}/diagnostics/threads 88 | * Health checks: {contextpath}/diagnostics/health 89 | * Ping: {contextpath}/diagnostics/ping 90 | 91 | To kill the Jetty server, use `Control-c`. 92 | 93 | Hot development mode (aka class reloading) 94 | ---------------------- 95 | You could just reload the servlet context in the Jetty command line after every recompile by hitting enter (at least until you run out of heap space!) but that really sucks most times. (Also a reminder that you don't have to reload the servlet context for JSP or static resource changes as they are LIVE since `mvn jetty:run` is serving directly out of `src/main/webapp`.) 96 | 97 | If you have [JRebel](http://zeroturnaround.com/software/jrebel/), you should already be good to go via IDE plugin or [attaching the JRebel agent to the JVM](http://manuals.zeroturnaround.com/jrebel/standalone/launch-quick-start.html). 98 | 99 | Otherwise, the [Spring Loaded](https://github.com/spring-projects/spring-loaded) project can serve as a poor man's JRebel in a pinch. The Spring Loaded jar is located in the `tools` directory and all of the configuration is in the pom.xml (jetty-maven-plugin and the debug profile). 100 | 101 | To start your app in Jetty with Spring Loaded: 102 | `mvn jetty:run-forked` 103 | 104 | To start your app in Jetty with Spring Loaded and JDWP on port `5005`: 105 | `mvn jetty:run-forked -P debug` 106 | 107 | Packaging for deployment 108 | ---------------------- 109 | TODO 110 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | com.willvuong 7 | spring-mvc-bootstrap-angularjs-starter 8 | spring-mvc-bootstrap-angularjs-starter 9 | war 10 | 1.0.0-BUILD-SNAPSHOT 11 | 12 | 13 | 1.7 14 | 3.2.5.RELEASE 15 | 1.7.4 16 | 1.7.5 17 | 3.0.1 18 | 19 | 20 | 21 | 22 | 23 | com.google.guava 24 | guava 25 | 15.0 26 | 27 | 28 | 29 | org.apache.commons 30 | commons-lang3 31 | 3.1 32 | 33 | 34 | 35 | 36 | org.springframework 37 | spring-context 38 | ${org.springframework-version} 39 | 40 | 41 | 42 | commons-logging 43 | commons-logging 44 | 45 | 46 | 47 | 48 | 49 | org.springframework 50 | spring-webmvc 51 | ${org.springframework-version} 52 | 53 | 54 | 55 | 56 | org.aspectj 57 | aspectjrt 58 | ${org.aspectj-version} 59 | 60 | 61 | 62 | 63 | org.slf4j 64 | slf4j-api 65 | ${org.slf4j-version} 66 | 67 | 68 | 69 | org.slf4j 70 | jcl-over-slf4j 71 | ${org.slf4j-version} 72 | runtime 73 | 74 | 75 | 76 | ch.qos.logback 77 | logback-classic 78 | 1.0.13 79 | 80 | 81 | 82 | org.codehaus.janino 83 | janino 84 | 2.6.1 85 | 86 | 87 | 88 | org.fusesource.jansi 89 | jansi 90 | 1.11 91 | 92 | 93 | 94 | 95 | javax.inject 96 | javax.inject 97 | 1 98 | 99 | 100 | 101 | 102 | javax.servlet 103 | javax.servlet-api 104 | 3.1.0 105 | provided 106 | 107 | 108 | 109 | javax.servlet.jsp 110 | jsp-api 111 | 2.1 112 | provided 113 | 114 | 115 | 116 | javax.servlet.jsp.jstl 117 | jstl-api 118 | 1.2 119 | 120 | 121 | javax.servlet 122 | servlet-api 123 | 124 | 125 | 126 | 127 | 128 | org.glassfish.web 129 | jstl-impl 130 | 1.2 131 | 132 | 133 | javax.servlet 134 | servlet-api 135 | 136 | 137 | 138 | 139 | 140 | opensymphony 141 | sitemesh 142 | 2.4.2 143 | 144 | 145 | 146 | 147 | org.codehaus.jackson 148 | jackson-mapper-asl 149 | 1.8.1 150 | 151 | 152 | 153 | 154 | javax.validation 155 | validation-api 156 | 1.0.0.GA 157 | 158 | 159 | 160 | org.hibernate 161 | hibernate-validator 162 | 4.1.0.Final 163 | 164 | 165 | 166 | 167 | joda-time 168 | joda-time 169 | 1.6.2 170 | 171 | 172 | 173 | 174 | com.codahale.metrics 175 | metrics-core 176 | ${metrics-version} 177 | 178 | 179 | 180 | com.codahale.metrics 181 | metrics-jvm 182 | ${metrics-version} 183 | 184 | 185 | 186 | com.codahale.metrics 187 | metrics-servlets 188 | ${metrics-version} 189 | 190 | 191 | 192 | com.codahale.metrics 193 | metrics-servlet 194 | ${metrics-version} 195 | 196 | 197 | 198 | 199 | org.springframework 200 | spring-test 201 | ${org.springframework-version} 202 | test 203 | 204 | 205 | 206 | junit 207 | junit 208 | 4.8.2 209 | test 210 | 211 | 212 | 213 | xmlunit 214 | xmlunit 215 | 1.2 216 | test 217 | 218 | 219 | 220 | com.jayway.jsonpath 221 | json-path 222 | 0.8.1 223 | test 224 | 225 | 226 | 227 | org.hamcrest 228 | hamcrest-library 229 | 1.3 230 | test 231 | 232 | 233 | 234 | 235 | 236 | 237 | org.springframework.maven.snapshot 238 | Spring Maven Snapshot Repository 239 | http://maven.springframework.org/snapshot 240 | 241 | false 242 | 243 | 244 | true 245 | 246 | 247 | 248 | 249 | 250 | org.springframework.maven.milestone 251 | Spring Maven Milestone Repository 252 | http://maven.springframework.org/milestone 253 | 254 | false 255 | 256 | 257 | 258 | 259 | 260 | 261 | debug 262 | 263 | -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | org.apache.maven.plugins 272 | maven-compiler-plugin 273 | 3.1 274 | 275 | ${java-version} 276 | ${java-version} 277 | 278 | 279 | 280 | 281 | org.apache.maven.plugins 282 | maven-surefire-plugin 283 | 2.16 284 | 285 | 286 | **/*Tests.java 287 | 288 | 289 | **/Abstract*.java 290 | 291 | junit:junit 292 | -Xmx512m 293 | 294 | 295 | 296 | 297 | org.apache.maven.plugins 298 | maven-dependency-plugin 299 | 2.8 300 | 301 | 302 | install 303 | install 304 | 305 | sources 306 | 307 | 308 | 309 | 310 | 311 | 312 | org.apache.tomcat.maven 313 | tomcat7-maven-plugin 314 | 2.0 315 | 316 | 317 | 318 | org.mortbay.jetty 319 | jetty-maven-plugin 320 | 8.1.14.v20131031 321 | 322 | 323 | /${project.artifactId} 324 | 325 | manual 326 | foo 327 | 8082 328 | 329 | true 330 | -javaagent:${basedir}/tools/springloaded-1.1.4.jar -noverify ${jdwp} 331 | 332 | 333 | 334 | 335 | 336 | -------------------------------------------------------------------------------- /scripts/aggregate_metrics.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import urllib2 3 | import json 4 | from collections import defaultdict 5 | from collections import Counter 6 | import numpy 7 | 8 | def main(argv): 9 | urls = argv[0:] 10 | counters = Counter() 11 | meters = defaultdict(Counter) 12 | timers = defaultdict(Counter) 13 | 14 | for url in urls: 15 | f = urllib2.urlopen(url) 16 | metric = json.loads(f.read()) 17 | 18 | # merge counters 19 | for counter in metric['counters'].keys(): 20 | # see if counter already exists 21 | # print counter 22 | counters[counter] += metric['counters'][counter]['count'] 23 | 24 | # merge meters 25 | for meter in metric['meters'].keys(): 26 | # print meter 27 | meters[meter]['count'] += metric['meters'][meter]['count'] 28 | 29 | # merge timers 30 | for timer, values in metric['timers'].items(): 31 | timers[timer]['count'] += metric['timers'][timer]['count'] 32 | timers[timer]['max'] = max(timers[timer]['max'], metric['timers'][timer]['max']) 33 | 34 | if timers[timer]['min'] == 0: 35 | timers[timer]['min'] = metric['timers'][timer]['min'] 36 | else: 37 | timers[timer]['min'] = min(timers[timer]['min'], metric['timers'][timer]['min']) 38 | 39 | if timers[timer]['means'] == 0: 40 | timers[timer]['means'] = [] 41 | timers[timer]['p50s'] = [] 42 | timers[timer]['p75s'] = [] 43 | timers[timer]['p95s'] = [] 44 | timers[timer]['p98s'] = [] 45 | timers[timer]['p99s'] = [] 46 | timers[timer]['p999s'] = [] 47 | timers[timer]['weight'] = [] 48 | 49 | timers[timer]['means'].append(metric['timers'][timer]['mean']) 50 | timers[timer]['p50s'].append(metric['timers'][timer]['p50']) 51 | timers[timer]['p75s'].append(metric['timers'][timer]['p75']) 52 | timers[timer]['p95s'].append(metric['timers'][timer]['p95']) 53 | timers[timer]['p98s'].append(metric['timers'][timer]['p98']) 54 | timers[timer]['p99s'].append(metric['timers'][timer]['p99']) 55 | timers[timer]['p999s'].append(metric['timers'][timer]['p999']) 56 | timers[timer]['weight'].append(metric['timers'][timer]['count']) 57 | timers[timer]['duration_units'] = metric['timers'][timer]['duration_units'] 58 | 59 | # average timers 60 | for name, timer in timers.items(): 61 | timer['mean'] = numpy.average(timer['means'], weights=timer['weight']) 62 | timer['p50'] = numpy.average(timer['p50s'], weights=timer['weight']) 63 | timer['p75'] = numpy.average(timer['p75s'], weights=timer['weight']) 64 | timer['p95'] = numpy.average(timer['p95s'], weights=timer['weight']) 65 | timer['p98'] = numpy.average(timer['p98s'], weights=timer['weight']) 66 | timer['p99'] = numpy.average(timer['p99s'], weights=timer['weight']) 67 | timer['p999'] = numpy.average(timer['p999s'], weights=timer['weight']) 68 | del timer['means'] 69 | del timer['p50s'] 70 | del timer['p75s'] 71 | del timer['p95s'] 72 | del timer['p98s'] 73 | del timer['p99s'] 74 | del timer['p999s'] 75 | del timer['weight'] 76 | 77 | print '=' * 80 78 | print 'COUNTERS' 79 | for k, v in counters.items(): 80 | print ' {0}: {1}'.format(k, v) 81 | print 82 | 83 | print '=' * 80 84 | print 'METERS' 85 | for key, counter in sorted(meters.items()): 86 | print key 87 | for k, v in counter.items(): 88 | print ' {0}: {1}'.format(k, v) 89 | print 90 | 91 | print '=' * 80 92 | print 'TIMERS' 93 | for key, counter in sorted(timers.items()): 94 | print key 95 | for k, v in sorted(counter.items()): 96 | print ' {0}: {1}'.format(k, v) 97 | 98 | if __name__ == "__main__": 99 | main(sys.argv[1:]) 100 | -------------------------------------------------------------------------------- /src/main/java/com/willvuong/bootstrapper/config/AppConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.willvuong.bootstrapper.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.context.annotation.Import; 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * User: will 9 | * Date: 11/23/13 10 | * Time: 12:34 PM 11 | */ 12 | @Configuration 13 | @Import(MetricsConfiguration.class) 14 | public class AppConfiguration { 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/com/willvuong/bootstrapper/config/MetricsConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.willvuong.bootstrapper.config; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import com.codahale.metrics.health.HealthCheckRegistry; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | /** 9 | * Created with IntelliJ IDEA. 10 | * User: will 11 | * Date: 12/5/13 12 | * Time: 2:45 PM 13 | */ 14 | @Configuration 15 | public class MetricsConfiguration { 16 | 17 | @Bean 18 | public MetricRegistry metricRegistry() { 19 | return new MetricRegistry(); 20 | } 21 | 22 | @Bean 23 | public HealthCheckRegistry healthCheckRegistry() { 24 | return new HealthCheckRegistry(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/willvuong/bootstrapper/controllers/ErrorHandlerController.java: -------------------------------------------------------------------------------- 1 | package com.willvuong.bootstrapper.controllers; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestParam; 6 | import org.springframework.web.bind.annotation.ResponseBody; 7 | 8 | /** 9 | * Created with IntelliJ IDEA. 10 | * User: will 11 | * Date: 11/23/13 12 | * Time: 3:14 PM 13 | */ 14 | @Controller 15 | public class ErrorHandlerController { 16 | 17 | @RequestMapping("/error") 18 | public String errorView() { 19 | return "/WEB-INF/views/error.jsp"; 20 | } 21 | 22 | @RequestMapping("/debug/ThrowException") 23 | @ResponseBody 24 | public String throwException(@RequestParam String really) { 25 | if ("true".equals(really)) { 26 | throw new RuntimeException("intentional exception throw to invoke error handler", new RuntimeException("causal exception")); 27 | } 28 | 29 | return "really=" + really; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/willvuong/bootstrapper/controllers/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.willvuong.bootstrapper.controllers; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * User: will 9 | * Date: 11/23/13 10 | * Time: 12:37 PM 11 | */ 12 | @Controller 13 | @RequestMapping("/") 14 | public class HomeController { 15 | 16 | @RequestMapping 17 | public String home() { 18 | return "/WEB-INF/views/angular-index.jsp"; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/willvuong/bootstrapper/filter/LogbackResponseServletFilter.java: -------------------------------------------------------------------------------- 1 | package com.willvuong.bootstrapper.filter; 2 | 3 | import ch.qos.logback.classic.LoggerContext; 4 | import com.willvuong.bootstrapper.util.ThreadLocalMemoryAppender; 5 | import org.apache.commons.lang3.StringEscapeUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.web.filter.OncePerRequestFilter; 9 | 10 | import javax.servlet.*; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import javax.servlet.http.HttpServletResponseWrapper; 14 | import java.io.CharArrayWriter; 15 | import java.io.IOException; 16 | import java.io.PrintWriter; 17 | 18 | /** 19 | * Created with IntelliJ IDEA. 20 | * User: will 21 | * Date: 11/24/13 22 | * Time: 5:08 PM 23 | */ 24 | public class LogbackResponseServletFilter extends OncePerRequestFilter { 25 | 26 | private static final Logger logger = LoggerFactory.getLogger(LogbackResponseServletFilter.class); 27 | 28 | 29 | @Override 30 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { 31 | boolean disabled = "true".equals(request.getParameter("disabled")); 32 | String url = request.getRequestURL().toString(); 33 | boolean notHtml = url.endsWith(".js") || url.endsWith(".css"); 34 | 35 | if (disabled || notHtml) { 36 | chain.doFilter(request, response); 37 | return; 38 | } 39 | 40 | CharResponseWrapper wrapper = new CharResponseWrapper(response); 41 | try { 42 | logger.trace("before doFilter()"); 43 | chain.doFilter(request, wrapper); 44 | } 45 | finally { 46 | boolean doFilter = wrapper.getContentType() != null && wrapper.getContentType().contains("text/html"); 47 | logger.debug("contentType: {}, filter this request? {}", wrapper.getContentType(), doFilter); 48 | 49 | if (!doFilter) { 50 | // do not modify response content 51 | return; 52 | } 53 | 54 | if (wrapper.toString() != null) { 55 | PrintWriter writer = response.getWriter(); 56 | StringBuilder responseBody = new StringBuilder(wrapper.toString()); 57 | logger.trace("responseBody before: {} bytes", responseBody.length()); 58 | 59 | String encoded = ThreadLocalMemoryAppender.ThreadLocalHolder.getBufferAsJson(null, null); 60 | ThreadLocalMemoryAppender.ThreadLocalHolder.clearLoggedEvents(); 61 | if (encoded != null) { 62 | StringBuilder div = new StringBuilder(encoded); 63 | logger.debug("html: {} bytes", div.length()); 64 | 65 | int pos = responseBody.lastIndexOf(""); 66 | responseBody.insert(pos, div); 67 | logger.trace("responseBody after: {} bytes", responseBody.length()); 68 | // logger.trace("{}", responseBody); 69 | } 70 | else { 71 | logger.debug("no events to write"); 72 | } 73 | 74 | response.setContentLength(responseBody.length()); 75 | writer.write(responseBody.toString()); 76 | writer.flush(); 77 | 78 | logger.debug("response written: {} bytes", responseBody.length()); 79 | } 80 | } 81 | 82 | } 83 | 84 | private class CharResponseWrapper extends HttpServletResponseWrapper { 85 | private CharArrayWriter output; 86 | private PrintWriter writer; 87 | 88 | private boolean getWriterCalled = false; 89 | private boolean getOutputStreamCalled = false; 90 | 91 | public CharResponseWrapper(HttpServletResponse response) { 92 | super(response); 93 | output = new CharArrayWriter(); 94 | } 95 | 96 | public String toString() { 97 | if (writer != null) { 98 | return output.toString(); 99 | } 100 | return null; 101 | } 102 | 103 | public PrintWriter getWriter() { 104 | if (writer != null) { 105 | return writer; 106 | } 107 | 108 | if (getOutputStreamCalled) { 109 | throw new IllegalStateException("getOutputStream() already called"); 110 | } 111 | 112 | getWriterCalled = true; 113 | writer = new PrintWriter(output); 114 | return writer; 115 | } 116 | 117 | @Override 118 | public ServletOutputStream getOutputStream() throws IOException { 119 | if (getWriterCalled) { 120 | throw new IllegalStateException("getWriter() already called"); 121 | } 122 | 123 | getOutputStreamCalled = true; 124 | return super.getOutputStream(); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/willvuong/bootstrapper/filter/RequestMDCServletFilter.java: -------------------------------------------------------------------------------- 1 | package com.willvuong.bootstrapper.filter; 2 | 3 | import org.apache.commons.lang3.RandomStringUtils; 4 | import org.slf4j.MDC; 5 | import org.springframework.web.filter.OncePerRequestFilter; 6 | 7 | import javax.servlet.FilterChain; 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | 13 | /** 14 | * Created with IntelliJ IDEA. 15 | * User: will 16 | * Date: 11/25/13 17 | * Time: 8:46 AM 18 | */ 19 | public class RequestMDCServletFilter extends OncePerRequestFilter { 20 | public static final String REQUESTURL = "REQUESTURL"; 21 | public static final String REQUESTURI = "REQUESTURI"; 22 | public static final String REQUESTID = "REQUESTID"; 23 | 24 | 25 | @Override 26 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 27 | MDC.put(REQUESTURL, request.getRequestURL().toString()); 28 | MDC.put(REQUESTURI, request.getRequestURI()); 29 | MDC.put(REQUESTID, RandomStringUtils.randomAlphanumeric(8)); 30 | 31 | try { 32 | filterChain.doFilter(request, response); 33 | } 34 | finally { 35 | MDC.clear(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/willvuong/bootstrapper/mvcconfig/GlobalDefaultExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.willvuong.bootstrapper.mvcconfig; 2 | 3 | import org.springframework.core.annotation.AnnotationUtils; 4 | import org.springframework.web.bind.annotation.ControllerAdvice; 5 | import org.springframework.web.bind.annotation.ExceptionHandler; 6 | import org.springframework.web.bind.annotation.ResponseStatus; 7 | import org.springframework.web.servlet.ModelAndView; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import java.util.Date; 11 | 12 | @ControllerAdvice 13 | class GlobalDefaultExceptionHandler { 14 | public static final String DEFAULT_ERROR_VIEW = "/error"; 15 | 16 | @ExceptionHandler(value = Exception.class) 17 | public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { 18 | // If the exception is annotated with @ResponseStatus rethrow it and let 19 | // the framework handle it - like the OrderNotFoundException example 20 | // at the start of this post. 21 | // AnnotationUtils is a Spring Framework utility class. 22 | if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) 23 | throw e; 24 | 25 | // Otherwise setup and send the user to a default error-view. 26 | ModelAndView mav = new ModelAndView(); 27 | mav.addObject("datetime", new Date()); 28 | mav.addObject("exception", e); 29 | mav.addObject("url", req.getRequestURL()); 30 | mav.setViewName(DEFAULT_ERROR_VIEW); 31 | return mav; 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/willvuong/bootstrapper/mvcconfig/MvcConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.willvuong.bootstrapper.mvcconfig; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; 6 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 7 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 9 | 10 | /** 11 | * Created with IntelliJ IDEA. 12 | * User: will 13 | * Date: 11/23/13 14 | * Time: 12:27 PM 15 | */ 16 | @Configuration 17 | @EnableWebMvc 18 | @ComponentScan(basePackages = {"com.willvuong.bootstrapper.controllers"}) 19 | public class MvcConfiguration extends WebMvcConfigurerAdapter { 20 | @Override 21 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 22 | registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); 23 | } 24 | 25 | @Override 26 | public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { 27 | configurer.enable(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/willvuong/bootstrapper/mvcconfig/ServletContextAutoConfigure.java: -------------------------------------------------------------------------------- 1 | package com.willvuong.bootstrapper.mvcconfig; 2 | 3 | import com.codahale.metrics.MetricRegistry; 4 | import com.codahale.metrics.health.HealthCheckRegistry; 5 | import com.codahale.metrics.jvm.FileDescriptorRatioGauge; 6 | import com.codahale.metrics.jvm.GarbageCollectorMetricSet; 7 | import com.codahale.metrics.jvm.MemoryUsageGaugeSet; 8 | import com.codahale.metrics.jvm.ThreadStatesGaugeSet; 9 | import com.codahale.metrics.servlet.InstrumentedFilter; 10 | import com.codahale.metrics.servlet.InstrumentedFilterContextListener; 11 | import com.codahale.metrics.servlets.HealthCheckServlet; 12 | import com.codahale.metrics.servlets.MetricsServlet; 13 | import com.codahale.metrics.servlets.PingServlet; 14 | import com.codahale.metrics.servlets.ThreadDumpServlet; 15 | import com.opensymphony.sitemesh.webapp.SiteMeshFilter; 16 | import com.willvuong.bootstrapper.filter.LogbackResponseServletFilter; 17 | import com.willvuong.bootstrapper.filter.RequestMDCServletFilter; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | import org.springframework.web.WebApplicationInitializer; 21 | import org.springframework.web.context.support.WebApplicationContextUtils; 22 | 23 | import javax.servlet.*; 24 | import javax.servlet.http.HttpServlet; 25 | import java.util.EnumSet; 26 | 27 | /** 28 | * Created with IntelliJ IDEA. 29 | * User: will 30 | * Date: 11/25/13 31 | * Time: 9:13 AM 32 | */ 33 | public class ServletContextAutoConfigure implements WebApplicationInitializer { 34 | 35 | private static final Logger logger = LoggerFactory.getLogger(ServletContextAutoConfigure.class); 36 | 37 | @Override 38 | public void onStartup(final ServletContext servletContext) throws ServletException { 39 | // set up a metric registry instance just for jvm metrics 40 | MetricRegistry jvmMetrics = new MetricRegistry(); 41 | jvmMetrics.register("memory", new MemoryUsageGaugeSet()); 42 | jvmMetrics.register("gc", new GarbageCollectorMetricSet()); 43 | jvmMetrics.register("threads", new ThreadStatesGaugeSet()); 44 | jvmMetrics.register("fileDescriptors", new FileDescriptorRatioGauge()); 45 | 46 | // handle listeners 47 | logger.info("autoconfiguring metrics servlet listener"); 48 | servletContext.addListener(new MetricsServlet.ContextListener() { 49 | @Override 50 | protected MetricRegistry getMetricRegistry() { 51 | return WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext).getBean(MetricRegistry.class); 52 | } 53 | }); 54 | 55 | logger.info("autoconfiguring health check registry listener"); 56 | servletContext.addListener(new HealthCheckServlet.ContextListener() { 57 | @Override 58 | protected HealthCheckRegistry getHealthCheckRegistry() { 59 | return WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext).getBean(HealthCheckRegistry.class); 60 | } 61 | }); 62 | 63 | logger.info("autoconfiguring instrumented filter listener"); 64 | servletContext.addListener(new InstrumentedFilterContextListener() { 65 | @Override 66 | protected MetricRegistry getMetricRegistry() { 67 | return WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext).getBean(MetricRegistry.class); 68 | } 69 | }); 70 | 71 | // handle filters 72 | logger.info("autoconfiguring requestMDCServletFilter"); 73 | addFilterIfDoesntExist(servletContext, "requestMDCServletFilter", RequestMDCServletFilter.class, 74 | null, true, "/*"); 75 | 76 | logger.info("autoconfiguring metricFilter"); 77 | addFilterIfDoesntExist(servletContext, "metricFilter", InstrumentedFilter.class, 78 | null, true, "/*"); 79 | 80 | logger.info("autoconfiguring logbackResponseServletFilter"); 81 | addFilterIfDoesntExist(servletContext, "logbackResponseServletFilter", LogbackResponseServletFilter.class, 82 | null, true, "/*"); 83 | 84 | logger.info("autoconfiguring site mesh filter"); 85 | addFilterIfDoesntExist(servletContext, "siteMeshFilter", SiteMeshFilter.class, 86 | null, true, "/*"); 87 | 88 | // handle servlets 89 | logger.info("autoconfiguring metric servlet"); 90 | addServletIfDoesntExist(servletContext, "metricsServlet", MetricsServlet.class, "/diagnostics/metrics"); 91 | 92 | logger.info("autoconfiguring health check servlet"); 93 | addServletIfDoesntExist(servletContext, "healthCheckServlet", HealthCheckServlet.class, "/diagnostics/health"); 94 | 95 | logger.info("autoconfiguring ping servlet"); 96 | addServletIfDoesntExist(servletContext, "pingServlet", PingServlet.class, "/diagnostics/ping"); 97 | 98 | logger.info("autoconfiguring thread dump servlet"); 99 | addServletIfDoesntExist(servletContext, "threadDumpServlet", ThreadDumpServlet.class, "/diagnostics/threads"); 100 | 101 | logger.info("autoconfiguring jvm servlet"); 102 | addServletIfDoesntExist(servletContext, "jvmServlet", new MetricsServlet(jvmMetrics), "/diagnostics/jvm"); 103 | 104 | logger.info("all autoconfiguration injected into servlet context to be initialized when context starts."); 105 | } 106 | 107 | public void addFilterIfDoesntExist(final ServletContext context, final String name, final Class filterClass, 108 | final EnumSet dispatcherTypes, final boolean isMatchAfter, 109 | final String... urlPatterns) { 110 | 111 | FilterRegistration.Dynamic filter = context.addFilter(name, filterClass); 112 | // if filter already exists, addFilter() will return null 113 | if (filter != null) { 114 | filter.addMappingForUrlPatterns(dispatcherTypes, isMatchAfter, urlPatterns); 115 | } 116 | } 117 | 118 | public void addServletIfDoesntExist(final ServletContext context, final String name, final Class servletClass, 119 | final String... urlPatterns) { 120 | 121 | ServletRegistration.Dynamic servlet = context.addServlet(name, servletClass); 122 | if (servlet != null) { 123 | servlet.addMapping(urlPatterns); 124 | } 125 | } 126 | 127 | public void addServletIfDoesntExist(final ServletContext context, final String name, final HttpServlet servlet, 128 | final String... urlPatterns) { 129 | 130 | ServletRegistration.Dynamic servletRegistration = context.addServlet(name, servlet); 131 | if (servletRegistration != null) { 132 | servletRegistration.addMapping(urlPatterns); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/com/willvuong/bootstrapper/util/ThreadLocalMemoryAppender.java: -------------------------------------------------------------------------------- 1 | package com.willvuong.bootstrapper.util; 2 | 3 | import ch.qos.logback.classic.spi.ILoggingEvent; 4 | import ch.qos.logback.core.AppenderBase; 5 | import com.google.common.base.Throwables; 6 | import com.google.common.collect.Lists; 7 | import org.codehaus.jackson.map.ObjectMapper; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.util.List; 12 | 13 | /** 14 | * Created with IntelliJ IDEA. 15 | * User: will 16 | * Date: 11/23/13 17 | * Time: 5:04 PM 18 | */ 19 | public class ThreadLocalMemoryAppender extends AppenderBase { 20 | 21 | private String htmlBefore = ""; 22 | private String htmlAfter = ""; 23 | 24 | // for json output 25 | private boolean jsonOutput = false; 26 | 27 | @Override 28 | protected void append(ILoggingEvent eventObject) { 29 | ThreadLocalHolder.appendLoggedEvent(eventObject); 30 | } 31 | 32 | @Override 33 | public void start() { 34 | addInfo("htmlBefore=" + htmlBefore); 35 | addInfo("htmlAfter=" + htmlAfter); 36 | 37 | super.start(); 38 | } 39 | 40 | @Override 41 | public void stop() { 42 | super.stop(); 43 | } 44 | 45 | public boolean isJsonOutput() { 46 | return jsonOutput; 47 | } 48 | 49 | public void setJson(boolean jsonOutput) { 50 | this.jsonOutput = jsonOutput; 51 | } 52 | 53 | public String getHtmlBefore() { 54 | return this.htmlBefore; 55 | } 56 | 57 | public void setHtmlBefore(String htmlBefore) { 58 | this.htmlBefore = htmlBefore; 59 | } 60 | 61 | public String getHtmlAfter() { 62 | return htmlAfter; 63 | } 64 | 65 | public void setHtmlAfter(String htmlAfter) { 66 | this.htmlAfter = htmlAfter; 67 | } 68 | 69 | 70 | public static abstract class ThreadLocalHolder { 71 | private static ThreadLocal> threadLocal = new ThreadLocal>() { 72 | @Override 73 | protected List initialValue() { 74 | return Lists.newArrayList(); 75 | } 76 | }; 77 | 78 | private static ThreadLocal output = new ThreadLocal() { 79 | @Override 80 | protected ByteArrayOutputStream initialValue() { 81 | return new ByteArrayOutputStream(2048); 82 | } 83 | }; 84 | 85 | public static List getLoggedEvents() { 86 | return threadLocal.get(); 87 | } 88 | 89 | public static void appendLoggedEvent(ILoggingEvent event) { 90 | threadLocal.get().add(event); 91 | } 92 | 93 | public static void clearLoggedEvents() { 94 | threadLocal.get().clear(); 95 | } 96 | 97 | public static String getBufferAsJson(String htmlBefore, String htmlAfter) { 98 | synchronized (threadLocal) { 99 | ObjectMapper mapper = new ObjectMapper(); 100 | try { 101 | ByteArrayOutputStream out = output.get(); 102 | out.reset(); 103 | 104 | if (htmlBefore != null) { 105 | out.write(htmlBefore.getBytes()); 106 | } 107 | 108 | out.write("".getBytes()); 113 | 114 | if (htmlAfter != null) { 115 | out.write(htmlAfter.getBytes()); 116 | } 117 | 118 | return out.toString(); 119 | } catch (IOException e) { 120 | throw Throwables.propagate(e); 121 | } 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | %d [%thread] %-5level %logger{36} - %msg%n 10 | 11 | 12 | 13 | true 14 | 15 | %boldWhite(%d) [%thread] %highlight(%-5level) %yellow(%logger{36}) - %green(%X{REQUESTID}) %cyan(%X{REQUESTURI}) - %msg%n 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/decorators.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /* 5 | 6 | 7 | 8 | /resources/* 9 | 10 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/decorators/decorator.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %> 2 | 3 | 4 | 5 | <decorator:title/> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | > 19 | 20 | 40 | 41 |
42 |
43 |

Bootstrap starter template

44 |

Use this document as a way to quickly start any new project.
All you get is this text and a mostly barebones HTML document.

45 |
46 | 47 |
48 |

Decorated body

49 | 50 |
51 | 52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/angular-index.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 2 | 3 | 4 | 5 | 6 | angular-index.jsp 7 | 8 | 9 | 10 | 14 | 15 |
16 | 17 |
Angular seed app: v
18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/error.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Error Page

9 |

Application has encountered an error.

10 | 11 |
12 | Date: ${datetime}
13 | Failed URL: ${url}
14 | Exception:  ${exception.message}
15 |     ${ste}
16 | 
17 | 
18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/views/index-async.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 43 | My AngularJS App 44 | 45 | 46 | 47 | 51 | 52 |
53 | 54 |
Angular seed app: v
55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | contextClass 9 | org.springframework.web.context.support.AnnotationConfigWebApplicationContext 10 | 11 | 12 | 13 | contextConfigLocation 14 | com.willvuong.bootstrapper.config.AppConfiguration 15 | 16 | 17 | 18 | 19 | org.springframework.web.context.ContextLoaderListener 20 | 21 | 22 | 23 | 24 | appServlet 25 | org.springframework.web.servlet.DispatcherServlet 26 | 27 | contextClass 28 | org.springframework.web.context.support.AnnotationConfigWebApplicationContext 29 | 30 | 31 | contextConfigLocation 32 | com.willvuong.bootstrapper.mvcconfig.MvcConfiguration 33 | 34 | 1 35 | true 36 | 37 | 38 | 39 | appServlet 40 | / 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/main/webapp/resources/css/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wvuong/spring-mvc-bootstrap-angularjs-starter/765c59e9468a0ce2c045fcac353fe0da5ccd8d9e/src/main/webapp/resources/css/.gitignore -------------------------------------------------------------------------------- /src/main/webapp/resources/css/app.css: -------------------------------------------------------------------------------- 1 | /* app css stylesheet */ 2 | 3 | .menu { 4 | list-style: none; 5 | border-bottom: 0.1em solid black; 6 | margin-bottom: 2em; 7 | padding: 0 0 0.5em; 8 | } 9 | 10 | .menu:before { 11 | content: "["; 12 | } 13 | 14 | .menu:after { 15 | content: "]"; 16 | } 17 | 18 | .menu > li { 19 | display: inline; 20 | } 21 | 22 | .menu > li:before { 23 | content: "|"; 24 | padding-right: 0.3em; 25 | } 26 | 27 | .menu > li:nth-child(1):before { 28 | content: ""; 29 | padding: 0; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/webapp/resources/img/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wvuong/spring-mvc-bootstrap-angularjs-starter/765c59e9468a0ce2c045fcac353fe0da5ccd8d9e/src/main/webapp/resources/img/.gitignore -------------------------------------------------------------------------------- /src/main/webapp/resources/index-async.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 43 | My AngularJS App 44 | 45 | 46 | 47 | 51 | 52 |
53 | 54 |
Angular seed app: v
55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/main/webapp/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My AngularJS App 6 | 7 | 8 | 9 | 13 | 14 |
15 | 16 |
Angular seed app: v
17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/webapp/resources/js/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | // Declare app level module which depends on filters, and services 5 | angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives']). 6 | config(['$routeProvider', function($routeProvider) { 7 | $routeProvider.when('/view1', {templateUrl: 'resources/partials/partial1.html', controller: MyCtrl1}); 8 | $routeProvider.when('/view2', {templateUrl: 'resources/partials/partial2.html', controller: MyCtrl2}); 9 | $routeProvider.otherwise({redirectTo: '/view1'}); 10 | }]); 11 | -------------------------------------------------------------------------------- /src/main/webapp/resources/js/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Controllers */ 4 | 5 | 6 | function MyCtrl1() {} 7 | MyCtrl1.$inject = []; 8 | 9 | 10 | function MyCtrl2() { 11 | } 12 | MyCtrl2.$inject = []; 13 | -------------------------------------------------------------------------------- /src/main/webapp/resources/js/directives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Directives */ 4 | 5 | 6 | angular.module('myApp.directives', []). 7 | directive('appVersion', ['version', function(version) { 8 | return function(scope, elm, attrs) { 9 | elm.text(version); 10 | }; 11 | }]); 12 | -------------------------------------------------------------------------------- /src/main/webapp/resources/js/filters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Filters */ 4 | 5 | angular.module('myApp.filters', []). 6 | filter('interpolate', ['version', function(version) { 7 | return function(text) { 8 | return String(text).replace(/\%VERSION\%/mg, version); 9 | } 10 | }]); 11 | -------------------------------------------------------------------------------- /src/main/webapp/resources/js/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Services */ 4 | 5 | 6 | // Demonstrate how to register services 7 | // In this case it is a simple value service. 8 | angular.module('myApp.services', []). 9 | value('version', '0.1'); 10 | -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/angular/angular-cookies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.5 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngCookies 12 | */ 13 | 14 | 15 | angular.module('ngCookies', ['ng']). 16 | /** 17 | * @ngdoc object 18 | * @name ngCookies.$cookies 19 | * @requires $browser 20 | * 21 | * @description 22 | * Provides read/write access to browser's cookies. 23 | * 24 | * Only a simple Object is exposed and by adding or removing properties to/from 25 | * this object, new cookies are created/deleted at the end of current $eval. 26 | * 27 | * @example 28 | 29 | 30 | 38 | 39 | 40 | */ 41 | factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) { 42 | var cookies = {}, 43 | lastCookies = {}, 44 | lastBrowserCookies, 45 | runEval = false, 46 | copy = angular.copy, 47 | isUndefined = angular.isUndefined; 48 | 49 | //creates a poller fn that copies all cookies from the $browser to service & inits the service 50 | $browser.addPollFn(function() { 51 | var currentCookies = $browser.cookies(); 52 | if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl 53 | lastBrowserCookies = currentCookies; 54 | copy(currentCookies, lastCookies); 55 | copy(currentCookies, cookies); 56 | if (runEval) $rootScope.$apply(); 57 | } 58 | })(); 59 | 60 | runEval = true; 61 | 62 | //at the end of each eval, push cookies 63 | //TODO: this should happen before the "delayed" watches fire, because if some cookies are not 64 | // strings or browser refuses to store some cookies, we update the model in the push fn. 65 | $rootScope.$watch(push); 66 | 67 | return cookies; 68 | 69 | 70 | /** 71 | * Pushes all the cookies from the service to the browser and verifies if all cookies were stored. 72 | */ 73 | function push() { 74 | var name, 75 | value, 76 | browserCookies, 77 | updated; 78 | 79 | //delete any cookies deleted in $cookies 80 | for (name in lastCookies) { 81 | if (isUndefined(cookies[name])) { 82 | $browser.cookies(name, undefined); 83 | } 84 | } 85 | 86 | //update all cookies updated in $cookies 87 | for(name in cookies) { 88 | value = cookies[name]; 89 | if (!angular.isString(value)) { 90 | if (angular.isDefined(lastCookies[name])) { 91 | cookies[name] = lastCookies[name]; 92 | } else { 93 | delete cookies[name]; 94 | } 95 | } else if (value !== lastCookies[name]) { 96 | $browser.cookies(name, value); 97 | updated = true; 98 | } 99 | } 100 | 101 | //verify what was actually stored 102 | if (updated){ 103 | updated = false; 104 | browserCookies = $browser.cookies(); 105 | 106 | for (name in cookies) { 107 | if (cookies[name] !== browserCookies[name]) { 108 | //delete or reset all cookies that the browser dropped from $cookies 109 | if (isUndefined(browserCookies[name])) { 110 | delete cookies[name]; 111 | } else { 112 | cookies[name] = browserCookies[name]; 113 | } 114 | updated = true; 115 | } 116 | } 117 | } 118 | } 119 | }]). 120 | 121 | 122 | /** 123 | * @ngdoc object 124 | * @name ngCookies.$cookieStore 125 | * @requires $cookies 126 | * 127 | * @description 128 | * Provides a key-value (string-object) storage, that is backed by session cookies. 129 | * Objects put or retrieved from this storage are automatically serialized or 130 | * deserialized by angular's toJson/fromJson. 131 | * @example 132 | */ 133 | factory('$cookieStore', ['$cookies', function($cookies) { 134 | 135 | return { 136 | /** 137 | * @ngdoc method 138 | * @name ngCookies.$cookieStore#get 139 | * @methodOf ngCookies.$cookieStore 140 | * 141 | * @description 142 | * Returns the value of given cookie key 143 | * 144 | * @param {string} key Id to use for lookup. 145 | * @returns {Object} Deserialized cookie value. 146 | */ 147 | get: function(key) { 148 | return angular.fromJson($cookies[key]); 149 | }, 150 | 151 | /** 152 | * @ngdoc method 153 | * @name ngCookies.$cookieStore#put 154 | * @methodOf ngCookies.$cookieStore 155 | * 156 | * @description 157 | * Sets a value for given cookie key 158 | * 159 | * @param {string} key Id for the `value`. 160 | * @param {Object} value Value to be stored. 161 | */ 162 | put: function(key, value) { 163 | $cookies[key] = angular.toJson(value); 164 | }, 165 | 166 | /** 167 | * @ngdoc method 168 | * @name ngCookies.$cookieStore#remove 169 | * @methodOf ngCookies.$cookieStore 170 | * 171 | * @description 172 | * Remove given cookie 173 | * 174 | * @param {string} key Id of the key-value pair to delete. 175 | */ 176 | remove: function(key) { 177 | delete $cookies[key]; 178 | } 179 | }; 180 | 181 | }]); 182 | 183 | })(window, window.angular); 184 | -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/angular/angular-cookies.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.5 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(m,f,l){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,c){var b={},g={},h,i=!1,j=f.copy,k=f.isUndefined;c.addPollFn(function(){var a=c.cookies();h!=a&&(h=a,j(a,g),j(a,b),i&&d.$apply())})();i=!0;d.$watch(function(){var a,e,d;for(a in g)k(b[a])&&c.cookies(a,l);for(a in b)e=b[a],f.isString(e)?e!==g[a]&&(c.cookies(a,e),d=!0):f.isDefined(g[a])?b[a]=g[a]:delete b[a];if(d)for(a in e=c.cookies(),b)b[a]!==e[a]&&(k(e[a])?delete b[a]:b[a]=e[a])});return b}]).factory("$cookieStore", 7 | ["$cookies",function(d){return{get:function(c){return f.fromJson(d[c])},put:function(c,b){d[c]=f.toJson(b)},remove:function(c){delete d[c]}}}])})(window,window.angular); 8 | -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/angular/angular-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.5 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | 7 | ( 8 | 9 | /** 10 | * @ngdoc interface 11 | * @name angular.Module 12 | * @description 13 | * 14 | * Interface for configuring angular {@link angular.module modules}. 15 | */ 16 | 17 | function setupModuleLoader(window) { 18 | 19 | function ensure(obj, name, factory) { 20 | return obj[name] || (obj[name] = factory()); 21 | } 22 | 23 | return ensure(ensure(window, 'angular', Object), 'module', function() { 24 | /** @type {Object.} */ 25 | var modules = {}; 26 | 27 | /** 28 | * @ngdoc function 29 | * @name angular.module 30 | * @description 31 | * 32 | * The `angular.module` is a global place for creating and registering Angular modules. All 33 | * modules (angular core or 3rd party) that should be available to an application must be 34 | * registered using this mechanism. 35 | * 36 | * 37 | * # Module 38 | * 39 | * A module is a collocation of services, directives, filters, and configuration information. Module 40 | * is used to configure the {@link AUTO.$injector $injector}. 41 | * 42 | *
 43 |      * // Create a new module
 44 |      * var myModule = angular.module('myModule', []);
 45 |      *
 46 |      * // register a new service
 47 |      * myModule.value('appName', 'MyCoolApp');
 48 |      *
 49 |      * // configure existing services inside initialization blocks.
 50 |      * myModule.config(function($locationProvider) {
 51 | 'use strict';
 52 |      *   // Configure existing providers
 53 |      *   $locationProvider.hashPrefix('!');
 54 |      * });
 55 |      * 
56 | * 57 | * Then you can create an injector and load your modules like this: 58 | * 59 | *
 60 |      * var injector = angular.injector(['ng', 'MyModule'])
 61 |      * 
62 | * 63 | * However it's more likely that you'll just use 64 | * {@link ng.directive:ngApp ngApp} or 65 | * {@link angular.bootstrap} to simplify this process for you. 66 | * 67 | * @param {!string} name The name of the module to create or retrieve. 68 | * @param {Array.=} requires If specified then new module is being created. If unspecified then the 69 | * the module is being retrieved for further configuration. 70 | * @param {Function} configFn Optional configuration function for the module. Same as 71 | * {@link angular.Module#config Module#config()}. 72 | * @returns {module} new module with the {@link angular.Module} api. 73 | */ 74 | return function module(name, requires, configFn) { 75 | if (requires && modules.hasOwnProperty(name)) { 76 | modules[name] = null; 77 | } 78 | return ensure(modules, name, function() { 79 | if (!requires) { 80 | throw Error('No module: ' + name); 81 | } 82 | 83 | /** @type {!Array.>} */ 84 | var invokeQueue = []; 85 | 86 | /** @type {!Array.} */ 87 | var runBlocks = []; 88 | 89 | var config = invokeLater('$injector', 'invoke'); 90 | 91 | /** @type {angular.Module} */ 92 | var moduleInstance = { 93 | // Private state 94 | _invokeQueue: invokeQueue, 95 | _runBlocks: runBlocks, 96 | 97 | /** 98 | * @ngdoc property 99 | * @name angular.Module#requires 100 | * @propertyOf angular.Module 101 | * @returns {Array.} List of module names which must be loaded before this module. 102 | * @description 103 | * Holds the list of modules which the injector will load before the current module is loaded. 104 | */ 105 | requires: requires, 106 | 107 | /** 108 | * @ngdoc property 109 | * @name angular.Module#name 110 | * @propertyOf angular.Module 111 | * @returns {string} Name of the module. 112 | * @description 113 | */ 114 | name: name, 115 | 116 | 117 | /** 118 | * @ngdoc method 119 | * @name angular.Module#provider 120 | * @methodOf angular.Module 121 | * @param {string} name service name 122 | * @param {Function} providerType Construction function for creating new instance of the service. 123 | * @description 124 | * See {@link AUTO.$provide#provider $provide.provider()}. 125 | */ 126 | provider: invokeLater('$provide', 'provider'), 127 | 128 | /** 129 | * @ngdoc method 130 | * @name angular.Module#factory 131 | * @methodOf angular.Module 132 | * @param {string} name service name 133 | * @param {Function} providerFunction Function for creating new instance of the service. 134 | * @description 135 | * See {@link AUTO.$provide#factory $provide.factory()}. 136 | */ 137 | factory: invokeLater('$provide', 'factory'), 138 | 139 | /** 140 | * @ngdoc method 141 | * @name angular.Module#service 142 | * @methodOf angular.Module 143 | * @param {string} name service name 144 | * @param {Function} constructor A constructor function that will be instantiated. 145 | * @description 146 | * See {@link AUTO.$provide#service $provide.service()}. 147 | */ 148 | service: invokeLater('$provide', 'service'), 149 | 150 | /** 151 | * @ngdoc method 152 | * @name angular.Module#value 153 | * @methodOf angular.Module 154 | * @param {string} name service name 155 | * @param {*} object Service instance object. 156 | * @description 157 | * See {@link AUTO.$provide#value $provide.value()}. 158 | */ 159 | value: invokeLater('$provide', 'value'), 160 | 161 | /** 162 | * @ngdoc method 163 | * @name angular.Module#constant 164 | * @methodOf angular.Module 165 | * @param {string} name constant name 166 | * @param {*} object Constant value. 167 | * @description 168 | * Because the constant are fixed, they get applied before other provide methods. 169 | * See {@link AUTO.$provide#constant $provide.constant()}. 170 | */ 171 | constant: invokeLater('$provide', 'constant', 'unshift'), 172 | 173 | /** 174 | * @ngdoc method 175 | * @name angular.Module#filter 176 | * @methodOf angular.Module 177 | * @param {string} name Filter name. 178 | * @param {Function} filterFactory Factory function for creating new instance of filter. 179 | * @description 180 | * See {@link ng.$filterProvider#register $filterProvider.register()}. 181 | */ 182 | filter: invokeLater('$filterProvider', 'register'), 183 | 184 | /** 185 | * @ngdoc method 186 | * @name angular.Module#controller 187 | * @methodOf angular.Module 188 | * @param {string} name Controller name. 189 | * @param {Function} constructor Controller constructor function. 190 | * @description 191 | * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 192 | */ 193 | controller: invokeLater('$controllerProvider', 'register'), 194 | 195 | /** 196 | * @ngdoc method 197 | * @name angular.Module#directive 198 | * @methodOf angular.Module 199 | * @param {string} name directive name 200 | * @param {Function} directiveFactory Factory function for creating new instance of 201 | * directives. 202 | * @description 203 | * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 204 | */ 205 | directive: invokeLater('$compileProvider', 'directive'), 206 | 207 | /** 208 | * @ngdoc method 209 | * @name angular.Module#config 210 | * @methodOf angular.Module 211 | * @param {Function} configFn Execute this function on module load. Useful for service 212 | * configuration. 213 | * @description 214 | * Use this method to register work which needs to be performed on module loading. 215 | */ 216 | config: config, 217 | 218 | /** 219 | * @ngdoc method 220 | * @name angular.Module#run 221 | * @methodOf angular.Module 222 | * @param {Function} initializationFn Execute this function after injector creation. 223 | * Useful for application initialization. 224 | * @description 225 | * Use this method to register work which should be performed when the injector is done 226 | * loading all modules. 227 | */ 228 | run: function(block) { 229 | runBlocks.push(block); 230 | return this; 231 | } 232 | }; 233 | 234 | if (configFn) { 235 | config(configFn); 236 | } 237 | 238 | return moduleInstance; 239 | 240 | /** 241 | * @param {string} provider 242 | * @param {string} method 243 | * @param {String=} insertMethod 244 | * @returns {angular.Module} 245 | */ 246 | function invokeLater(provider, method, insertMethod) { 247 | return function() { 248 | invokeQueue[insertMethod || 'push']([provider, method, arguments]); 249 | return moduleInstance; 250 | } 251 | } 252 | }); 253 | }; 254 | }); 255 | 256 | } 257 | )(window); 258 | 259 | /** 260 | * Closure compiler type information 261 | * 262 | * @typedef { { 263 | * requires: !Array., 264 | * invokeQueue: !Array.>, 265 | * 266 | * service: function(string, Function):angular.Module, 267 | * factory: function(string, Function):angular.Module, 268 | * value: function(string, *):angular.Module, 269 | * 270 | * filter: function(string, Function):angular.Module, 271 | * 272 | * init: function(Function):angular.Module 273 | * } } 274 | */ 275 | angular.Module; 276 | 277 | -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/angular/angular-loader.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.5 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(i){'use strict';function d(c,b,e){return c[b]||(c[b]=e())}return d(d(i,"angular",Object),"module",function(){var c={};return function(b,e,f){e&&c.hasOwnProperty(b)&&(c[b]=null);return d(c,b,function(){function a(a,b,d){return function(){c[d||"push"]([a,b,arguments]);return g}}if(!e)throw Error("No module: "+b);var c=[],d=[],h=a("$injector","invoke"),g={_invokeQueue:c,_runBlocks:d,requires:e,name:b,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"), 7 | value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:h,run:function(a){d.push(a);return this}};f&&h(f);return g})}})})(window); 8 | -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/angular/angular-resource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.5 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngResource 12 | * @description 13 | */ 14 | 15 | /** 16 | * @ngdoc object 17 | * @name ngResource.$resource 18 | * @requires $http 19 | * 20 | * @description 21 | * A factory which creates a resource object that lets you interact with 22 | * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. 23 | * 24 | * The returned resource object has action methods which provide high-level behaviors without 25 | * the need to interact with the low level {@link ng.$http $http} service. 26 | * 27 | * @param {string} url A parameterized URL template with parameters prefixed by `:` as in 28 | * `/user/:username`. If you are using a URL with a port number (e.g. 29 | * `http://example.com:8080/api`), you'll need to escape the colon character before the port 30 | * number, like this: `$resource('http://example.com\\:8080/api')`. 31 | * 32 | * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 33 | * `actions` methods. 34 | * 35 | * Each key value in the parameter object is first bound to url template if present and then any 36 | * excess keys are appended to the url search query after the `?`. 37 | * 38 | * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 39 | * URL `/path/greet?salutation=Hello`. 40 | * 41 | * If the parameter value is prefixed with `@` then the value of that parameter is extracted from 42 | * the data object (useful for non-GET operations). 43 | * 44 | * @param {Object.=} actions Hash with declaration of custom action that should extend the 45 | * default set of resource actions. The declaration should be created in the following format: 46 | * 47 | * {action1: {method:?, params:?, isArray:?}, 48 | * action2: {method:?, params:?, isArray:?}, 49 | * ...} 50 | * 51 | * Where: 52 | * 53 | * - `action` – {string} – The name of action. This name becomes the name of the method on your 54 | * resource object. 55 | * - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, 56 | * and `JSONP` 57 | * - `params` – {object=} – Optional set of pre-bound parameters for this action. 58 | * - isArray – {boolean=} – If true then the returned object for this action is an array, see 59 | * `returns` section. 60 | * 61 | * @returns {Object} A resource "class" object with methods for the default set of resource actions 62 | * optionally extended with custom `actions`. The default set contains these actions: 63 | * 64 | * { 'get': {method:'GET'}, 65 | * 'save': {method:'POST'}, 66 | * 'query': {method:'GET', isArray:true}, 67 | * 'remove': {method:'DELETE'}, 68 | * 'delete': {method:'DELETE'} }; 69 | * 70 | * Calling these methods invoke an {@link ng.$http} with the specified http method, 71 | * destination and parameters. When the data is returned from the server then the object is an 72 | * instance of the resource class. The actions `save`, `remove` and `delete` are available on it 73 | * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, 74 | * read, update, delete) on server-side data like this: 75 | *
 76 |         var User = $resource('/user/:userId', {userId:'@id'});
 77 |         var user = User.get({userId:123}, function() {
 78 |           user.abc = true;
 79 |           user.$save();
 80 |         });
 81 |      
82 | * 83 | * It is important to realize that invoking a $resource object method immediately returns an 84 | * empty reference (object or array depending on `isArray`). Once the data is returned from the 85 | * server the existing reference is populated with the actual data. This is a useful trick since 86 | * usually the resource is assigned to a model which is then rendered by the view. Having an empty 87 | * object results in no rendering, once the data arrives from the server then the object is 88 | * populated with the data and the view automatically re-renders itself showing the new data. This 89 | * means that in most case one never has to write a callback function for the action methods. 90 | * 91 | * The action methods on the class object or instance object can be invoked with the following 92 | * parameters: 93 | * 94 | * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` 95 | * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` 96 | * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 97 | * 98 | * 99 | * @example 100 | * 101 | * # Credit card resource 102 | * 103 | *
104 |      // Define CreditCard class
105 |      var CreditCard = $resource('/user/:userId/card/:cardId',
106 |       {userId:123, cardId:'@id'}, {
107 |        charge: {method:'POST', params:{charge:true}}
108 |       });
109 | 
110 |      // We can retrieve a collection from the server
111 |      var cards = CreditCard.query(function() {
112 |        // GET: /user/123/card
113 |        // server returns: [ {id:456, number:'1234', name:'Smith'} ];
114 | 
115 |        var card = cards[0];
116 |        // each item is an instance of CreditCard
117 |        expect(card instanceof CreditCard).toEqual(true);
118 |        card.name = "J. Smith";
119 |        // non GET methods are mapped onto the instances
120 |        card.$save();
121 |        // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
122 |        // server returns: {id:456, number:'1234', name: 'J. Smith'};
123 | 
124 |        // our custom method is mapped as well.
125 |        card.$charge({amount:9.99});
126 |        // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
127 |      });
128 | 
129 |      // we can create an instance as well
130 |      var newCard = new CreditCard({number:'0123'});
131 |      newCard.name = "Mike Smith";
132 |      newCard.$save();
133 |      // POST: /user/123/card {number:'0123', name:'Mike Smith'}
134 |      // server returns: {id:789, number:'01234', name: 'Mike Smith'};
135 |      expect(newCard.id).toEqual(789);
136 |  * 
137 | * 138 | * The object returned from this function execution is a resource "class" which has "static" method 139 | * for each action in the definition. 140 | * 141 | * Calling these methods invoke `$http` on the `url` template with the given `method` and `params`. 142 | * When the data is returned from the server then the object is an instance of the resource type and 143 | * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 144 | * operations (create, read, update, delete) on server-side data. 145 | 146 |
147 |      var User = $resource('/user/:userId', {userId:'@id'});
148 |      var user = User.get({userId:123}, function() {
149 |        user.abc = true;
150 |        user.$save();
151 |      });
152 |    
153 | * 154 | * It's worth noting that the success callback for `get`, `query` and other method gets passed 155 | * in the response that came from the server as well as $http header getter function, so one 156 | * could rewrite the above example and get access to http headers as: 157 | * 158 |
159 |      var User = $resource('/user/:userId', {userId:'@id'});
160 |      User.get({userId:123}, function(u, getResponseHeaders){
161 |        u.abc = true;
162 |        u.$save(function(u, putResponseHeaders) {
163 |          //u => saved user object
164 |          //putResponseHeaders => $http header getter
165 |        });
166 |      });
167 |    
168 | 169 | * # Buzz client 170 | 171 | Let's look at what a buzz client created with the `$resource` service looks like: 172 | 173 | 174 | 194 | 195 |
196 | 197 | 198 |
199 |
200 |

201 | 202 | {{item.actor.name}} 203 | Expand replies: {{item.links.replies[0].count}} 204 |

205 | {{item.object.content | html}} 206 |
207 | 208 | {{reply.actor.name}}: {{reply.content | html}} 209 |
210 |
211 |
212 |
213 | 214 | 215 |
216 | */ 217 | angular.module('ngResource', ['ng']). 218 | factory('$resource', ['$http', '$parse', function($http, $parse) { 219 | var DEFAULT_ACTIONS = { 220 | 'get': {method:'GET'}, 221 | 'save': {method:'POST'}, 222 | 'query': {method:'GET', isArray:true}, 223 | 'remove': {method:'DELETE'}, 224 | 'delete': {method:'DELETE'} 225 | }; 226 | var noop = angular.noop, 227 | forEach = angular.forEach, 228 | extend = angular.extend, 229 | copy = angular.copy, 230 | isFunction = angular.isFunction, 231 | getter = function(obj, path) { 232 | return $parse(path)(obj); 233 | }; 234 | 235 | /** 236 | * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 237 | * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 238 | * segments: 239 | * segment = *pchar 240 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 241 | * pct-encoded = "%" HEXDIG HEXDIG 242 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 243 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 244 | * / "*" / "+" / "," / ";" / "=" 245 | */ 246 | function encodeUriSegment(val) { 247 | return encodeUriQuery(val, true). 248 | replace(/%26/gi, '&'). 249 | replace(/%3D/gi, '='). 250 | replace(/%2B/gi, '+'); 251 | } 252 | 253 | 254 | /** 255 | * This method is intended for encoding *key* or *value* parts of query component. We need a custom 256 | * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be 257 | * encoded per http://tools.ietf.org/html/rfc3986: 258 | * query = *( pchar / "/" / "?" ) 259 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 260 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 261 | * pct-encoded = "%" HEXDIG HEXDIG 262 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 263 | * / "*" / "+" / "," / ";" / "=" 264 | */ 265 | function encodeUriQuery(val, pctEncodeSpaces) { 266 | return encodeURIComponent(val). 267 | replace(/%40/gi, '@'). 268 | replace(/%3A/gi, ':'). 269 | replace(/%24/g, '$'). 270 | replace(/%2C/gi, ','). 271 | replace((pctEncodeSpaces ? null : /%20/g), '+'); 272 | } 273 | 274 | function Route(template, defaults) { 275 | this.template = template = template + '#'; 276 | this.defaults = defaults || {}; 277 | var urlParams = this.urlParams = {}; 278 | forEach(template.split(/\W/), function(param){ 279 | if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(template))) { 280 | urlParams[param] = true; 281 | } 282 | }); 283 | this.template = template.replace(/\\:/g, ':'); 284 | } 285 | 286 | Route.prototype = { 287 | url: function(params) { 288 | var self = this, 289 | url = this.template, 290 | val, 291 | encodedVal; 292 | 293 | params = params || {}; 294 | forEach(this.urlParams, function(_, urlParam){ 295 | val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 296 | if (angular.isDefined(val) && val !== null) { 297 | encodedVal = encodeUriSegment(val); 298 | url = url.replace(new RegExp(":" + urlParam + "(\\W)", "g"), encodedVal + "$1"); 299 | } else { 300 | url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W)", "g"), function(match, 301 | leadingSlashes, tail) { 302 | if (tail.charAt(0) == '/') { 303 | return tail; 304 | } else { 305 | return leadingSlashes + tail; 306 | } 307 | }); 308 | } 309 | }); 310 | url = url.replace(/\/?#$/, ''); 311 | var query = []; 312 | forEach(params, function(value, key){ 313 | if (!self.urlParams[key]) { 314 | query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value)); 315 | } 316 | }); 317 | query.sort(); 318 | url = url.replace(/\/*$/, ''); 319 | return url + (query.length ? '?' + query.join('&') : ''); 320 | } 321 | }; 322 | 323 | 324 | function ResourceFactory(url, paramDefaults, actions) { 325 | var route = new Route(url); 326 | 327 | actions = extend({}, DEFAULT_ACTIONS, actions); 328 | 329 | function extractParams(data, actionParams){ 330 | var ids = {}; 331 | actionParams = extend({}, paramDefaults, actionParams); 332 | forEach(actionParams, function(value, key){ 333 | ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; 334 | }); 335 | return ids; 336 | } 337 | 338 | function Resource(value){ 339 | copy(value || {}, this); 340 | } 341 | 342 | forEach(actions, function(action, name) { 343 | action.method = angular.uppercase(action.method); 344 | var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH'; 345 | Resource[name] = function(a1, a2, a3, a4) { 346 | var params = {}; 347 | var data; 348 | var success = noop; 349 | var error = null; 350 | switch(arguments.length) { 351 | case 4: 352 | error = a4; 353 | success = a3; 354 | //fallthrough 355 | case 3: 356 | case 2: 357 | if (isFunction(a2)) { 358 | if (isFunction(a1)) { 359 | success = a1; 360 | error = a2; 361 | break; 362 | } 363 | 364 | success = a2; 365 | error = a3; 366 | //fallthrough 367 | } else { 368 | params = a1; 369 | data = a2; 370 | success = a3; 371 | break; 372 | } 373 | case 1: 374 | if (isFunction(a1)) success = a1; 375 | else if (hasBody) data = a1; 376 | else params = a1; 377 | break; 378 | case 0: break; 379 | default: 380 | throw "Expected between 0-4 arguments [params, data, success, error], got " + 381 | arguments.length + " arguments."; 382 | } 383 | 384 | var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); 385 | $http({ 386 | method: action.method, 387 | url: route.url(extend({}, extractParams(data, action.params || {}), params)), 388 | data: data 389 | }).then(function(response) { 390 | var data = response.data; 391 | 392 | if (data) { 393 | if (action.isArray) { 394 | value.length = 0; 395 | forEach(data, function(item) { 396 | value.push(new Resource(item)); 397 | }); 398 | } else { 399 | copy(data, value); 400 | } 401 | } 402 | (success||noop)(value, response.headers); 403 | }, error); 404 | 405 | return value; 406 | }; 407 | 408 | 409 | Resource.prototype['$' + name] = function(a1, a2, a3) { 410 | var params = extractParams(this), 411 | success = noop, 412 | error; 413 | 414 | switch(arguments.length) { 415 | case 3: params = a1; success = a2; error = a3; break; 416 | case 2: 417 | case 1: 418 | if (isFunction(a1)) { 419 | success = a1; 420 | error = a2; 421 | } else { 422 | params = a1; 423 | success = a2 || noop; 424 | } 425 | case 0: break; 426 | default: 427 | throw "Expected between 1-3 arguments [params, success, error], got " + 428 | arguments.length + " arguments."; 429 | } 430 | var data = hasBody ? this : undefined; 431 | Resource[name].call(this, params, data, success, error); 432 | }; 433 | }); 434 | 435 | Resource.bind = function(additionalParamDefaults){ 436 | return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); 437 | }; 438 | 439 | return Resource; 440 | } 441 | 442 | return ResourceFactory; 443 | }]); 444 | 445 | })(window, window.angular); 446 | -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/angular/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.5 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(C,d,w){'use strict';d.module("ngResource",["ng"]).factory("$resource",["$http","$parse",function(x,y){function s(b,e){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(e?null:/%20/g,"+")}function t(b,e){this.template=b+="#";this.defaults=e||{};var a=this.urlParams={};h(b.split(/\W/),function(f){f&&RegExp("(^|[^\\\\]):"+f+"\\W").test(b)&&(a[f]=!0)});this.template=b.replace(/\\:/g,":")}function u(b,e,a){function f(m,a){var b= 7 | {},a=o({},e,a);h(a,function(a,z){var c;a.charAt&&a.charAt(0)=="@"?(c=a.substr(1),c=y(c)(m)):c=a;b[z]=c});return b}function g(a){v(a||{},this)}var k=new t(b),a=o({},A,a);h(a,function(a,b){a.method=d.uppercase(a.method);var e=a.method=="POST"||a.method=="PUT"||a.method=="PATCH";g[b]=function(b,c,d,B){var j={},i,l=p,q=null;switch(arguments.length){case 4:q=B,l=d;case 3:case 2:if(r(c)){if(r(b)){l=b;q=c;break}l=c;q=d}else{j=b;i=c;l=d;break}case 1:r(b)?l=b:e?i=b:j=b;break;case 0:break;default:throw"Expected between 0-4 arguments [params, data, success, error], got "+ 8 | arguments.length+" arguments.";}var n=this instanceof g?this:a.isArray?[]:new g(i);x({method:a.method,url:k.url(o({},f(i,a.params||{}),j)),data:i}).then(function(b){var c=b.data;if(c)a.isArray?(n.length=0,h(c,function(a){n.push(new g(a))})):v(c,n);(l||p)(n,b.headers)},q);return n};g.prototype["$"+b]=function(a,d,h){var m=f(this),j=p,i;switch(arguments.length){case 3:m=a;j=d;i=h;break;case 2:case 1:r(a)?(j=a,i=d):(m=a,j=d||p);case 0:break;default:throw"Expected between 1-3 arguments [params, success, error], got "+ 9 | arguments.length+" arguments.";}g[b].call(this,m,e?this:w,j,i)}});g.bind=function(d){return u(b,o({},e,d),a)};return g}var A={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},p=d.noop,h=d.forEach,o=d.extend,v=d.copy,r=d.isFunction;t.prototype={url:function(b){var e=this,a=this.template,f,g,b=b||{};h(this.urlParams,function(h,c){f=b.hasOwnProperty(c)?b[c]:e.defaults[c];d.isDefined(f)&&f!==null?(g=s(f,!0).replace(/%26/gi,"&").replace(/%3D/gi, 10 | "=").replace(/%2B/gi,"+"),a=a.replace(RegExp(":"+c+"(\\W)","g"),g+"$1")):a=a.replace(RegExp("(/?):"+c+"(\\W)","g"),function(a,b,c){return c.charAt(0)=="/"?c:b+c})});var a=a.replace(/\/?#$/,""),k=[];h(b,function(a,b){e.urlParams[b]||k.push(s(b)+"="+s(a))});k.sort();a=a.replace(/\/*$/,"");return a+(k.length?"?"+k.join("&"):"")}};return u}])})(window,window.angular); 11 | -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/angular/angular-sanitize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.5 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngSanitize 12 | * @description 13 | */ 14 | 15 | /* 16 | * HTML Parser By Misko Hevery (misko@hevery.com) 17 | * based on: HTML Parser By John Resig (ejohn.org) 18 | * Original code by Erik Arvidsson, Mozilla Public License 19 | * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js 20 | * 21 | * // Use like so: 22 | * htmlParser(htmlString, { 23 | * start: function(tag, attrs, unary) {}, 24 | * end: function(tag) {}, 25 | * chars: function(text) {}, 26 | * comment: function(text) {} 27 | * }); 28 | * 29 | */ 30 | 31 | 32 | /** 33 | * @ngdoc service 34 | * @name ngSanitize.$sanitize 35 | * @function 36 | * 37 | * @description 38 | * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are 39 | * then serialized back to properly escaped html string. This means that no unsafe input can make 40 | * it into the returned string, however, since our parser is more strict than a typical browser 41 | * parser, it's possible that some obscure input, which would be recognized as valid HTML by a 42 | * browser, won't make it through the sanitizer. 43 | * 44 | * @param {string} html Html input. 45 | * @returns {string} Sanitized html. 46 | * 47 | * @example 48 | 49 | 50 | 58 |
59 | Snippet: 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
FilterSourceRendered
html filter 69 |
<div ng-bind-html="snippet">
</div>
70 |
72 |
73 |
no filter
<div ng-bind="snippet">
</div>
unsafe html filter
<div ng-bind-html-unsafe="snippet">
</div>
86 |
87 |
88 | 89 | it('should sanitize the html snippet ', function() { 90 | expect(using('#html-filter').element('div').html()). 91 | toBe('

an html\nclick here\nsnippet

'); 92 | }); 93 | 94 | it('should escape snippet without any filter', function() { 95 | expect(using('#escaped-html').element('div').html()). 96 | toBe("<p style=\"color:blue\">an html\n" + 97 | "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 98 | "snippet</p>"); 99 | }); 100 | 101 | it('should inline raw snippet if filtered as unsafe', function() { 102 | expect(using('#html-unsafe-filter').element("div").html()). 103 | toBe("

an html\n" + 104 | "click here\n" + 105 | "snippet

"); 106 | }); 107 | 108 | it('should update', function() { 109 | input('snippet').enter('new text'); 110 | expect(using('#html-filter').binding('snippet')).toBe('new text'); 111 | expect(using('#escaped-html').element('div').html()).toBe("new <b>text</b>"); 112 | expect(using('#html-unsafe-filter').binding("snippet")).toBe('new text'); 113 | }); 114 |
115 |
116 | */ 117 | var $sanitize = function(html) { 118 | var buf = []; 119 | htmlParser(html, htmlSanitizeWriter(buf)); 120 | return buf.join(''); 121 | }; 122 | 123 | 124 | // Regular Expressions for parsing tags and attributes 125 | var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, 126 | END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/, 127 | ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, 128 | BEGIN_TAG_REGEXP = /^/g, 131 | CDATA_REGEXP = //g, 132 | URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/, 133 | NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character) 134 | 135 | 136 | // Good source of info about elements and attributes 137 | // http://dev.w3.org/html5/spec/Overview.html#semantics 138 | // http://simon.html5.org/html-elements 139 | 140 | // Safe Void Elements - HTML5 141 | // http://dev.w3.org/html5/spec/Overview.html#void-elements 142 | var voidElements = makeMap("area,br,col,hr,img,wbr"); 143 | 144 | // Elements that you can, intentionally, leave open (and which close themselves) 145 | // http://dev.w3.org/html5/spec/Overview.html#optional-tags 146 | var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), 147 | optionalEndTagInlineElements = makeMap("rp,rt"), 148 | optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements); 149 | 150 | // Safe Block Elements - HTML5 151 | var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article,aside," + 152 | "blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6," + 153 | "header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); 154 | 155 | // Inline Elements - HTML5 156 | var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b,bdi,bdo," + 157 | "big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small," + 158 | "span,strike,strong,sub,sup,time,tt,u,var")); 159 | 160 | 161 | // Special Elements (can contain anything) 162 | var specialElements = makeMap("script,style"); 163 | 164 | var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements); 165 | 166 | //Attributes that have href and hence need to be sanitized 167 | var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap"); 168 | var validAttrs = angular.extend({}, uriAttrs, makeMap( 169 | 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ 170 | 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ 171 | 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ 172 | 'scope,scrolling,shape,span,start,summary,target,title,type,'+ 173 | 'valign,value,vspace,width')); 174 | 175 | function makeMap(str) { 176 | var obj = {}, items = str.split(','), i; 177 | for (i = 0; i < items.length; i++) obj[items[i]] = true; 178 | return obj; 179 | } 180 | 181 | 182 | /** 183 | * @example 184 | * htmlParser(htmlString, { 185 | * start: function(tag, attrs, unary) {}, 186 | * end: function(tag) {}, 187 | * chars: function(text) {}, 188 | * comment: function(text) {} 189 | * }); 190 | * 191 | * @param {string} html string 192 | * @param {object} handler 193 | */ 194 | function htmlParser( html, handler ) { 195 | var index, chars, match, stack = [], last = html; 196 | stack.last = function() { return stack[ stack.length - 1 ]; }; 197 | 198 | while ( html ) { 199 | chars = true; 200 | 201 | // Make sure we're not in a script or style element 202 | if ( !stack.last() || !specialElements[ stack.last() ] ) { 203 | 204 | // Comment 205 | if ( html.indexOf(""); 207 | 208 | if ( index >= 0 ) { 209 | if (handler.comment) handler.comment( html.substring( 4, index ) ); 210 | html = html.substring( index + 3 ); 211 | chars = false; 212 | } 213 | 214 | // end tag 215 | } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { 216 | match = html.match( END_TAG_REGEXP ); 217 | 218 | if ( match ) { 219 | html = html.substring( match[0].length ); 220 | match[0].replace( END_TAG_REGEXP, parseEndTag ); 221 | chars = false; 222 | } 223 | 224 | // start tag 225 | } else if ( BEGIN_TAG_REGEXP.test(html) ) { 226 | match = html.match( START_TAG_REGEXP ); 227 | 228 | if ( match ) { 229 | html = html.substring( match[0].length ); 230 | match[0].replace( START_TAG_REGEXP, parseStartTag ); 231 | chars = false; 232 | } 233 | } 234 | 235 | if ( chars ) { 236 | index = html.indexOf("<"); 237 | 238 | var text = index < 0 ? html : html.substring( 0, index ); 239 | html = index < 0 ? "" : html.substring( index ); 240 | 241 | if (handler.chars) handler.chars( decodeEntities(text) ); 242 | } 243 | 244 | } else { 245 | html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){ 246 | text = text. 247 | replace(COMMENT_REGEXP, "$1"). 248 | replace(CDATA_REGEXP, "$1"); 249 | 250 | if (handler.chars) handler.chars( decodeEntities(text) ); 251 | 252 | return ""; 253 | }); 254 | 255 | parseEndTag( "", stack.last() ); 256 | } 257 | 258 | if ( html == last ) { 259 | throw "Parse Error: " + html; 260 | } 261 | last = html; 262 | } 263 | 264 | // Clean up any remaining tags 265 | parseEndTag(); 266 | 267 | function parseStartTag( tag, tagName, rest, unary ) { 268 | tagName = angular.lowercase(tagName); 269 | if ( blockElements[ tagName ] ) { 270 | while ( stack.last() && inlineElements[ stack.last() ] ) { 271 | parseEndTag( "", stack.last() ); 272 | } 273 | } 274 | 275 | if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { 276 | parseEndTag( "", tagName ); 277 | } 278 | 279 | unary = voidElements[ tagName ] || !!unary; 280 | 281 | if ( !unary ) 282 | stack.push( tagName ); 283 | 284 | var attrs = {}; 285 | 286 | rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) { 287 | var value = doubleQuotedValue 288 | || singleQoutedValue 289 | || unqoutedValue 290 | || ''; 291 | 292 | attrs[name] = decodeEntities(value); 293 | }); 294 | if (handler.start) handler.start( tagName, attrs, unary ); 295 | } 296 | 297 | function parseEndTag( tag, tagName ) { 298 | var pos = 0, i; 299 | tagName = angular.lowercase(tagName); 300 | if ( tagName ) 301 | // Find the closest opened tag of the same type 302 | for ( pos = stack.length - 1; pos >= 0; pos-- ) 303 | if ( stack[ pos ] == tagName ) 304 | break; 305 | 306 | if ( pos >= 0 ) { 307 | // Close all the open elements, up the stack 308 | for ( i = stack.length - 1; i >= pos; i-- ) 309 | if (handler.end) handler.end( stack[ i ] ); 310 | 311 | // Remove the open elements from the stack 312 | stack.length = pos; 313 | } 314 | } 315 | } 316 | 317 | /** 318 | * decodes all entities into regular string 319 | * @param value 320 | * @returns {string} A string with decoded entities. 321 | */ 322 | var hiddenPre=document.createElement("pre"); 323 | function decodeEntities(value) { 324 | hiddenPre.innerHTML=value.replace(//g, '>'); 343 | } 344 | 345 | /** 346 | * create an HTML/XML writer which writes to buffer 347 | * @param {Array} buf use buf.jain('') to get out sanitized html string 348 | * @returns {object} in the form of { 349 | * start: function(tag, attrs, unary) {}, 350 | * end: function(tag) {}, 351 | * chars: function(text) {}, 352 | * comment: function(text) {} 353 | * } 354 | */ 355 | function htmlSanitizeWriter(buf){ 356 | var ignore = false; 357 | var out = angular.bind(buf, buf.push); 358 | return { 359 | start: function(tag, attrs, unary){ 360 | tag = angular.lowercase(tag); 361 | if (!ignore && specialElements[tag]) { 362 | ignore = tag; 363 | } 364 | if (!ignore && validElements[tag] == true) { 365 | out('<'); 366 | out(tag); 367 | angular.forEach(attrs, function(value, key){ 368 | var lkey=angular.lowercase(key); 369 | if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) { 370 | out(' '); 371 | out(key); 372 | out('="'); 373 | out(encodeEntities(value)); 374 | out('"'); 375 | } 376 | }); 377 | out(unary ? '/>' : '>'); 378 | } 379 | }, 380 | end: function(tag){ 381 | tag = angular.lowercase(tag); 382 | if (!ignore && validElements[tag] == true) { 383 | out(''); 386 | } 387 | if (tag == ignore) { 388 | ignore = false; 389 | } 390 | }, 391 | chars: function(chars){ 392 | if (!ignore) { 393 | out(encodeEntities(chars)); 394 | } 395 | } 396 | }; 397 | } 398 | 399 | 400 | // define ngSanitize module and register $sanitize service 401 | angular.module('ngSanitize', []).value('$sanitize', $sanitize); 402 | 403 | /** 404 | * @ngdoc directive 405 | * @name ngSanitize.directive:ngBindHtml 406 | * 407 | * @description 408 | * Creates a binding that will sanitize the result of evaluating the `expression` with the 409 | * {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element. 410 | * 411 | * See {@link ngSanitize.$sanitize $sanitize} docs for examples. 412 | * 413 | * @element ANY 414 | * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 415 | */ 416 | angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) { 417 | return function(scope, element, attr) { 418 | element.addClass('ng-binding').data('$binding', attr.ngBindHtml); 419 | scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) { 420 | value = $sanitize(value); 421 | element.html(value || ''); 422 | }); 423 | }; 424 | }]); 425 | /** 426 | * @ngdoc filter 427 | * @name ngSanitize.filter:linky 428 | * @function 429 | * 430 | * @description 431 | * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and 432 | * plain email address links. 433 | * 434 | * @param {string} text Input text. 435 | * @returns {string} Html-linkified text. 436 | * 437 | * @usage 438 | 439 | * 440 | * @example 441 | 442 | 443 | 453 |
454 | Snippet: 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 466 | 469 | 470 | 471 | 472 | 473 | 474 | 475 |
FilterSourceRendered
linky filter 464 |
<div ng-bind-html="snippet | linky">
</div>
465 |
467 |
468 |
no filter
<div ng-bind="snippet">
</div>
476 | 477 | 478 | it('should linkify the snippet with urls', function() { 479 | expect(using('#linky-filter').binding('snippet | linky')). 480 | toBe('Pretty text with some links: ' + 481 | 'http://angularjs.org/, ' + 482 | 'us@somewhere.org, ' + 483 | 'another@somewhere.org, ' + 484 | 'and one more: ftp://127.0.0.1/.'); 485 | }); 486 | 487 | it ('should not linkify snippet without the linky filter', function() { 488 | expect(using('#escaped-html').binding('snippet')). 489 | toBe("Pretty text with some links:\n" + 490 | "http://angularjs.org/,\n" + 491 | "mailto:us@somewhere.org,\n" + 492 | "another@somewhere.org,\n" + 493 | "and one more: ftp://127.0.0.1/."); 494 | }); 495 | 496 | it('should update', function() { 497 | input('snippet').enter('new http://link.'); 498 | expect(using('#linky-filter').binding('snippet | linky')). 499 | toBe('new http://link.'); 500 | expect(using('#escaped-html').binding('snippet')).toBe('new http://link.'); 501 | }); 502 | 503 | 504 | */ 505 | angular.module('ngSanitize').filter('linky', function() { 506 | var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/, 507 | MAILTO_REGEXP = /^mailto:/; 508 | 509 | return function(text) { 510 | if (!text) return text; 511 | var match; 512 | var raw = text; 513 | var html = []; 514 | // TODO(vojta): use $sanitize instead 515 | var writer = htmlSanitizeWriter(html); 516 | var url; 517 | var i; 518 | while ((match = raw.match(LINKY_URL_REGEXP))) { 519 | // We can not end in these as they are sometimes found at the end of the sentence 520 | url = match[0]; 521 | // if we did not match ftp/http/mailto then assume mailto 522 | if (match[2] == match[3]) url = 'mailto:' + url; 523 | i = match.index; 524 | writer.chars(raw.substr(0, i)); 525 | writer.start('a', {href:url}); 526 | writer.chars(match[0].replace(MAILTO_REGEXP, '')); 527 | writer.end('a'); 528 | raw = raw.substring(i + match[0].length); 529 | } 530 | writer.chars(raw); 531 | return html.join(''); 532 | }; 533 | }); 534 | 535 | })(window, window.angular); 536 | -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/angular/angular-sanitize.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.5 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(I,g){'use strict';function i(a){var d={},a=a.split(","),b;for(b=0;b=0;e--)if(f[e]==b)break;if(e>=0){for(c=f.length-1;c>=e;c--)d.end&&d.end(f[c]);f.length= 7 | e}}var c,h,f=[],j=a;for(f.last=function(){return f[f.length-1]};a;){h=!0;if(!f.last()||!q[f.last()]){if(a.indexOf("<\!--")===0)c=a.indexOf("--\>"),c>=0&&(d.comment&&d.comment(a.substring(4,c)),a=a.substring(c+3),h=!1);else if(B.test(a)){if(c=a.match(r))a=a.substring(c[0].length),c[0].replace(r,e),h=!1}else if(C.test(a)&&(c=a.match(s)))a=a.substring(c[0].length),c[0].replace(s,b),h=!1;h&&(c=a.indexOf("<"),h=c<0?a:a.substring(0,c),a=c<0?"":a.substring(c),d.chars&&d.chars(k(h)))}else a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+ 8 | f.last()+"[^>]*>","i"),function(b,a){a=a.replace(D,"$1").replace(E,"$1");d.chars&&d.chars(k(a));return""}),e("",f.last());if(a==j)throw"Parse Error: "+a;j=a}e()}function k(a){l.innerHTML=a.replace(//g,">")}function u(a){var d=!1,b=g.bind(a,a.push);return{start:function(a,c,h){a=g.lowercase(a);!d&&q[a]&&(d=a);!d&&v[a]== 9 | !0&&(b("<"),b(a),g.forEach(c,function(a,c){var e=g.lowercase(c);if(G[e]==!0&&(w[e]!==!0||a.match(H)))b(" "),b(c),b('="'),b(t(a)),b('"')}),b(h?"/>":">"))},end:function(a){a=g.lowercase(a);!d&&v[a]==!0&&(b(""));a==d&&(d=!1)},chars:function(a){d||b(t(a))}}}var s=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,r=/^<\s*\/\s*([\w:-]+)[^>]*>/,A=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,C=/^/g, 10 | E=//g,H=/^((ftp|https?):\/\/|mailto:|#)/,F=/([^\#-~| |!])/g,p=i("area,br,col,hr,img,wbr"),x=i("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=i("rp,rt"),o=g.extend({},y,x),m=g.extend({},x,i("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),n=g.extend({},y,i("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")), 11 | q=i("script,style"),v=g.extend({},p,m,n,o),w=i("background,cite,href,longdesc,src,usemap"),G=g.extend({},w,i("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),l=document.createElement("pre");g.module("ngSanitize",[]).value("$sanitize",function(a){var d=[]; 12 | z(a,u(d));return d.join("")});g.module("ngSanitize").directive("ngBindHtml",["$sanitize",function(a){return function(d,b,e){b.addClass("ng-binding").data("$binding",e.ngBindHtml);d.$watch(e.ngBindHtml,function(c){c=a(c);b.html(c||"")})}}]);g.module("ngSanitize").filter("linky",function(){var a=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,d=/^mailto:/;return function(b){if(!b)return b;for(var e=b,c=[],h=u(c),f,g;b=e.match(a);)f=b[0],b[2]==b[3]&&(f="mailto:"+f),g=b.index, 13 | h.chars(e.substr(0,g)),h.start("a",{href:f}),h.chars(b[0].replace(d,"")),h.end("a"),e=e.substring(g+b[0].length);h.chars(e);return c.join("")}})})(window,window.angular); 14 | -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/angular/version.txt: -------------------------------------------------------------------------------- 1 | 1.0.5 2 | -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/bootstrap/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.2 by @fat and @mdo 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | * 6 | * Designed and built with all the love in the world by @mdo and @fat. 7 | */ 8 | 9 | .btn-default, 10 | .btn-primary, 11 | .btn-success, 12 | .btn-info, 13 | .btn-warning, 14 | .btn-danger { 15 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); 16 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 17 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 18 | } 19 | 20 | .btn-default:active, 21 | .btn-primary:active, 22 | .btn-success:active, 23 | .btn-info:active, 24 | .btn-warning:active, 25 | .btn-danger:active, 26 | .btn-default.active, 27 | .btn-primary.active, 28 | .btn-success.active, 29 | .btn-info.active, 30 | .btn-warning.active, 31 | .btn-danger.active { 32 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 33 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 34 | } 35 | 36 | .btn:active, 37 | .btn.active { 38 | background-image: none; 39 | } 40 | 41 | .btn-default { 42 | text-shadow: 0 1px 0 #fff; 43 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e0e0e0)); 44 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%); 45 | background-image: -moz-linear-gradient(top, #ffffff 0%, #e0e0e0 100%); 46 | background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%); 47 | background-repeat: repeat-x; 48 | border-color: #dbdbdb; 49 | border-color: #ccc; 50 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 51 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 52 | } 53 | 54 | .btn-default:hover, 55 | .btn-default:focus { 56 | background-color: #e0e0e0; 57 | background-position: 0 -15px; 58 | } 59 | 60 | .btn-default:active, 61 | .btn-default.active { 62 | background-color: #e0e0e0; 63 | border-color: #dbdbdb; 64 | } 65 | 66 | .btn-primary { 67 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#2d6ca2)); 68 | background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%); 69 | background-image: -moz-linear-gradient(top, #428bca 0%, #2d6ca2 100%); 70 | background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%); 71 | background-repeat: repeat-x; 72 | border-color: #2b669a; 73 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0); 74 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 75 | } 76 | 77 | .btn-primary:hover, 78 | .btn-primary:focus { 79 | background-color: #2d6ca2; 80 | background-position: 0 -15px; 81 | } 82 | 83 | .btn-primary:active, 84 | .btn-primary.active { 85 | background-color: #2d6ca2; 86 | border-color: #2b669a; 87 | } 88 | 89 | .btn-success { 90 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#419641)); 91 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 92 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #419641 100%); 93 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 94 | background-repeat: repeat-x; 95 | border-color: #3e8f3e; 96 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 97 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 98 | } 99 | 100 | .btn-success:hover, 101 | .btn-success:focus { 102 | background-color: #419641; 103 | background-position: 0 -15px; 104 | } 105 | 106 | .btn-success:active, 107 | .btn-success.active { 108 | background-color: #419641; 109 | border-color: #3e8f3e; 110 | } 111 | 112 | .btn-warning { 113 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#eb9316)); 114 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 115 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 116 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 117 | background-repeat: repeat-x; 118 | border-color: #e38d13; 119 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 120 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 121 | } 122 | 123 | .btn-warning:hover, 124 | .btn-warning:focus { 125 | background-color: #eb9316; 126 | background-position: 0 -15px; 127 | } 128 | 129 | .btn-warning:active, 130 | .btn-warning.active { 131 | background-color: #eb9316; 132 | border-color: #e38d13; 133 | } 134 | 135 | .btn-danger { 136 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c12e2a)); 137 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 138 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 139 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 140 | background-repeat: repeat-x; 141 | border-color: #b92c28; 142 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 143 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 144 | } 145 | 146 | .btn-danger:hover, 147 | .btn-danger:focus { 148 | background-color: #c12e2a; 149 | background-position: 0 -15px; 150 | } 151 | 152 | .btn-danger:active, 153 | .btn-danger.active { 154 | background-color: #c12e2a; 155 | border-color: #b92c28; 156 | } 157 | 158 | .btn-info { 159 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#2aabd2)); 160 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 161 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 162 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 163 | background-repeat: repeat-x; 164 | border-color: #28a4c9; 165 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 166 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 167 | } 168 | 169 | .btn-info:hover, 170 | .btn-info:focus { 171 | background-color: #2aabd2; 172 | background-position: 0 -15px; 173 | } 174 | 175 | .btn-info:active, 176 | .btn-info.active { 177 | background-color: #2aabd2; 178 | border-color: #28a4c9; 179 | } 180 | 181 | .thumbnail, 182 | .img-thumbnail { 183 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 184 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 185 | } 186 | 187 | .dropdown-menu > li > a:hover, 188 | .dropdown-menu > li > a:focus { 189 | background-color: #e8e8e8; 190 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8)); 191 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 192 | background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 193 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 194 | background-repeat: repeat-x; 195 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 196 | } 197 | 198 | .dropdown-menu > .active > a, 199 | .dropdown-menu > .active > a:hover, 200 | .dropdown-menu > .active > a:focus { 201 | background-color: #357ebd; 202 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 203 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 204 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 205 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 206 | background-repeat: repeat-x; 207 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 208 | } 209 | 210 | .navbar-default { 211 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8)); 212 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 213 | background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 214 | background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); 215 | background-repeat: repeat-x; 216 | border-radius: 4px; 217 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 218 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 219 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 220 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 221 | } 222 | 223 | .navbar-default .navbar-nav > .active > a { 224 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f3f3f3)); 225 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); 226 | background-image: -moz-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); 227 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%); 228 | background-repeat: repeat-x; 229 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0); 230 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); 231 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); 232 | } 233 | 234 | .navbar-brand, 235 | .navbar-nav > li > a { 236 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); 237 | } 238 | 239 | .navbar-inverse { 240 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222)); 241 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%); 242 | background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%); 243 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); 244 | background-repeat: repeat-x; 245 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 246 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 247 | } 248 | 249 | .navbar-inverse .navbar-nav > .active > a { 250 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#222222), to(#282828)); 251 | background-image: -webkit-linear-gradient(top, #222222 0%, #282828 100%); 252 | background-image: -moz-linear-gradient(top, #222222 0%, #282828 100%); 253 | background-image: linear-gradient(to bottom, #222222 0%, #282828 100%); 254 | background-repeat: repeat-x; 255 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0); 256 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); 257 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); 258 | } 259 | 260 | .navbar-inverse .navbar-brand, 261 | .navbar-inverse .navbar-nav > li > a { 262 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 263 | } 264 | 265 | .navbar-static-top, 266 | .navbar-fixed-top, 267 | .navbar-fixed-bottom { 268 | border-radius: 0; 269 | } 270 | 271 | .alert { 272 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); 273 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 274 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 275 | } 276 | 277 | .alert-success { 278 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc)); 279 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 280 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 281 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 282 | background-repeat: repeat-x; 283 | border-color: #b2dba1; 284 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 285 | } 286 | 287 | .alert-info { 288 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0)); 289 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 290 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 291 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 292 | background-repeat: repeat-x; 293 | border-color: #9acfea; 294 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 295 | } 296 | 297 | .alert-warning { 298 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0)); 299 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 300 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 301 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 302 | background-repeat: repeat-x; 303 | border-color: #f5e79e; 304 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 305 | } 306 | 307 | .alert-danger { 308 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3)); 309 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 310 | background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 311 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 312 | background-repeat: repeat-x; 313 | border-color: #dca7a7; 314 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 315 | } 316 | 317 | .progress { 318 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5)); 319 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 320 | background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 321 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 322 | background-repeat: repeat-x; 323 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 324 | } 325 | 326 | .progress-bar { 327 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); 328 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%); 329 | background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); 330 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 331 | background-repeat: repeat-x; 332 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 333 | } 334 | 335 | .progress-bar-success { 336 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); 337 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 338 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); 339 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 340 | background-repeat: repeat-x; 341 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 342 | } 343 | 344 | .progress-bar-info { 345 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); 346 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 347 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 348 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 349 | background-repeat: repeat-x; 350 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 351 | } 352 | 353 | .progress-bar-warning { 354 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); 355 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 356 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 357 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 358 | background-repeat: repeat-x; 359 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 360 | } 361 | 362 | .progress-bar-danger { 363 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); 364 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 365 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); 366 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 367 | background-repeat: repeat-x; 368 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 369 | } 370 | 371 | .list-group { 372 | border-radius: 4px; 373 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 374 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 375 | } 376 | 377 | .list-group-item.active, 378 | .list-group-item.active:hover, 379 | .list-group-item.active:focus { 380 | text-shadow: 0 -1px 0 #3071a9; 381 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3)); 382 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%); 383 | background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%); 384 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); 385 | background-repeat: repeat-x; 386 | border-color: #3278b3; 387 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); 388 | } 389 | 390 | .panel { 391 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 392 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 393 | } 394 | 395 | .panel-default > .panel-heading { 396 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8)); 397 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 398 | background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 399 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 400 | background-repeat: repeat-x; 401 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 402 | } 403 | 404 | .panel-primary > .panel-heading { 405 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 406 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 407 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 408 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 409 | background-repeat: repeat-x; 410 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 411 | } 412 | 413 | .panel-success > .panel-heading { 414 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6)); 415 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 416 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 417 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 418 | background-repeat: repeat-x; 419 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 420 | } 421 | 422 | .panel-info > .panel-heading { 423 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3)); 424 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 425 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 426 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 427 | background-repeat: repeat-x; 428 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 429 | } 430 | 431 | .panel-warning > .panel-heading { 432 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc)); 433 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 434 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 435 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 436 | background-repeat: repeat-x; 437 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 438 | } 439 | 440 | .panel-danger > .panel-heading { 441 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc)); 442 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 443 | background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 444 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 445 | background-repeat: repeat-x; 446 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 447 | } 448 | 449 | .well { 450 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5)); 451 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 452 | background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 453 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 454 | background-repeat: repeat-x; 455 | border-color: #dcdcdc; 456 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 457 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 458 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 459 | } -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/bootstrap/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.2 by @fat and @mdo 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | * 6 | * Designed and built with all the love in the world by @mdo and @fat. 7 | */ 8 | 9 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#e0e0e0));background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-moz-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe0e0e0',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#2d6ca2));background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:-moz-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);background-repeat:repeat-x;border-color:#2b669a;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff2d6ca2',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#419641));background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);background-repeat:repeat-x;border-color:#3e8f3e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff419641',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#eb9316));background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);background-repeat:repeat-x;border-color:#e38d13;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffeb9316',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c12e2a));background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);background-repeat:repeat-x;border-color:#b92c28;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc12e2a',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#2aabd2));background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);background-repeat:repeat-x;border-color:#28a4c9;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2aabd2',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-moz-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.navbar-default{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#f8f8f8));background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-moz-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff8f8f8',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-gradient(linear,left 0,left 100%,from(#ebebeb),to(#f3f3f3));background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:-moz-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff3f3f3',GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.075);box-shadow:inset 0 3px 9px rgba(0,0,0,0.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-gradient(linear,left 0,left 100%,from(#3c3c3c),to(#222));background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-moz-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-gradient(linear,left 0,left 100%,from(#222),to(#282828));background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:-moz-linear-gradient(top,#222 0,#282828 100%);background-image:linear-gradient(to bottom,#222 0,#282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff282828',GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.25);box-shadow:inset 0 3px 9px rgba(0,0,0,0.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#c8e5bc));background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;border-color:#b2dba1;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffc8e5bc',GradientType=0)}.alert-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#b9def0));background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;border-color:#9acfea;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffb9def0',GradientType=0)}.alert-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#f8efc0));background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;border-color:#f5e79e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fff8efc0',GradientType=0)}.alert-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#e7c3c3));background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-moz-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;border-color:#dca7a7;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffe7c3c3',GradientType=0)}.progress{background-image:-webkit-gradient(linear,left 0,left 100%,from(#ebebeb),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-moz-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff5f5f5',GradientType=0)}.progress-bar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.progress-bar-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.progress-bar-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.progress-bar-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.progress-bar-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3278b3));background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:-moz-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;border-color:#3278b3;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3278b3',GradientType=0)}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-moz-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#d0e9c6));background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffd0e9c6',GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#c4e3f3));background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffc4e3f3',GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#faf2cc));background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fffaf2cc',GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#ebcccc));background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-moz-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffebcccc',GradientType=0)}.well{background-image:-webkit-gradient(linear,left 0,left 100%,from(#e8e8e8),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-moz-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;border-color:#dcdcdc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)} -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wvuong/spring-mvc-bootstrap-angularjs-starter/765c59e9468a0ce2c045fcac353fe0da5ccd8d9e/src/main/webapp/resources/lib/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wvuong/spring-mvc-bootstrap-angularjs-starter/765c59e9468a0ce2c045fcac353fe0da5ccd8d9e/src/main/webapp/resources/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wvuong/spring-mvc-bootstrap-angularjs-starter/765c59e9468a0ce2c045fcac353fe0da5ccd8d9e/src/main/webapp/resources/lib/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/main/webapp/resources/lib/bootstrap/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.2 by @fat and @mdo 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | * 6 | * Designed and built with all the love in the world by @mdo and @fat. 7 | */ 8 | 9 | if("undefined"==typeof jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active")).trigger("change");"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); -------------------------------------------------------------------------------- /src/main/webapp/resources/partials/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wvuong/spring-mvc-bootstrap-angularjs-starter/765c59e9468a0ce2c045fcac353fe0da5ccd8d9e/src/main/webapp/resources/partials/.gitignore -------------------------------------------------------------------------------- /src/main/webapp/resources/partials/partial1.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 1.

2 | -------------------------------------------------------------------------------- /src/main/webapp/resources/partials/partial2.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 2.

2 |

3 | Showing of 'interpolate' filter: 4 | {{ 'Current version is v%VERSION%.' | interpolate }} 5 |

6 | -------------------------------------------------------------------------------- /src/test/java/com/willvuong/bootstrapper/util/ThreadLocalMemoryAppenderTest.java: -------------------------------------------------------------------------------- 1 | package com.willvuong.bootstrapper.util; 2 | 3 | import ch.qos.logback.classic.Logger; 4 | import ch.qos.logback.classic.LoggerContext; 5 | import ch.qos.logback.classic.encoder.PatternLayoutEncoder; 6 | import ch.qos.logback.classic.spi.ILoggingEvent; 7 | import ch.qos.logback.core.encoder.LayoutWrappingEncoder; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.List; 13 | 14 | import static org.hamcrest.MatcherAssert.assertThat; 15 | import static org.hamcrest.Matchers.containsString; 16 | import static org.hamcrest.Matchers.is; 17 | 18 | /** 19 | * Created with IntelliJ IDEA. 20 | * User: will 21 | * Date: 11/23/13 22 | * Time: 11:49 PM 23 | */ 24 | public class ThreadLocalMemoryAppenderTest { 25 | 26 | private Logger rootLogger = null; 27 | private LoggerContext loggerContext = null; 28 | 29 | @Before 30 | public void setup() { 31 | rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); 32 | loggerContext = rootLogger.getLoggerContext(); 33 | // we are not interested in auto-configuration 34 | loggerContext.reset(); 35 | } 36 | 37 | @Test 38 | public void testEventLoggingAndReset() { 39 | // set up 40 | PatternLayoutEncoder encoder = new PatternLayoutEncoder(); 41 | encoder.setContext(loggerContext); 42 | encoder.setPattern("%-5level [%thread]: %message%n"); 43 | encoder.start(); 44 | 45 | ThreadLocalMemoryAppender appender = new ThreadLocalMemoryAppender(); 46 | appender.setContext(loggerContext); 47 | appender.start(); 48 | rootLogger.addAppender(appender); 49 | 50 | // log 51 | rootLogger.debug("Message 1"); 52 | rootLogger.warn("Message 2"); 53 | 54 | // verify 55 | List buffer = ThreadLocalMemoryAppender.ThreadLocalHolder.getLoggedEvents(); 56 | assertThat(buffer.size(), is(2)); 57 | 58 | ThreadLocalMemoryAppender.ThreadLocalHolder.clearLoggedEvents(); 59 | buffer = ThreadLocalMemoryAppender.ThreadLocalHolder.getLoggedEvents(); 60 | assertThat(buffer.size(), is(0)); 61 | } 62 | 63 | @Test 64 | public void testMultipleThreads() { 65 | // set up 66 | PatternLayoutEncoder encoder = new PatternLayoutEncoder(); 67 | encoder.setContext(loggerContext); 68 | encoder.setPattern("%-5level [%thread]: %message%n"); 69 | encoder.start(); 70 | 71 | ThreadLocalMemoryAppender appender = new ThreadLocalMemoryAppender(); 72 | appender.setContext(loggerContext); 73 | appender.start(); 74 | rootLogger.addAppender(appender); 75 | 76 | Thread thread1 = new Thread() { 77 | private Logger logger = (Logger) LoggerFactory.getLogger("thread1"); 78 | 79 | @Override 80 | public void run() { 81 | logger.debug("Message 1"); 82 | logger.warn("Message 2"); 83 | 84 | // verify 85 | List buffer = ThreadLocalMemoryAppender.ThreadLocalHolder.getLoggedEvents(); 86 | assertThat(buffer.size(), is(2)); 87 | 88 | ThreadLocalMemoryAppender.ThreadLocalHolder.clearLoggedEvents(); 89 | buffer = ThreadLocalMemoryAppender.ThreadLocalHolder.getLoggedEvents(); 90 | assertThat(buffer.size(), is(0)); 91 | } 92 | }; 93 | 94 | Thread thread2 = new Thread() { 95 | private Logger logger = (Logger) LoggerFactory.getLogger("thread2"); 96 | 97 | @Override 98 | public void run() { 99 | logger.debug("Message 1"); 100 | logger.warn("Message 2"); 101 | 102 | // verify 103 | List buffer = ThreadLocalMemoryAppender.ThreadLocalHolder.getLoggedEvents(); 104 | assertThat(buffer.size(), is(2)); 105 | 106 | ThreadLocalMemoryAppender.ThreadLocalHolder.clearLoggedEvents(); 107 | buffer = ThreadLocalMemoryAppender.ThreadLocalHolder.getLoggedEvents(); 108 | assertThat(buffer.size(), is(0)); 109 | } 110 | }; 111 | 112 | thread1.start(); 113 | thread2.start(); 114 | } 115 | 116 | @Test 117 | public void testDefaultEncoder() { 118 | // set up 119 | ThreadLocalMemoryAppender appender = new ThreadLocalMemoryAppender(); 120 | appender.setContext(loggerContext); 121 | appender.start(); 122 | rootLogger.addAppender(appender); 123 | 124 | // log 125 | Logger logger = (Logger) LoggerFactory.getLogger("test"); 126 | logger.debug("Message 1"); 127 | logger.warn("Message 2"); 128 | 129 | String s = ThreadLocalMemoryAppender.ThreadLocalHolder.getBufferAsJson(null, null); 130 | System.out.println(s); 131 | /* 132 | assertThat(s, containsString("")); 133 | assertThat(s, containsString("")); 134 | assertThat(s, containsString("Message 1")); 135 | assertThat(s, containsString("Message 2")); 136 | */ 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /tools/springloaded-1.1.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wvuong/spring-mvc-bootstrap-angularjs-starter/765c59e9468a0ce2c045fcac353fe0da5ccd8d9e/tools/springloaded-1.1.4.jar --------------------------------------------------------------------------------