├── .gitignore ├── README.md ├── angularjs-webapp ├── .gitignore ├── README.md ├── build.gradle ├── pom.xml └── src │ └── main │ ├── java │ └── no │ │ └── kodemaker │ │ └── ps │ │ └── AnguarJSWebApp.java │ └── webapp │ ├── .DS_Store │ ├── WEB-INF │ └── web.xml │ ├── css │ ├── foundation.min.css │ ├── raffler.css │ └── todo.css │ ├── data │ └── persons.json │ ├── index.html │ ├── js │ ├── avengers.js │ ├── beers.js │ ├── components.js │ ├── event.js │ ├── hello.js │ ├── lib │ │ ├── README │ │ ├── angular-1.0.7.js │ │ ├── angular-1.1.5.js │ │ ├── angular-1.2.0rc1.js │ │ ├── angular-resource-1.1.5.js │ │ ├── angular-resource-1.2.0rc1.js │ │ ├── angular-route-1.2.0rc1.js │ │ └── ui-bootstrap-tpls-0.5.0.js │ ├── menu.js │ ├── persons.js │ ├── raffler.js │ ├── scope-isolation.js │ ├── sharing-data.js │ ├── tabset.js │ ├── todo.js │ └── twitter.js │ └── partials │ ├── controller-and-directives.html │ ├── filters-and-directives.html │ ├── hello.html │ ├── persons.html │ ├── raffler.html │ ├── scope-isolation.html │ ├── sharing-data.html │ └── tabs.html ├── build.gradle ├── doc ├── deployment_diagram.graffle │ ├── data.plist │ ├── image1.ai │ └── image2.ai └── deployment_diagram.jpg ├── dw-server ├── README ├── build.gradle ├── dw-server.yml ├── pom.xml ├── src │ ├── main │ │ └── java │ │ │ └── no │ │ │ └── kodemaker │ │ │ └── ps │ │ │ └── dw │ │ │ └── eventservice │ │ │ ├── EventConfiguration.java │ │ │ ├── EventService.java │ │ │ ├── health │ │ │ └── TemplateHealthCheck.java │ │ │ ├── persistence │ │ │ ├── PersonDao.java │ │ │ └── PersonMapper.java │ │ │ ├── representations │ │ │ ├── Person.java │ │ │ └── Saying.java │ │ │ └── resources │ │ │ ├── HelloWorldResource.java │ │ │ └── PersonsResource.java │ └── test │ │ └── java │ │ └── no │ │ └── kodemaker │ │ └── example │ │ └── dw │ │ └── hello │ │ └── client │ │ └── HelloWorldClient.java └── start_server.sh ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pom.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | .springBeans 5 | *.iml 6 | *.ipr 7 | *.iws 8 | classes 9 | .idea 10 | target 11 | build 12 | .gradle 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # General info 2 | 3 | This is a little example application that I have used to learn to use the AngularJS and Dropwizard 4 | frameworks. It also serves as a project template that can be used to get a new project up and running 5 | quickly. A blog article describing what I did can be found here: 6 | [Getting Started With AngularJS, Jetty and Dropwizard](http://perspilling.heroku.com/blog/2013/08/21/getting-started-with-angularjs-jetty-and-dropwizard/) 7 | 8 | # Architecture information 9 | 10 | AngularJS is used to create a simple webapp (angularjs-webapp module), while Dropwizard has 11 | been used to create a backend server with RESTful services (dw-server module). The two are deployed on different 12 | web servers, as shown here: 13 | 14 | ![Deployment diagram](doc/deployment_diagram.jpg) 15 | 16 | # Topics that have been explored 17 | 18 | ## AngularJS 19 | - organizing an AngularJS webapp in modules and partials 20 | - AngularJS directives, filters, calling backend services via $resource, etc. 21 | - using Bootstrap with AngularJS 22 | - using $promise returned from $resource calls to ensure that certains functions are executed in sequence 23 | 24 | ## Jetty 25 | - using Jetty for both the webapp and the backend services 26 | - embedding Jetty in the application in dw-server so that it is very easy to run and debug from the IDE. 27 | - configuring Jetty in the angular-webapp as a proxy to the dw-server in order to tackle the issue that 28 | a JavaScript application is not allowed to call back to another server than it's origination server. 29 | 30 | ## Dropwizard 31 | - creating RESTful (JSON) services with Jersey & Jackson, including health checks 32 | - getting the services to handle requests from AngularJS $resource 33 | - creating persistence logic with JDBI 34 | 35 | ## Maven 36 | - using the the [appassembler-maven-plugin](http://mojo.codehaus.org/appassembler/appassembler-maven-plugin/index.html) 37 | to generate a script that will set the classpath and run the webapp 38 | - building fat JARS with [Maven Shade Plugin](http://maven.apache.org/plugins/maven-shade-plugin/) making it 39 | possible to deploy Dropwizard services as single .jar files. 40 | 41 | ## [Gradle](http://www.gradle.org) 42 | - For those with allergies towards xml bloat, there is a gradle (almost) equivalent build. 43 | 44 | 45 | ### Running the app with gradle 46 | 1. Start the dwServer `$projectRoot:> ./gradlew runDwServer` 47 | 2. Start the webApp `$projectRoot:> ./gradlew jettyMrun` 48 | 49 | First time you run ./gradlew it will download a gradle distribution, not install neeeded. 50 | 51 | 52 | -------------------------------------------------------------------------------- /angularjs-webapp/.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | .springBeans 5 | target 6 | -------------------------------------------------------------------------------- /angularjs-webapp/README.md: -------------------------------------------------------------------------------- 1 | # General info 2 | 3 | This "Java / AngularJS" webapp uses an embedded Jetty container, as is described in 4 | [Maven + Jetty = Quick WebService](http://www.dev-smart.com/archives/436) and 5 | [http://www.jamesward.com/2011/08/23/war-less-java-web-apps](http://www.jamesward.com/2011/08/23/war-less-java-web-apps). 6 | 7 | # Build 8 | 9 | Build the project with 10 | 11 | $ mvn install 12 | 13 | # Configure 14 | 15 | You will need to set the `REPO` environment variable, so the execution wrapper script knows where to find the maven dependencies. For example: 16 | 17 | $ export REPO=$HOME/.m2/repository 18 | 19 | # Run 20 | 21 | Now you can run your webapp with: 22 | 23 | $ sh target/bin/webapp 24 | 25 | (the wrapper script is not executable by default). 26 | 27 | ## Run & debug Jetty in IDEA in hot swap mode 28 | 29 | When developing in IDEA it is very useful to make Jetty run in hot swap mode so that you instantly 30 | see the result of source changes in the resulting web page. The setup for this is described here 31 | [https://gist.github.com/naaman/1053217](https://gist.github.com/naaman/1053217). 32 | 33 | ## IDEA LiveEdit plugin + JetBrains IDE Support plugin for Chrome 34 | 35 | (IDEA LiveEdit pluging)[http://plugins.jetbrains.com/plugin/?id=7007] + (JetBrains IDE Support plugin for Chrome) 36 | [https://chrome.google.com/webstore/detail/jetbrains-ide-support/hmhgeddbohgjknpmjagkdomcpobmllji] is a very 37 | powerful combination for developing HTML/JavaScript. 38 | -------------------------------------------------------------------------------- /angularjs-webapp/build.gradle: -------------------------------------------------------------------------------- 1 | // Using custom third party jetty plugin to enable jetty 8 support 2 | buildscript { 3 | repositories { 4 | mavenCentral() 5 | } 6 | dependencies { 7 | classpath group: 'com.github.martins1930.gradle', name: 'JettyMulti', version: '1.2.0-RELEASE' 8 | } 9 | } 10 | apply plugin: 'jettymulti' 11 | 12 | 13 | dependencies { 14 | compile libs.jetty_webapp, libs.jetty_servlets, libs.servlet_jsp 15 | } 16 | 17 | jettyMRun.contextApp = "" 18 | -------------------------------------------------------------------------------- /angularjs-webapp/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | no.kodemaker.ps 7 | angularjs-dropwizard-example 8 | 1.0-SNAPSHOT 9 | 10 | 11 | angularjs-webapp 12 | 1.0-SNAPSHOT 13 | angularjs-webapp 14 | jar 15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | org.eclipse.jetty 26 | jetty-webapp 27 | 28 | 29 | 30 | 31 | org.eclipse.jetty 32 | jetty-servlets 33 | 34 | 35 | 36 | 37 | org.glassfish.web 38 | javax.servlet.jsp 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.apache.maven.plugins 46 | maven-compiler-plugin 47 | 2.3.2 48 | 49 | ${java.version} 50 | ${java.version} 51 | 52 | 53 | 54 | 60 | 61 | org.codehaus.mojo 62 | appassembler-maven-plugin 63 | 1.4 64 | 65 | target 66 | 67 | 68 | no.kodemaker.ps.AnguarJSWebApp 69 | webapp 70 | 71 | 72 | 73 | 74 | 75 | package 76 | 77 | assemble 78 | 79 | 80 | 81 | 82 | 83 | 88 | 89 | org.mortbay.jetty 90 | jetty-maven-plugin 91 | 92 | 0 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/java/no/kodemaker/ps/AnguarJSWebApp.java: -------------------------------------------------------------------------------- 1 | package no.kodemaker.ps; 2 | 3 | import org.eclipse.jetty.server.Server; 4 | import org.eclipse.jetty.webapp.WebAppContext; 5 | 6 | /** 7 | * This class launches the web application in an embedded Jetty container. 8 | * This is the entry point to your application. The Java command that is used for 9 | * launching should fire this main method. 10 | */ 11 | public class AnguarJSWebApp { 12 | public static void main(String[] args) throws Exception { 13 | // The simple Jetty config here will serve static content from the webapp directory 14 | String webappDirLocation = "src/main/webapp/"; 15 | 16 | // The port that we should run on can be set into an environment variable 17 | // Look for that variable and default to 8080 if it isn't there. 18 | String webPort = System.getenv("PORT"); 19 | if (webPort == null || webPort.isEmpty()) { 20 | webPort = "8080"; 21 | } 22 | Server server = new Server(Integer.valueOf(webPort)); 23 | 24 | WebAppContext webapp = new WebAppContext(); 25 | webapp.setContextPath("/"); 26 | webapp.setDescriptor(webappDirLocation + "/WEB-INF/web.xml"); 27 | webapp.setResourceBase(webappDirLocation); 28 | 29 | server.setHandler(webapp); 30 | server.start(); 31 | server.join(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perspilling/angularjs-dropwizard-example/7d4e1169757ead3e008627b9455fae6e84eab292/angularjs-webapp/src/main/webapp/.DS_Store -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | index.html 10 | 11 | 12 | 13 | 14 | proxy 15 | org.eclipse.jetty.servlets.ProxyServlet$Transparent 16 | 17 | ProxyTo 18 | http://localhost:9000/ 19 | 20 | 21 | Prefix 22 | /api 23 | 24 | 1 25 | 26 | 27 | 28 | 29 | 30 | proxy 31 | /api/* 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/css/raffler.css: -------------------------------------------------------------------------------- 1 | .raffle-list { 2 | list-style: none; 3 | margin: 0; 4 | padding: 0; 5 | margin-top: 10px; 6 | margin-bottom: 10px; 7 | border-top: solid 1px #CCC; 8 | } 9 | 10 | .raffle-entry { 11 | border-bottom: solid 1px #CCC; 12 | padding: 10px 10px; 13 | font-size: 16px; 14 | position: relative; 15 | } 16 | 17 | .raffle-entry.winner { 18 | position: absolute; 19 | right: 10px; 20 | top: 10px; 21 | color: #999; 22 | } -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/css/todo.css: -------------------------------------------------------------------------------- 1 | .done-true { 2 | text-decoration: line-through; 3 | color: grey; 4 | } -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/data/persons.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Per", 4 | "email": "per@kodemaker.no", 5 | "mobile": null 6 | }, 7 | { 8 | "name": "Magnus", 9 | "email": "magnus@kodemaker.no", 10 | "mobile": null 11 | }, 12 | { 13 | "name": "Ronny", 14 | "email": "ronny@kodemaker.no", 15 | "mobile": null 16 | }, 17 | { 18 | "name": "August", 19 | "email": "august@kodemaker.no", 20 | "mobile": null 21 | } 22 | ] -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 |
16 |
17 |
18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/avengers.js: -------------------------------------------------------------------------------- 1 | var myApp = angular.module('avengers', []); 2 | 3 | myApp.directive("superman", function () { 4 | return { 5 | restrict: "E", 6 | template: "
This is a superman directive
" 7 | } 8 | }) 9 | 10 | myApp.directive("working", function () { 11 | return { 12 | restrict: "A", 13 | link: function() { 14 | alert("I'm working") 15 | } 16 | } 17 | }) 18 | 19 | myApp.directive("enter", function () { 20 | return function(scope, element, attrs) { 21 | element.bind("mouseenter", function () { 22 | //console.log("I'm in you!"); 23 | element.addClass(attrs.enter); 24 | }) 25 | } 26 | }) 27 | 28 | myApp.directive("enter", function () { 29 | return function(scope, element, attrs) { 30 | element.bind("mouseleave", function () { 31 | //console.log("I'm leaving you!"); 32 | element.removeClass(attrs.enter); 33 | }) 34 | } 35 | }) 36 | 37 | myApp.factory('Avengers', function () { 38 | var Avengers = {}; 39 | Avengers.cast = [ 40 | { 41 | name: "Robert Downey Jr.", 42 | character: "Tony Stark / Iron Man" 43 | }, 44 | { 45 | name: "Chris Evans", 46 | character: "Steve Rogers / Captain America" 47 | }, 48 | { 49 | name: "Mark Ruffalo", 50 | character: "Bruce Banner / The Hulk" 51 | }, 52 | { 53 | name: "Chris Hemsworth", 54 | character: "Thor" 55 | }, 56 | { 57 | name: "Scarlett Johansson", 58 | character: "Natasha Romanoff / Black Widow" 59 | }, 60 | { 61 | name: "Jeremy Renner", 62 | character: "Clint Barton / Hawkeye" 63 | }, 64 | { 65 | name: "Tom Hiddleston", 66 | character: "Loki" 67 | }, 68 | { 69 | name: "Clark Gregg", 70 | character: "Agent Phil Coulson" 71 | }, 72 | { 73 | name: "Cobie Smulders", 74 | character: "Agent Maria Hill" 75 | }, 76 | { 77 | name: "Stellan Skarsgård", 78 | character: "Selvig" 79 | }, 80 | { 81 | name: "Samuel L. Jackson", 82 | character: "Nick Fury" 83 | }, 84 | { 85 | name: "Gwyneth Paltrow", 86 | character: "Pepper Potts" 87 | } 88 | ]; 89 | return Avengers; 90 | }) 91 | 92 | function AvengersCtrl($scope, Avengers) { 93 | $scope.avengers = Avengers; 94 | } -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/beers.js: -------------------------------------------------------------------------------- 1 | 2 | function BeerCounter($scope, $locale) { 3 | $scope.beers = [0, 1, 2, 3, 4, 5, 6, 7]; 4 | if ($locale.id == 'en-us') { 5 | $scope.beerForms = { 6 | 0: 'no beers', 7 | one: '{} beer', 8 | other: '{} beers' 9 | }; 10 | } else { 11 | $scope.beerForms = { 12 | 0: 'žiadne pivo', 13 | one: '{} pivo', 14 | few: '{} pivá', 15 | other: '{} pív' 16 | }; 17 | } 18 | } -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/components.js: -------------------------------------------------------------------------------- 1 | 2 | angular.module('components', ['sidebarMenu', 'ui.bootstrap']). 3 | directive('tabs', function() { 4 | return { 5 | restrict: 'E', 6 | transclude: true, 7 | scope: {}, 8 | controller: function($scope, $element) { 9 | var panes = $scope.panes = []; 10 | 11 | $scope.select = function(pane) { 12 | angular.forEach(panes, function(pane) { 13 | pane.selected = false; 14 | }); 15 | pane.selected = true; 16 | } 17 | 18 | this.addPane = function(pane) { 19 | if (panes.length == 0) $scope.select(pane); 20 | panes.push(pane); 21 | } 22 | }, 23 | template: 24 | '
' + 25 | '' + 30 | '
' + 31 | '
', 32 | replace: true 33 | }; 34 | }). 35 | directive('pane', function() { 36 | return { 37 | require: '^tabs', 38 | restrict: 'E', 39 | transclude: true, 40 | scope: { title: '@' }, 41 | link: function(scope, element, attrs, tabsCtrl) { 42 | tabsCtrl.addPane(scope); 43 | }, 44 | template: 45 | '
' + 46 | '
', 47 | replace: true 48 | }; 49 | }) -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/event.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created with IntelliJ IDEA. 3 | * User: PerSpilling 4 | * Date: 7/24/13 5 | * Time: 9:34 PM 6 | * To change this template use File | Settings | File Templates. 7 | */ 8 | 9 | function HelloCntl($scope) { 10 | $scope.name = 'World'; 11 | } -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/hello.js: -------------------------------------------------------------------------------- 1 | //var helloApp = angular.module('helloApp', ['ui.bootstrap', 'sidebarMenu']); 2 | var helloApp = angular.module('helloApp', ['sidebarMenu', 'sharingData', 'avengers', 3 | 'scopeIsolation', 'twitterApp', 'raffleApp', 'tabs', 'persons']); 4 | 5 | function HelloCtrl($scope) { 6 | $scope.data = {message: "World"} 7 | } 8 | 9 | function FirstCtrl($scope) { 10 | } 11 | 12 | function SecondCtrl($scope) { 13 | } 14 | 15 | function ButtonsCtrl ($scope) { 16 | 17 | $scope.singleModel = 1; 18 | 19 | $scope.radioModel = 'Middle'; 20 | 21 | $scope.checkModel = { 22 | left: false, 23 | middle: true, 24 | right: false 25 | }; 26 | }; -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/lib/README: -------------------------------------------------------------------------------- 1 | Using AngularJS v1.1.5 here. AngularJS can be downloaded from: http://code.angularjs.org/ -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/lib/angular-resource-1.1.5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.1.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 | * # Installation 28 | * To use $resource make sure you have included the `angular-resource-1.1.5.js` that comes in Angular 29 | * package. You can also find this file on Google CDN, bower as well as at 30 | * {@link http://code.angularjs.org/ code.angularjs.org}. 31 | * 32 | * Finally load the module in your application: 33 | * 34 | * angular.module('app', ['ngResource']); 35 | * 36 | * and you are ready to get started! 37 | * 38 | * @param {string} url A parametrized URL template with parameters prefixed by `:` as in 39 | * `/user/:username`. If you are using a URL with a port number (e.g. 40 | * `http://example.com:8080/api`), you'll need to escape the colon character before the port 41 | * number, like this: `$resource('http://example.com\\:8080/api')`. 42 | * 43 | * If you are using a url with a suffix, just add the suffix, like this: 44 | * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json') 45 | * or even `$resource('http://example.com/resource/:resource_id.:format')` 46 | * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be 47 | * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you 48 | * can escape it with `/\.`. 49 | * 50 | * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 51 | * `actions` methods. If any of the parameter value is a function, it will be executed every time 52 | * when a param value needs to be obtained for a request (unless the param was overridden). 53 | * 54 | * Each key value in the parameter object is first bound to url template if present and then any 55 | * excess keys are appended to the url search query after the `?`. 56 | * 57 | * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 58 | * URL `/path/greet?salutation=Hello`. 59 | * 60 | * If the parameter value is prefixed with `@` then the value of that parameter is extracted from 61 | * the data object (useful for non-GET operations). 62 | * 63 | * @param {Object.=} actions Hash with declaration of custom action that should extend the 64 | * default set of resource actions. The declaration should be created in the format of {@link 65 | * ng.$http#Parameters $http.config}: 66 | * 67 | * {action1: {method:?, params:?, isArray:?, headers:?, ...}, 68 | * action2: {method:?, params:?, isArray:?, headers:?, ...}, 69 | * ...} 70 | * 71 | * Where: 72 | * 73 | * - **`action`** – {string} – The name of action. This name becomes the name of the method on your 74 | * resource object. 75 | * - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, 76 | * and `JSONP`. 77 | * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of the 78 | * parameter value is a function, it will be executed every time when a param value needs to be 79 | * obtained for a request (unless the param was overridden). 80 | * - **`url`** – {string} – action specific `url` override. The url templating is supported just like 81 | * for the resource-level urls. 82 | * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, see 83 | * `returns` section. 84 | * - **`transformRequest`** – `{function(data, headersGetter)|Array.}` – 85 | * transform function or an array of such functions. The transform function takes the http 86 | * request body and headers and returns its transformed (typically serialized) version. 87 | * - **`transformResponse`** – `{function(data, headersGetter)|Array.}` – 88 | * transform function or an array of such functions. The transform function takes the http 89 | * response body and headers and returns its transformed (typically deserialized) version. 90 | * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the 91 | * GET request, otherwise if a cache instance built with 92 | * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 93 | * caching. 94 | * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that 95 | * should abort the request when resolved. 96 | * - **`withCredentials`** - `{boolean}` - whether to to set the `withCredentials` flag on the 97 | * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 98 | * requests with credentials} for more information. 99 | * - **`responseType`** - `{string}` - see {@link 100 | * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. 101 | * 102 | * @returns {Object} A resource "class" object with methods for the default set of resource actions 103 | * optionally extended with custom `actions`. The default set contains these actions: 104 | * 105 | * { 'get': {method:'GET'}, 106 | * 'save': {method:'POST'}, 107 | * 'query': {method:'GET', isArray:true}, 108 | * 'remove': {method:'DELETE'}, 109 | * 'delete': {method:'DELETE'} }; 110 | * 111 | * Calling these methods invoke an {@link ng.$http} with the specified http method, 112 | * destination and parameters. When the data is returned from the server then the object is an 113 | * instance of the resource class. The actions `save`, `remove` and `delete` are available on it 114 | * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, 115 | * read, update, delete) on server-side data like this: 116 | *
117 |         var User = $resource('/user/:userId', {userId:'@id'});
118 |         var user = User.get({userId:123}, function() {
119 |           user.abc = true;
120 |           user.$save();
121 |         });
122 |      
123 | * 124 | * It is important to realize that invoking a $resource object method immediately returns an 125 | * empty reference (object or array depending on `isArray`). Once the data is returned from the 126 | * server the existing reference is populated with the actual data. This is a useful trick since 127 | * usually the resource is assigned to a model which is then rendered by the view. Having an empty 128 | * object results in no rendering, once the data arrives from the server then the object is 129 | * populated with the data and the view automatically re-renders itself showing the new data. This 130 | * means that in most case one never has to write a callback function for the action methods. 131 | * 132 | * The action methods on the class object or instance object can be invoked with the following 133 | * parameters: 134 | * 135 | * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` 136 | * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` 137 | * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 138 | * 139 | * 140 | * The Resource instances and collection have these additional properties: 141 | * 142 | * - `$then`: the `then` method of a {@link ng.$q promise} derived from the underlying 143 | * {@link ng.$http $http} call. 144 | * 145 | * The success callback for the `$then` method will be resolved if the underlying `$http` requests 146 | * succeeds. 147 | * 148 | * The success callback is called with a single object which is the {@link ng.$http http response} 149 | * object extended with a new property `resource`. This `resource` property is a reference to the 150 | * result of the resource action — resource object or array of resources. 151 | * 152 | * The error callback is called with the {@link ng.$http http response} object when an http 153 | * error occurs. 154 | * 155 | * - `$resolved`: true if the promise has been resolved (either with success or rejection); 156 | * Knowing if the Resource has been resolved is useful in data-binding. 157 | * 158 | * @example 159 | * 160 | * # Credit card resource 161 | * 162 | *
163 |      // Define CreditCard class
164 |      var CreditCard = $resource('/user/:userId/card/:cardId',
165 |       {userId:123, cardId:'@id'}, {
166 |        charge: {method:'POST', params:{charge:true}}
167 |       });
168 | 
169 |      // We can retrieve a collection from the server
170 |      var cards = CreditCard.query(function() {
171 |        // GET: /user/123/card
172 |        // server returns: [ {id:456, number:'1234', name:'Smith'} ];
173 | 
174 |        var card = cards[0];
175 |        // each item is an instance of CreditCard
176 |        expect(card instanceof CreditCard).toEqual(true);
177 |        card.name = "J. Smith";
178 |        // non GET methods are mapped onto the instances
179 |        card.$save();
180 |        // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
181 |        // server returns: {id:456, number:'1234', name: 'J. Smith'};
182 | 
183 |        // our custom method is mapped as well.
184 |        card.$charge({amount:9.99});
185 |        // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
186 |      });
187 | 
188 |      // we can create an instance as well
189 |      var newCard = new CreditCard({number:'0123'});
190 |      newCard.name = "Mike Smith";
191 |      newCard.$save();
192 |      // POST: /user/123/card {number:'0123', name:'Mike Smith'}
193 |      // server returns: {id:789, number:'01234', name: 'Mike Smith'};
194 |      expect(newCard.id).toEqual(789);
195 |  * 
196 | * 197 | * The object returned from this function execution is a resource "class" which has "static" method 198 | * for each action in the definition. 199 | * 200 | * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and `headers`. 201 | * When the data is returned from the server then the object is an instance of the resource type and 202 | * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 203 | * operations (create, read, update, delete) on server-side data. 204 | 205 |
206 |      var User = $resource('/user/:userId', {userId:'@id'});
207 |      var user = User.get({userId:123}, function() {
208 |        user.abc = true;
209 |        user.$save();
210 |      });
211 |    
212 | * 213 | * It's worth noting that the success callback for `get`, `query` and other method gets passed 214 | * in the response that came from the server as well as $http header getter function, so one 215 | * could rewrite the above example and get access to http headers as: 216 | * 217 |
218 |      var User = $resource('/user/:userId', {userId:'@id'});
219 |      User.get({userId:123}, function(u, getResponseHeaders){
220 |        u.abc = true;
221 |        u.$save(function(u, putResponseHeaders) {
222 |          //u => saved user object
223 |          //putResponseHeaders => $http header getter
224 |        });
225 |      });
226 |    
227 | 228 | * # Buzz client 229 | 230 | Let's look at what a buzz client created with the `$resource` service looks like: 231 | 232 | 233 | 253 | 254 |
255 | 256 | 257 |
258 |
259 |

260 | 261 | {{item.actor.name}} 262 | Expand replies: {{item.links.replies[0].count}} 263 |

264 | {{item.object.content | html}} 265 |
266 | 267 | {{reply.actor.name}}: {{reply.content | html}} 268 |
269 |
270 |
271 |
272 | 273 | 274 |
275 | */ 276 | angular.module('ngResource', ['ng']). 277 | factory('$resource', ['$http', '$parse', function($http, $parse) { 278 | var DEFAULT_ACTIONS = { 279 | 'get': {method:'GET'}, 280 | 'save': {method:'POST'}, 281 | 'query': {method:'GET', isArray:true}, 282 | 'remove': {method:'DELETE'}, 283 | 'delete': {method:'DELETE'} 284 | }; 285 | var noop = angular.noop, 286 | forEach = angular.forEach, 287 | extend = angular.extend, 288 | copy = angular.copy, 289 | isFunction = angular.isFunction, 290 | getter = function(obj, path) { 291 | return $parse(path)(obj); 292 | }; 293 | 294 | /** 295 | * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 296 | * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 297 | * segments: 298 | * segment = *pchar 299 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 300 | * pct-encoded = "%" HEXDIG HEXDIG 301 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 302 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 303 | * / "*" / "+" / "," / ";" / "=" 304 | */ 305 | function encodeUriSegment(val) { 306 | return encodeUriQuery(val, true). 307 | replace(/%26/gi, '&'). 308 | replace(/%3D/gi, '='). 309 | replace(/%2B/gi, '+'); 310 | } 311 | 312 | 313 | /** 314 | * This method is intended for encoding *key* or *value* parts of query component. We need a custom 315 | * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 316 | * encoded per http://tools.ietf.org/html/rfc3986: 317 | * query = *( pchar / "/" / "?" ) 318 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 319 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 320 | * pct-encoded = "%" HEXDIG HEXDIG 321 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 322 | * / "*" / "+" / "," / ";" / "=" 323 | */ 324 | function encodeUriQuery(val, pctEncodeSpaces) { 325 | return encodeURIComponent(val). 326 | replace(/%40/gi, '@'). 327 | replace(/%3A/gi, ':'). 328 | replace(/%24/g, '$'). 329 | replace(/%2C/gi, ','). 330 | replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 331 | } 332 | 333 | function Route(template, defaults) { 334 | this.template = template; 335 | this.defaults = defaults || {}; 336 | this.urlParams = {}; 337 | } 338 | 339 | Route.prototype = { 340 | setUrlParams: function(config, params, actionUrl) { 341 | var self = this, 342 | url = actionUrl || self.template, 343 | val, 344 | encodedVal; 345 | 346 | var urlParams = self.urlParams = {}; 347 | forEach(url.split(/\W/), function(param){ 348 | if (param && (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { 349 | urlParams[param] = true; 350 | } 351 | }); 352 | url = url.replace(/\\:/g, ':'); 353 | 354 | params = params || {}; 355 | forEach(self.urlParams, function(_, urlParam){ 356 | val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 357 | if (angular.isDefined(val) && val !== null) { 358 | encodedVal = encodeUriSegment(val); 359 | url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1"); 360 | } else { 361 | url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, 362 | leadingSlashes, tail) { 363 | if (tail.charAt(0) == '/') { 364 | return tail; 365 | } else { 366 | return leadingSlashes + tail; 367 | } 368 | }); 369 | } 370 | }); 371 | 372 | // strip trailing slashes and set the url 373 | url = url.replace(/\/+$/, ''); 374 | // then replace collapse `/.` if found in the last URL path segment before the query 375 | // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` 376 | url = url.replace(/\/\.(?=\w+($|\?))/, '.'); 377 | // replace escaped `/\.` with `/.` 378 | config.url = url.replace(/\/\\\./, '/.'); 379 | 380 | 381 | // set params - delegate param encoding to $http 382 | forEach(params, function(value, key){ 383 | if (!self.urlParams[key]) { 384 | config.params = config.params || {}; 385 | config.params[key] = value; 386 | } 387 | }); 388 | } 389 | }; 390 | 391 | 392 | function ResourceFactory(url, paramDefaults, actions) { 393 | var route = new Route(url); 394 | 395 | actions = extend({}, DEFAULT_ACTIONS, actions); 396 | 397 | function extractParams(data, actionParams){ 398 | var ids = {}; 399 | actionParams = extend({}, paramDefaults, actionParams); 400 | forEach(actionParams, function(value, key){ 401 | if (isFunction(value)) { value = value(); } 402 | ids[key] = value && value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; 403 | }); 404 | return ids; 405 | } 406 | 407 | function Resource(value){ 408 | copy(value || {}, this); 409 | } 410 | 411 | forEach(actions, function(action, name) { 412 | action.method = angular.uppercase(action.method); 413 | var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH'; 414 | Resource[name] = function(a1, a2, a3, a4) { 415 | var params = {}; 416 | var data; 417 | var success = noop; 418 | var error = null; 419 | var promise; 420 | 421 | switch(arguments.length) { 422 | case 4: 423 | error = a4; 424 | success = a3; 425 | //fallthrough 426 | case 3: 427 | case 2: 428 | if (isFunction(a2)) { 429 | if (isFunction(a1)) { 430 | success = a1; 431 | error = a2; 432 | break; 433 | } 434 | 435 | success = a2; 436 | error = a3; 437 | //fallthrough 438 | } else { 439 | params = a1; 440 | data = a2; 441 | success = a3; 442 | break; 443 | } 444 | case 1: 445 | if (isFunction(a1)) success = a1; 446 | else if (hasBody) data = a1; 447 | else params = a1; 448 | break; 449 | case 0: break; 450 | default: 451 | throw "Expected between 0-4 arguments [params, data, success, error], got " + 452 | arguments.length + " arguments."; 453 | } 454 | 455 | var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); 456 | var httpConfig = {}, 457 | promise; 458 | 459 | forEach(action, function(value, key) { 460 | if (key != 'params' && key != 'isArray' ) { 461 | httpConfig[key] = copy(value); 462 | } 463 | }); 464 | httpConfig.data = data; 465 | route.setUrlParams(httpConfig, extend({}, extractParams(data, action.params || {}), params), action.url); 466 | 467 | function markResolved() { value.$resolved = true; } 468 | 469 | promise = $http(httpConfig); 470 | value.$resolved = false; 471 | 472 | promise.then(markResolved, markResolved); 473 | value.$then = promise.then(function(response) { 474 | var data = response.data; 475 | var then = value.$then, resolved = value.$resolved; 476 | 477 | if (data) { 478 | if (action.isArray) { 479 | value.length = 0; 480 | forEach(data, function(item) { 481 | value.push(new Resource(item)); 482 | }); 483 | } else { 484 | copy(data, value); 485 | value.$then = then; 486 | value.$resolved = resolved; 487 | } 488 | } 489 | 490 | (success||noop)(value, response.headers); 491 | 492 | response.resource = value; 493 | return response; 494 | }, error).then; 495 | 496 | return value; 497 | }; 498 | 499 | 500 | Resource.prototype['$' + name] = function(a1, a2, a3) { 501 | var params = extractParams(this), 502 | success = noop, 503 | error; 504 | 505 | switch(arguments.length) { 506 | case 3: params = a1; success = a2; error = a3; break; 507 | case 2: 508 | case 1: 509 | if (isFunction(a1)) { 510 | success = a1; 511 | error = a2; 512 | } else { 513 | params = a1; 514 | success = a2 || noop; 515 | } 516 | case 0: break; 517 | default: 518 | throw "Expected between 1-3 arguments [params, success, error], got " + 519 | arguments.length + " arguments."; 520 | } 521 | var data = hasBody ? this : undefined; 522 | Resource[name].call(this, params, data, success, error); 523 | }; 524 | }); 525 | 526 | Resource.bind = function(additionalParamDefaults){ 527 | return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); 528 | }; 529 | 530 | return Resource; 531 | } 532 | 533 | return ResourceFactory; 534 | }]); 535 | 536 | 537 | })(window, window.angular); 538 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/lib/angular-resource-1.2.0rc1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.2.0rc1 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) {'use strict'; 7 | 8 | var $resourceMinErr = angular.$$minErr('$resource'); 9 | 10 | /** 11 | * @ngdoc overview 12 | * @name ngResource 13 | * @description 14 | */ 15 | 16 | /** 17 | * @ngdoc object 18 | * @name ngResource.$resource 19 | * @requires $http 20 | * 21 | * @description 22 | * A factory which creates a resource object that lets you interact with 23 | * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. 24 | * 25 | * The returned resource object has action methods which provide high-level behaviors without 26 | * the need to interact with the low level {@link ng.$http $http} service. 27 | * 28 | * # Installation 29 | * To use $resource make sure you have included the `angular-resource.js` that comes in Angular 30 | * package. You can also find this file on Google CDN, bower as well as at 31 | * {@link http://code.angularjs.org/ code.angularjs.org}. 32 | * 33 | * Finally load the module in your application: 34 | * 35 | * angular.module('app', ['ngResource']); 36 | * 37 | * and you are ready to get started! 38 | * 39 | * @param {string} url A parametrized URL template with parameters prefixed by `:` as in 40 | * `/user/:username`. If you are using a URL with a port number (e.g. 41 | * `http://example.com:8080/api`), it will be respected. 42 | * 43 | * If you are using a url with a suffix, just add the suffix, like this: 44 | * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json') 45 | * or even `$resource('http://example.com/resource/:resource_id.:format')` 46 | * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be 47 | * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you 48 | * can escape it with `/\.`. 49 | * 50 | * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 51 | * `actions` methods. If any of the parameter value is a function, it will be executed every time 52 | * when a param value needs to be obtained for a request (unless the param was overridden). 53 | * 54 | * Each key value in the parameter object is first bound to url template if present and then any 55 | * excess keys are appended to the url search query after the `?`. 56 | * 57 | * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 58 | * URL `/path/greet?salutation=Hello`. 59 | * 60 | * If the parameter value is prefixed with `@` then the value of that parameter is extracted from 61 | * the data object (useful for non-GET operations). 62 | * 63 | * @param {Object.=} actions Hash with declaration of custom action that should extend the 64 | * default set of resource actions. The declaration should be created in the format of {@link 65 | * ng.$http#Parameters $http.config}: 66 | * 67 | * {action1: {method:?, params:?, isArray:?, headers:?, ...}, 68 | * action2: {method:?, params:?, isArray:?, headers:?, ...}, 69 | * ...} 70 | * 71 | * Where: 72 | * 73 | * - **`action`** – {string} – The name of action. This name becomes the name of the method on your 74 | * resource object. 75 | * - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, 76 | * and `JSONP`. 77 | * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of the 78 | * parameter value is a function, it will be executed every time when a param value needs to be 79 | * obtained for a request (unless the param was overridden). 80 | * - **`url`** – {string} – action specific `url` override. The url templating is supported just like 81 | * for the resource-level urls. 82 | * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, see 83 | * `returns` section. 84 | * - **`transformRequest`** – `{function(data, headersGetter)|Array.}` – 85 | * transform function or an array of such functions. The transform function takes the http 86 | * request body and headers and returns its transformed (typically serialized) version. 87 | * - **`transformResponse`** – `{function(data, headersGetter)|Array.}` – 88 | * transform function or an array of such functions. The transform function takes the http 89 | * response body and headers and returns its transformed (typically deserialized) version. 90 | * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the 91 | * GET request, otherwise if a cache instance built with 92 | * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 93 | * caching. 94 | * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that 95 | * should abort the request when resolved. 96 | * - **`withCredentials`** - `{boolean}` - whether to to set the `withCredentials` flag on the 97 | * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 98 | * requests with credentials} for more information. 99 | * - **`responseType`** - `{string}` - see {@link 100 | * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. 101 | * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - 102 | * `response` and `responseError`. Both `response` and `responseError` interceptors get called 103 | * with `http response` object. See {@link ng.$http $http interceptors}. 104 | * 105 | * @returns {Object} A resource "class" object with methods for the default set of resource actions 106 | * optionally extended with custom `actions`. The default set contains these actions: 107 | * 108 | * { 'get': {method:'GET'}, 109 | * 'save': {method:'POST'}, 110 | * 'query': {method:'GET', isArray:true}, 111 | * 'remove': {method:'DELETE'}, 112 | * 'delete': {method:'DELETE'} }; 113 | * 114 | * Calling these methods invoke an {@link ng.$http} with the specified http method, 115 | * destination and parameters. When the data is returned from the server then the object is an 116 | * instance of the resource class. The actions `save`, `remove` and `delete` are available on it 117 | * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, 118 | * read, update, delete) on server-side data like this: 119 | *
120 |         var User = $resource('/user/:userId', {userId:'@id'});
121 |         var user = User.get({userId:123}, function() {
122 |           user.abc = true;
123 |           user.$save();
124 |         });
125 |      
126 | * 127 | * It is important to realize that invoking a $resource object method immediately returns an 128 | * empty reference (object or array depending on `isArray`). Once the data is returned from the 129 | * server the existing reference is populated with the actual data. This is a useful trick since 130 | * usually the resource is assigned to a model which is then rendered by the view. Having an empty 131 | * object results in no rendering, once the data arrives from the server then the object is 132 | * populated with the data and the view automatically re-renders itself showing the new data. This 133 | * means that in most case one never has to write a callback function for the action methods. 134 | * 135 | * The action methods on the class object or instance object can be invoked with the following 136 | * parameters: 137 | * 138 | * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` 139 | * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` 140 | * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 141 | * 142 | * Success callback is called with (value, responseHeaders) arguments. Error callback is called 143 | * with (httpResponse) argument. 144 | * 145 | * Class actions return empty instance (with additional properties below). 146 | * Instance actions return promise of the action. 147 | * 148 | * The Resource instances and collection have these additional properties: 149 | * 150 | * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this 151 | * instance or collection. 152 | * 153 | * On success, the promise is resolved with the same resource instance or collection object, 154 | * updated with data from server. This makes it easy to use in 155 | * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view rendering 156 | * until the resource(s) are loaded. 157 | * 158 | * On failure, the promise is resolved with the {@link ng.$http http response} object, 159 | * without the `resource` property. 160 | * 161 | * - `$resolved`: `true` after first server interaction is completed (either with success or rejection), 162 | * `false` before that. Knowing if the Resource has been resolved is useful in data-binding. 163 | * 164 | * @example 165 | * 166 | * # Credit card resource 167 | * 168 | *
169 |      // Define CreditCard class
170 |      var CreditCard = $resource('/user/:userId/card/:cardId',
171 |       {userId:123, cardId:'@id'}, {
172 |        charge: {method:'POST', params:{charge:true}}
173 |       });
174 | 
175 |      // We can retrieve a collection from the server
176 |      var cards = CreditCard.query(function() {
177 |        // GET: /user/123/card
178 |        // server returns: [ {id:456, number:'1234', name:'Smith'} ];
179 | 
180 |        var card = cards[0];
181 |        // each item is an instance of CreditCard
182 |        expect(card instanceof CreditCard).toEqual(true);
183 |        card.name = "J. Smith";
184 |        // non GET methods are mapped onto the instances
185 |        card.$save();
186 |        // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
187 |        // server returns: {id:456, number:'1234', name: 'J. Smith'};
188 | 
189 |        // our custom method is mapped as well.
190 |        card.$charge({amount:9.99});
191 |        // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
192 |      });
193 | 
194 |      // we can create an instance as well
195 |      var newCard = new CreditCard({number:'0123'});
196 |      newCard.name = "Mike Smith";
197 |      newCard.$save();
198 |      // POST: /user/123/card {number:'0123', name:'Mike Smith'}
199 |      // server returns: {id:789, number:'01234', name: 'Mike Smith'};
200 |      expect(newCard.id).toEqual(789);
201 |  * 
202 | * 203 | * The object returned from this function execution is a resource "class" which has "static" method 204 | * for each action in the definition. 205 | * 206 | * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and `headers`. 207 | * When the data is returned from the server then the object is an instance of the resource type and 208 | * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 209 | * operations (create, read, update, delete) on server-side data. 210 | 211 |
212 |      var User = $resource('/user/:userId', {userId:'@id'});
213 |      var user = User.get({userId:123}, function() {
214 |        user.abc = true;
215 |        user.$save();
216 |      });
217 |    
218 | * 219 | * It's worth noting that the success callback for `get`, `query` and other method gets passed 220 | * in the response that came from the server as well as $http header getter function, so one 221 | * could rewrite the above example and get access to http headers as: 222 | * 223 |
224 |      var User = $resource('/user/:userId', {userId:'@id'});
225 |      User.get({userId:123}, function(u, getResponseHeaders){
226 |        u.abc = true;
227 |        u.$save(function(u, putResponseHeaders) {
228 |          //u => saved user object
229 |          //putResponseHeaders => $http header getter
230 |        });
231 |      });
232 |    
233 | 234 | * # Buzz client 235 | 236 | Let's look at what a buzz client created with the `$resource` service looks like: 237 | 238 | 239 | 259 | 260 |
261 | 262 | 263 |
264 |
265 |

266 | 267 | {{item.actor.name}} 268 | Expand replies: {{item.links.replies[0].count}} 269 |

270 | {{item.object.content | html}} 271 |
272 | 273 | {{reply.actor.name}}: {{reply.content | html}} 274 |
275 |
276 |
277 |
278 | 279 | 280 |
281 | */ 282 | angular.module('ngResource', ['ng']). 283 | factory('$resource', ['$http', '$parse', '$q', function($http, $parse, $q) { 284 | var DEFAULT_ACTIONS = { 285 | 'get': {method:'GET'}, 286 | 'save': {method:'POST'}, 287 | 'query': {method:'GET', isArray:true}, 288 | 'remove': {method:'DELETE'}, 289 | 'delete': {method:'DELETE'} 290 | }; 291 | var noop = angular.noop, 292 | forEach = angular.forEach, 293 | extend = angular.extend, 294 | copy = angular.copy, 295 | isFunction = angular.isFunction, 296 | getter = function(obj, path) { 297 | return $parse(path)(obj); 298 | }; 299 | 300 | /** 301 | * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 302 | * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 303 | * segments: 304 | * segment = *pchar 305 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 306 | * pct-encoded = "%" HEXDIG HEXDIG 307 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 308 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 309 | * / "*" / "+" / "," / ";" / "=" 310 | */ 311 | function encodeUriSegment(val) { 312 | return encodeUriQuery(val, true). 313 | replace(/%26/gi, '&'). 314 | replace(/%3D/gi, '='). 315 | replace(/%2B/gi, '+'); 316 | } 317 | 318 | 319 | /** 320 | * This method is intended for encoding *key* or *value* parts of query component. We need a custom 321 | * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 322 | * encoded per http://tools.ietf.org/html/rfc3986: 323 | * query = *( pchar / "/" / "?" ) 324 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 325 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 326 | * pct-encoded = "%" HEXDIG HEXDIG 327 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 328 | * / "*" / "+" / "," / ";" / "=" 329 | */ 330 | function encodeUriQuery(val, pctEncodeSpaces) { 331 | return encodeURIComponent(val). 332 | replace(/%40/gi, '@'). 333 | replace(/%3A/gi, ':'). 334 | replace(/%24/g, '$'). 335 | replace(/%2C/gi, ','). 336 | replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 337 | } 338 | 339 | function Route(template, defaults) { 340 | this.template = template; 341 | this.defaults = defaults || {}; 342 | this.urlParams = {}; 343 | } 344 | 345 | Route.prototype = { 346 | setUrlParams: function(config, params, actionUrl) { 347 | var self = this, 348 | url = actionUrl || self.template, 349 | val, 350 | encodedVal; 351 | 352 | var urlParams = self.urlParams = {}; 353 | forEach(url.split(/\W/), function(param){ 354 | if (!(new RegExp("^\\d+$").test(param)) && param && (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { 355 | urlParams[param] = true; 356 | } 357 | }); 358 | url = url.replace(/\\:/g, ':'); 359 | 360 | params = params || {}; 361 | forEach(self.urlParams, function(_, urlParam){ 362 | val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 363 | if (angular.isDefined(val) && val !== null) { 364 | encodedVal = encodeUriSegment(val); 365 | url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1"); 366 | } else { 367 | url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, 368 | leadingSlashes, tail) { 369 | if (tail.charAt(0) == '/') { 370 | return tail; 371 | } else { 372 | return leadingSlashes + tail; 373 | } 374 | }); 375 | } 376 | }); 377 | 378 | // strip trailing slashes and set the url 379 | url = url.replace(/\/+$/, ''); 380 | // then replace collapse `/.` if found in the last URL path segment before the query 381 | // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` 382 | url = url.replace(/\/\.(?=\w+($|\?))/, '.'); 383 | // replace escaped `/\.` with `/.` 384 | config.url = url.replace(/\/\\\./, '/.'); 385 | 386 | 387 | // set params - delegate param encoding to $http 388 | forEach(params, function(value, key){ 389 | if (!self.urlParams[key]) { 390 | config.params = config.params || {}; 391 | config.params[key] = value; 392 | } 393 | }); 394 | } 395 | }; 396 | 397 | 398 | function ResourceFactory(url, paramDefaults, actions) { 399 | var route = new Route(url); 400 | 401 | actions = extend({}, DEFAULT_ACTIONS, actions); 402 | 403 | function extractParams(data, actionParams){ 404 | var ids = {}; 405 | actionParams = extend({}, paramDefaults, actionParams); 406 | forEach(actionParams, function(value, key){ 407 | if (isFunction(value)) { value = value(); } 408 | ids[key] = value && value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; 409 | }); 410 | return ids; 411 | } 412 | 413 | function defaultResponseInterceptor(response) { 414 | return response.resource; 415 | } 416 | 417 | function Resource(value){ 418 | copy(value || {}, this); 419 | } 420 | 421 | forEach(actions, function(action, name) { 422 | var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); 423 | 424 | Resource[name] = function(a1, a2, a3, a4) { 425 | var params = {}, data, success, error; 426 | 427 | switch(arguments.length) { 428 | case 4: 429 | error = a4; 430 | success = a3; 431 | //fallthrough 432 | case 3: 433 | case 2: 434 | if (isFunction(a2)) { 435 | if (isFunction(a1)) { 436 | success = a1; 437 | error = a2; 438 | break; 439 | } 440 | 441 | success = a2; 442 | error = a3; 443 | //fallthrough 444 | } else { 445 | params = a1; 446 | data = a2; 447 | success = a3; 448 | break; 449 | } 450 | case 1: 451 | if (isFunction(a1)) success = a1; 452 | else if (hasBody) data = a1; 453 | else params = a1; 454 | break; 455 | case 0: break; 456 | default: 457 | throw $resourceMinErr('badargs', 458 | "Expected up to 4 arguments [params, data, success, error], got {0} arguments", arguments.length); 459 | } 460 | 461 | var isInstanceCall = data instanceof Resource; 462 | var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); 463 | var httpConfig = {}; 464 | var responseInterceptor = action.interceptor && action.interceptor.response || defaultResponseInterceptor; 465 | var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || undefined; 466 | 467 | forEach(action, function(value, key) { 468 | if (key != 'params' && key != 'isArray' && key != 'interceptor') { 469 | httpConfig[key] = copy(value); 470 | } 471 | }); 472 | 473 | httpConfig.data = data; 474 | route.setUrlParams(httpConfig, extend({}, extractParams(data, action.params || {}), params), action.url); 475 | 476 | var promise = $http(httpConfig).then(function(response) { 477 | var data = response.data, 478 | promise = value.$promise; 479 | 480 | if (data) { 481 | if ( angular.isArray(data) != !!action.isArray ) { 482 | throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected response' + 483 | ' to contain an {0} but got an {1}', 484 | action.isArray?'array':'object', angular.isArray(data)?'array':'object'); 485 | } 486 | if (action.isArray) { 487 | value.length = 0; 488 | forEach(data, function(item) { 489 | value.push(new Resource(item)); 490 | }); 491 | } else { 492 | copy(data, value); 493 | value.$promise = promise; 494 | } 495 | } 496 | 497 | value.$resolved = true; 498 | 499 | (success||noop)(value, response.headers); 500 | 501 | response.resource = value; 502 | 503 | return response; 504 | }, function(response) { 505 | value.$resolved = true; 506 | 507 | (error||noop)(response); 508 | 509 | return $q.reject(response); 510 | }).then(responseInterceptor, responseErrorInterceptor); 511 | 512 | 513 | if (!isInstanceCall) { 514 | // we are creating instance / collection 515 | // - set the initial promise 516 | // - return the instance / collection 517 | value.$promise = promise; 518 | value.$resolved = false; 519 | 520 | return value; 521 | } 522 | 523 | // instance call 524 | return promise; 525 | }; 526 | 527 | 528 | Resource.prototype['$' + name] = function(params, success, error) { 529 | if (isFunction(params)) { 530 | error = success; success = params; params = {}; 531 | } 532 | var result = Resource[name](params, this, success, error); 533 | return result.$promise || result; 534 | }; 535 | }); 536 | 537 | Resource.bind = function(additionalParamDefaults){ 538 | return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); 539 | }; 540 | 541 | return Resource; 542 | } 543 | 544 | return ResourceFactory; 545 | }]); 546 | 547 | 548 | })(window, window.angular); 549 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/lib/angular-route-1.2.0rc1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.2.0rc1 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) {'use strict'; 7 | 8 | var copy = angular.copy, 9 | equals = angular.equals, 10 | extend = angular.extend, 11 | forEach = angular.forEach, 12 | isDefined = angular.isDefined, 13 | isFunction = angular.isFunction, 14 | isString = angular.isString, 15 | jqLite = angular.element, 16 | noop = angular.noop, 17 | toJson = angular.toJson; 18 | 19 | 20 | function inherit(parent, extra) { 21 | return extend(new (extend(function() {}, {prototype:parent}))(), extra); 22 | } 23 | 24 | /** 25 | * @ngdoc overview 26 | * @name ngRoute 27 | * @description 28 | * 29 | * Module that provides routing and deeplinking services and directives for angular apps. 30 | */ 31 | 32 | var ngRouteModule = angular.module('ngRoute', ['ng']). 33 | provider('$route', $RouteProvider); 34 | 35 | /** 36 | * @ngdoc object 37 | * @name ngRoute.$routeProvider 38 | * @function 39 | * 40 | * @description 41 | * 42 | * Used for configuring routes. See {@link ngRoute.$route $route} for an example. 43 | */ 44 | function $RouteProvider(){ 45 | var routes = {}; 46 | 47 | /** 48 | * @ngdoc method 49 | * @name ngRoute.$routeProvider#when 50 | * @methodOf ngRoute.$routeProvider 51 | * 52 | * @param {string} path Route path (matched against `$location.path`). If `$location.path` 53 | * contains redundant trailing slash or is missing one, the route will still match and the 54 | * `$location.path` will be updated to add or drop the trailing slash to exactly match the 55 | * route definition. 56 | * 57 | * * `path` can contain named groups starting with a colon (`:name`). All characters up 58 | * to the next slash are matched and stored in `$routeParams` under the given `name` 59 | * when the route matches. 60 | * * `path` can contain named groups starting with a colon and ending with a star (`:name*`). 61 | * All characters are eagerly stored in `$routeParams` under the given `name` 62 | * when the route matches. 63 | * * `path` can contain optional named groups with a question mark (`:name?`). 64 | * 65 | * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match 66 | * `/color/brown/largecode/code/with/slashs/edit` and extract: 67 | * 68 | * * `color: brown` 69 | * * `largecode: code/with/slashs`. 70 | * 71 | * 72 | * @param {Object} route Mapping information to be assigned to `$route.current` on route 73 | * match. 74 | * 75 | * Object properties: 76 | * 77 | * - `controller` – `{(string|function()=}` – Controller fn that should be associated with newly 78 | * created scope or the name of a {@link angular.Module#controller registered controller} 79 | * if passed as a string. 80 | * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be 81 | * published to scope under the `controllerAs` name. 82 | * - `template` – `{string=|function()=}` – html template as a string or a function that 83 | * returns an html template as a string which should be used by {@link 84 | * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. 85 | * This property takes precedence over `templateUrl`. 86 | * 87 | * If `template` is a function, it will be called with the following parameters: 88 | * 89 | * - `{Array.}` - route parameters extracted from the current 90 | * `$location.path()` by applying the current route 91 | * 92 | * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html 93 | * template that should be used by {@link ngRoute.directive:ngView ngView}. 94 | * 95 | * If `templateUrl` is a function, it will be called with the following parameters: 96 | * 97 | * - `{Array.}` - route parameters extracted from the current 98 | * `$location.path()` by applying the current route 99 | * 100 | * - `resolve` - `{Object.=}` - An optional map of dependencies which should 101 | * be injected into the controller. If any of these dependencies are promises, they will be 102 | * resolved and converted to a value before the controller is instantiated and the 103 | * `$routeChangeSuccess` event is fired. The map object is: 104 | * 105 | * - `key` – `{string}`: a name of a dependency to be injected into the controller. 106 | * - `factory` - `{string|function}`: If `string` then it is an alias for a service. 107 | * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} 108 | * and the return value is treated as the dependency. If the result is a promise, it is resolved 109 | * before its value is injected into the controller. Be aware that `ngRoute.$routeParams` will 110 | * still refer to the previous route within these resolve functions. Use `$route.current.params` 111 | * to access the new route parameters, instead. 112 | * 113 | * - `redirectTo` – {(string|function())=} – value to update 114 | * {@link ng.$location $location} path with and trigger route redirection. 115 | * 116 | * If `redirectTo` is a function, it will be called with the following parameters: 117 | * 118 | * - `{Object.}` - route parameters extracted from the current 119 | * `$location.path()` by applying the current route templateUrl. 120 | * - `{string}` - current `$location.path()` 121 | * - `{Object}` - current `$location.search()` 122 | * 123 | * The custom `redirectTo` function is expected to return a string which will be used 124 | * to update `$location.path()` and `$location.search()`. 125 | * 126 | * - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search() 127 | * changes. 128 | * 129 | * If the option is set to `false` and url in the browser changes, then 130 | * `$routeUpdate` event is broadcasted on the root scope. 131 | * 132 | * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive 133 | * 134 | * If the option is set to `true`, then the particular route can be matched without being 135 | * case sensitive 136 | * 137 | * @returns {Object} self 138 | * 139 | * @description 140 | * Adds a new route definition to the `$route` service. 141 | */ 142 | this.when = function(path, route) { 143 | routes[path] = extend( 144 | {reloadOnSearch: true}, 145 | route, 146 | path && pathRegExp(path, route) 147 | ); 148 | 149 | // create redirection for trailing slashes 150 | if (path) { 151 | var redirectPath = (path[path.length-1] == '/') 152 | ? path.substr(0, path.length-1) 153 | : path +'/'; 154 | 155 | routes[redirectPath] = extend( 156 | {redirectTo: path}, 157 | pathRegExp(redirectPath, route) 158 | ); 159 | } 160 | 161 | return this; 162 | }; 163 | 164 | /** 165 | * @param path {string} path 166 | * @param opts {Object} options 167 | * @return {?Object} 168 | * 169 | * @description 170 | * Normalizes the given path, returning a regular expression 171 | * and the original path. 172 | * 173 | * Inspired by pathRexp in visionmedia/express/lib/utils.js. 174 | */ 175 | function pathRegExp(path, opts) { 176 | var insensitive = opts.caseInsensitiveMatch, 177 | ret = { 178 | originalPath: path, 179 | regexp: path 180 | }, 181 | keys = ret.keys = []; 182 | 183 | path = path 184 | .replace(/([().])/g, '\\$1') 185 | .replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){ 186 | var optional = option === '?' ? option : null; 187 | var star = option === '*' ? option : null; 188 | keys.push({ name: key, optional: !!optional }); 189 | slash = slash || ''; 190 | return '' 191 | + (optional ? '' : slash) 192 | + '(?:' 193 | + (optional ? slash : '') 194 | + (star && '(.+)?' || '([^/]+)?') + ')' 195 | + (optional || ''); 196 | }) 197 | .replace(/([\/$\*])/g, '\\$1'); 198 | 199 | ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); 200 | return ret; 201 | } 202 | 203 | /** 204 | * @ngdoc method 205 | * @name ngRoute.$routeProvider#otherwise 206 | * @methodOf ngRoute.$routeProvider 207 | * 208 | * @description 209 | * Sets route definition that will be used on route change when no other route definition 210 | * is matched. 211 | * 212 | * @param {Object} params Mapping information to be assigned to `$route.current`. 213 | * @returns {Object} self 214 | */ 215 | this.otherwise = function(params) { 216 | this.when(null, params); 217 | return this; 218 | }; 219 | 220 | 221 | this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache', '$sce', 222 | function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { 223 | 224 | /** 225 | * @ngdoc object 226 | * @name ngRoute.$route 227 | * @requires $location 228 | * @requires $routeParams 229 | * 230 | * @property {Object} current Reference to the current route definition. 231 | * The route definition contains: 232 | * 233 | * - `controller`: The controller constructor as define in route definition. 234 | * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for 235 | * controller instantiation. The `locals` contain 236 | * the resolved values of the `resolve` map. Additionally the `locals` also contain: 237 | * 238 | * - `$scope` - The current route scope. 239 | * - `$template` - The current route template HTML. 240 | * 241 | * @property {Array.} routes Array of all configured routes. 242 | * 243 | * @description 244 | * Is used for deep-linking URLs to controllers and views (HTML partials). 245 | * It watches `$location.url()` and tries to map the path to an existing route definition. 246 | * 247 | * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. 248 | * 249 | * The `$route` service is typically used in conjunction with {@link ngRoute.directive:ngView ngView} 250 | * directive and the {@link ngRoute.$routeParams $routeParams} service. 251 | * 252 | * @example 253 | This example shows how changing the URL hash causes the `$route` to match a route against the 254 | URL, and the `ngView` pulls in the partial. 255 | 256 | Note that this example is using {@link ng.directive:script inlined templates} 257 | to get it working on jsfiddle as well. 258 | 259 | 260 | 261 |
262 | Choose: 263 | Moby | 264 | Moby: Ch1 | 265 | Gatsby | 266 | Gatsby: Ch4 | 267 | Scarlet Letter
268 | 269 |
270 |
271 | 272 |
$location.path() = {{$location.path()}}
273 |
$route.current.templateUrl = {{$route.current.templateUrl}}
274 |
$route.current.params = {{$route.current.params}}
275 |
$route.current.scope.name = {{$route.current.scope.name}}
276 |
$routeParams = {{$routeParams}}
277 |
278 |
279 | 280 | 281 | controller: {{name}}
282 | Book Id: {{params.bookId}}
283 |
284 | 285 | 286 | controller: {{name}}
287 | Book Id: {{params.bookId}}
288 | Chapter Id: {{params.chapterId}} 289 |
290 | 291 | 292 | angular.module('ngView', ['ngRoute']).config(function($routeProvider, $locationProvider) { 293 | $routeProvider.when('/Book/:bookId', { 294 | templateUrl: 'book.html', 295 | controller: BookCntl, 296 | resolve: { 297 | // I will cause a 1 second delay 298 | delay: function($q, $timeout) { 299 | var delay = $q.defer(); 300 | $timeout(delay.resolve, 1000); 301 | return delay.promise; 302 | } 303 | } 304 | }); 305 | $routeProvider.when('/Book/:bookId/ch/:chapterId', { 306 | templateUrl: 'chapter.html', 307 | controller: ChapterCntl 308 | }); 309 | 310 | // configure html5 to get links working on jsfiddle 311 | $locationProvider.html5Mode(true); 312 | }); 313 | 314 | function MainCntl($scope, $route, $routeParams, $location) { 315 | $scope.$route = $route; 316 | $scope.$location = $location; 317 | $scope.$routeParams = $routeParams; 318 | } 319 | 320 | function BookCntl($scope, $routeParams) { 321 | $scope.name = "BookCntl"; 322 | $scope.params = $routeParams; 323 | } 324 | 325 | function ChapterCntl($scope, $routeParams) { 326 | $scope.name = "ChapterCntl"; 327 | $scope.params = $routeParams; 328 | } 329 | 330 | 331 | 332 | it('should load and compile correct template', function() { 333 | element('a:contains("Moby: Ch1")').click(); 334 | var content = element('.doc-example-live [ng-view]').text(); 335 | expect(content).toMatch(/controller\: ChapterCntl/); 336 | expect(content).toMatch(/Book Id\: Moby/); 337 | expect(content).toMatch(/Chapter Id\: 1/); 338 | 339 | element('a:contains("Scarlet")').click(); 340 | sleep(2); // promises are not part of scenario waiting 341 | content = element('.doc-example-live [ng-view]').text(); 342 | expect(content).toMatch(/controller\: BookCntl/); 343 | expect(content).toMatch(/Book Id\: Scarlet/); 344 | }); 345 | 346 |
347 | */ 348 | 349 | /** 350 | * @ngdoc event 351 | * @name ngRoute.$route#$routeChangeStart 352 | * @eventOf ngRoute.$route 353 | * @eventType broadcast on root scope 354 | * @description 355 | * Broadcasted before a route change. At this point the route services starts 356 | * resolving all of the dependencies needed for the route change to occurs. 357 | * Typically this involves fetching the view template as well as any dependencies 358 | * defined in `resolve` route property. Once all of the dependencies are resolved 359 | * `$routeChangeSuccess` is fired. 360 | * 361 | * @param {Route} next Future route information. 362 | * @param {Route} current Current route information. 363 | */ 364 | 365 | /** 366 | * @ngdoc event 367 | * @name ngRoute.$route#$routeChangeSuccess 368 | * @eventOf ngRoute.$route 369 | * @eventType broadcast on root scope 370 | * @description 371 | * Broadcasted after a route dependencies are resolved. 372 | * {@link ngRoute.directive:ngView ngView} listens for the directive 373 | * to instantiate the controller and render the view. 374 | * 375 | * @param {Object} angularEvent Synthetic event object. 376 | * @param {Route} current Current route information. 377 | * @param {Route|Undefined} previous Previous route information, or undefined if current is first route entered. 378 | */ 379 | 380 | /** 381 | * @ngdoc event 382 | * @name ngRoute.$route#$routeChangeError 383 | * @eventOf ngRoute.$route 384 | * @eventType broadcast on root scope 385 | * @description 386 | * Broadcasted if any of the resolve promises are rejected. 387 | * 388 | * @param {Route} current Current route information. 389 | * @param {Route} previous Previous route information. 390 | * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. 391 | */ 392 | 393 | /** 394 | * @ngdoc event 395 | * @name ngRoute.$route#$routeUpdate 396 | * @eventOf ngRoute.$route 397 | * @eventType broadcast on root scope 398 | * @description 399 | * 400 | * The `reloadOnSearch` property has been set to false, and we are reusing the same 401 | * instance of the Controller. 402 | */ 403 | 404 | var forceReload = false, 405 | $route = { 406 | routes: routes, 407 | 408 | /** 409 | * @ngdoc method 410 | * @name ngRoute.$route#reload 411 | * @methodOf ngRoute.$route 412 | * 413 | * @description 414 | * Causes `$route` service to reload the current route even if 415 | * {@link ng.$location $location} hasn't changed. 416 | * 417 | * As a result of that, {@link ngRoute.directive:ngView ngView} 418 | * creates new scope, reinstantiates the controller. 419 | */ 420 | reload: function() { 421 | forceReload = true; 422 | $rootScope.$evalAsync(updateRoute); 423 | } 424 | }; 425 | 426 | $rootScope.$on('$locationChangeSuccess', updateRoute); 427 | 428 | return $route; 429 | 430 | ///////////////////////////////////////////////////// 431 | 432 | /** 433 | * @param on {string} current url 434 | * @param route {Object} route regexp to match the url against 435 | * @return {?Object} 436 | * 437 | * @description 438 | * Check if the route matches the current url. 439 | * 440 | * Inspired by match in 441 | * visionmedia/express/lib/router/router.js. 442 | */ 443 | function switchRouteMatcher(on, route) { 444 | var keys = route.keys, 445 | params = {}; 446 | 447 | if (!route.regexp) return null; 448 | 449 | var m = route.regexp.exec(on); 450 | if (!m) return null; 451 | 452 | var N = 0; 453 | for (var i = 1, len = m.length; i < len; ++i) { 454 | var key = keys[i - 1]; 455 | 456 | var val = 'string' == typeof m[i] 457 | ? decodeURIComponent(m[i]) 458 | : m[i]; 459 | 460 | if (key && val) { 461 | params[key.name] = val; 462 | } 463 | } 464 | return params; 465 | } 466 | 467 | function updateRoute() { 468 | var next = parseRoute(), 469 | last = $route.current; 470 | 471 | if (next && last && next.$$route === last.$$route 472 | && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) { 473 | last.params = next.params; 474 | copy(last.params, $routeParams); 475 | $rootScope.$broadcast('$routeUpdate', last); 476 | } else if (next || last) { 477 | forceReload = false; 478 | $rootScope.$broadcast('$routeChangeStart', next, last); 479 | $route.current = next; 480 | if (next) { 481 | if (next.redirectTo) { 482 | if (isString(next.redirectTo)) { 483 | $location.path(interpolate(next.redirectTo, next.params)).search(next.params) 484 | .replace(); 485 | } else { 486 | $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) 487 | .replace(); 488 | } 489 | } 490 | } 491 | 492 | $q.when(next). 493 | then(function() { 494 | if (next) { 495 | var locals = extend({}, next.resolve), 496 | template, templateUrl; 497 | 498 | forEach(locals, function(value, key) { 499 | locals[key] = isString(value) ? $injector.get(value) : $injector.invoke(value); 500 | }); 501 | 502 | if (isDefined(template = next.template)) { 503 | if (isFunction(template)) { 504 | template = template(next.params); 505 | } 506 | } else if (isDefined(templateUrl = next.templateUrl)) { 507 | if (isFunction(templateUrl)) { 508 | templateUrl = templateUrl(next.params); 509 | } 510 | templateUrl = $sce.getTrustedResourceUrl(templateUrl); 511 | if (isDefined(templateUrl)) { 512 | next.loadedTemplateUrl = templateUrl; 513 | template = $http.get(templateUrl, {cache: $templateCache}). 514 | then(function(response) { return response.data; }); 515 | } 516 | } 517 | if (isDefined(template)) { 518 | locals['$template'] = template; 519 | } 520 | return $q.all(locals); 521 | } 522 | }). 523 | // after route change 524 | then(function(locals) { 525 | if (next == $route.current) { 526 | if (next) { 527 | next.locals = locals; 528 | copy(next.params, $routeParams); 529 | } 530 | $rootScope.$broadcast('$routeChangeSuccess', next, last); 531 | } 532 | }, function(error) { 533 | if (next == $route.current) { 534 | $rootScope.$broadcast('$routeChangeError', next, last, error); 535 | } 536 | }); 537 | } 538 | } 539 | 540 | 541 | /** 542 | * @returns the current active route, by matching it against the URL 543 | */ 544 | function parseRoute() { 545 | // Match a route 546 | var params, match; 547 | forEach(routes, function(route, path) { 548 | if (!match && (params = switchRouteMatcher($location.path(), route))) { 549 | match = inherit(route, { 550 | params: extend({}, $location.search(), params), 551 | pathParams: params}); 552 | match.$$route = route; 553 | } 554 | }); 555 | // No route matched; fallback to "otherwise" route 556 | return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); 557 | } 558 | 559 | /** 560 | * @returns interpolation of the redirect path with the parameters 561 | */ 562 | function interpolate(string, params) { 563 | var result = []; 564 | forEach((string||'').split(':'), function(segment, i) { 565 | if (i == 0) { 566 | result.push(segment); 567 | } else { 568 | var segmentMatch = segment.match(/(\w+)(.*)/); 569 | var key = segmentMatch[1]; 570 | result.push(params[key]); 571 | result.push(segmentMatch[2] || ''); 572 | delete params[key]; 573 | } 574 | }); 575 | return result.join(''); 576 | } 577 | }]; 578 | } 579 | 580 | ngRouteModule.provider('$routeParams', $RouteParamsProvider); 581 | 582 | 583 | /** 584 | * @ngdoc object 585 | * @name ngRoute.$routeParams 586 | * @requires $route 587 | * 588 | * @description 589 | * Current set of route parameters. The route parameters are a combination of the 590 | * {@link ng.$location $location} `search()`, and `path()`. The `path` parameters 591 | * are extracted when the {@link ngRoute.$route $route} path is matched. 592 | * 593 | * In case of parameter name collision, `path` params take precedence over `search` params. 594 | * 595 | * The service guarantees that the identity of the `$routeParams` object will remain unchanged 596 | * (but its properties will likely change) even when a route change occurs. 597 | * 598 | * Note that the `$routeParams` are only updated *after* a route change completes successfully. 599 | * This means that you cannot rely on `$routeParams` being correct in route resolve functions. 600 | * Instead you can use `$route.current.params` to access the new route's parameters. 601 | * 602 | * @example 603 | *
604 |  *  // Given:
605 |  *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
606 |  *  // Route: /Chapter/:chapterId/Section/:sectionId
607 |  *  //
608 |  *  // Then
609 |  *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
610 |  * 
611 | */ 612 | function $RouteParamsProvider() { 613 | this.$get = function() { return {}; }; 614 | } 615 | 616 | /** 617 | * @ngdoc directive 618 | * @name ngRoute.directive:ngView 619 | * @restrict ECA 620 | * 621 | * @description 622 | * # Overview 623 | * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by 624 | * including the rendered template of the current route into the main layout (`index.html`) file. 625 | * Every time the current route changes, the included view changes with it according to the 626 | * configuration of the `$route` service. 627 | * 628 | * @animations 629 | * enter - animation is used to bring new content into the browser. 630 | * leave - animation is used to animate existing content away. 631 | * 632 | * The enter and leave animation occur concurrently. 633 | * 634 | * @scope 635 | * @example 636 | 637 | 638 |
639 | Choose: 640 | Moby | 641 | Moby: Ch1 | 642 | Gatsby | 643 | Gatsby: Ch4 | 644 | Scarlet Letter
645 | 646 |
647 |
648 |
649 |
650 | 651 |
$location.path() = {{main.$location.path()}}
652 |
$route.current.templateUrl = {{main.$route.current.templateUrl}}
653 |
$route.current.params = {{main.$route.current.params}}
654 |
$route.current.scope.name = {{main.$route.current.scope.name}}
655 |
$routeParams = {{main.$routeParams}}
656 |
657 |
658 | 659 | 660 |
661 | controller: {{book.name}}
662 | Book Id: {{book.params.bookId}}
663 |
664 |
665 | 666 | 667 |
668 | controller: {{chapter.name}}
669 | Book Id: {{chapter.params.bookId}}
670 | Chapter Id: {{chapter.params.chapterId}} 671 |
672 |
673 | 674 | 675 | .example-animate-container { 676 | position:relative; 677 | background:white; 678 | border:1px solid black; 679 | height:40px; 680 | overflow:hidden; 681 | } 682 | 683 | .example-animate-container > div { 684 | padding:10px; 685 | } 686 | 687 | .view-example.ng-enter, .view-example.ng-leave { 688 | -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 689 | -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 690 | -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 691 | transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 692 | 693 | display:block; 694 | width:100%; 695 | border-left:1px solid black; 696 | 697 | position:absolute; 698 | top:0; 699 | left:0; 700 | right:0; 701 | bottom:0; 702 | padding:10px; 703 | } 704 | 705 | .example-animate-container { 706 | position:relative; 707 | height:100px; 708 | } 709 | 710 | .view-example.ng-enter { 711 | left:100%; 712 | } 713 | .view-example.ng-enter.ng-enter-active { 714 | left:0; 715 | } 716 | 717 | .view-example.ng-leave { } 718 | .view-example.ng-leave.ng-leave-active { 719 | left:-100%; 720 | } 721 | 722 | 723 | 724 | angular.module('ngViewExample', ['ngRoute', 'ngAnimate'], function($routeProvider, $locationProvider) { 725 | $routeProvider.when('/Book/:bookId', { 726 | templateUrl: 'book.html', 727 | controller: BookCntl, 728 | controllerAs: 'book' 729 | }); 730 | $routeProvider.when('/Book/:bookId/ch/:chapterId', { 731 | templateUrl: 'chapter.html', 732 | controller: ChapterCntl, 733 | controllerAs: 'chapter' 734 | }); 735 | 736 | // configure html5 to get links working on jsfiddle 737 | $locationProvider.html5Mode(true); 738 | }); 739 | 740 | function MainCntl($route, $routeParams, $location) { 741 | this.$route = $route; 742 | this.$location = $location; 743 | this.$routeParams = $routeParams; 744 | } 745 | 746 | function BookCntl($routeParams) { 747 | this.name = "BookCntl"; 748 | this.params = $routeParams; 749 | } 750 | 751 | function ChapterCntl($routeParams) { 752 | this.name = "ChapterCntl"; 753 | this.params = $routeParams; 754 | } 755 | 756 | 757 | 758 | it('should load and compile correct template', function() { 759 | element('a:contains("Moby: Ch1")').click(); 760 | var content = element('.doc-example-live [ng-view]').text(); 761 | expect(content).toMatch(/controller\: ChapterCntl/); 762 | expect(content).toMatch(/Book Id\: Moby/); 763 | expect(content).toMatch(/Chapter Id\: 1/); 764 | 765 | element('a:contains("Scarlet")').click(); 766 | content = element('.doc-example-live [ng-view]').text(); 767 | expect(content).toMatch(/controller\: BookCntl/); 768 | expect(content).toMatch(/Book Id\: Scarlet/); 769 | }); 770 | 771 |
772 | */ 773 | 774 | 775 | /** 776 | * @ngdoc event 777 | * @name ngRoute.directive:ngView#$viewContentLoaded 778 | * @eventOf ngRoute.directive:ngView 779 | * @eventType emit on the current ngView scope 780 | * @description 781 | * Emitted every time the ngView content is reloaded. 782 | */ 783 | var NG_VIEW_PRIORITY = 500; 784 | var ngViewDirective = ['$route', '$anchorScroll', '$compile', '$controller', '$animate', 785 | function($route, $anchorScroll, $compile, $controller, $animate) { 786 | return { 787 | restrict: 'ECA', 788 | terminal: true, 789 | priority: NG_VIEW_PRIORITY, 790 | compile: function(element, attr) { 791 | var onloadExp = attr.onload || ''; 792 | 793 | element.html(''); 794 | var anchor = jqLite(document.createComment(' ngView ')); 795 | element.replaceWith(anchor); 796 | 797 | return function(scope) { 798 | var currentScope, currentElement; 799 | 800 | scope.$on('$routeChangeSuccess', update); 801 | update(); 802 | 803 | function cleanupLastView() { 804 | if (currentScope) { 805 | currentScope.$destroy(); 806 | currentScope = null; 807 | } 808 | if(currentElement) { 809 | $animate.leave(currentElement); 810 | currentElement = null; 811 | } 812 | } 813 | 814 | function update() { 815 | var locals = $route.current && $route.current.locals, 816 | template = locals && locals.$template; 817 | 818 | if (template) { 819 | cleanupLastView(); 820 | 821 | currentScope = scope.$new(); 822 | currentElement = element.clone(); 823 | currentElement.html(template); 824 | $animate.enter(currentElement, null, anchor); 825 | 826 | var link = $compile(currentElement, false, NG_VIEW_PRIORITY - 1), 827 | current = $route.current; 828 | 829 | if (current.controller) { 830 | locals.$scope = currentScope; 831 | var controller = $controller(current.controller, locals); 832 | if (current.controllerAs) { 833 | currentScope[current.controllerAs] = controller; 834 | } 835 | currentElement.data('$ngControllerController', controller); 836 | currentElement.children().data('$ngControllerController', controller); 837 | } 838 | 839 | current.scope = currentScope; 840 | 841 | link(currentScope); 842 | 843 | currentScope.$emit('$viewContentLoaded'); 844 | currentScope.$eval(onloadExp); 845 | 846 | // $anchorScroll might listen on event... 847 | $anchorScroll(); 848 | } else { 849 | cleanupLastView(); 850 | } 851 | } 852 | } 853 | } 854 | }; 855 | }]; 856 | 857 | ngRouteModule.directive('ngView', ngViewDirective); 858 | 859 | 860 | })(window, window.angular); 861 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/menu.js: -------------------------------------------------------------------------------- 1 | var sidebarMenu = angular.module('sidebarMenu', ['ngRoute']) 2 | .config(function ($locationProvider, $routeProvider) { 3 | // browser reload doesn't work when html5 mode is turned on.. 4 | //$locationProvider.html5Mode(true); 5 | $routeProvider 6 | .when('/', {templateUrl: '/partials/hello.html'}) 7 | .when('/sharing-data', {templateUrl: '/partials/sharing-data.html'}) 8 | .when('/filters-and-directives', {templateUrl: '/partials/filters-and-directives.html'}) 9 | .when('/controller-and-directives', {templateUrl: '/partials/controller-and-directives.html'}) 10 | .when('/scope-isolation', {templateUrl: '/partials/scope-isolation.html'}) 11 | .when('/raffler', {templateUrl: '/partials/raffler.html'}) 12 | .when('/tabs', {templateUrl: '/partials/tabs.html'}) 13 | .when('/persons', {templateUrl: '/partials/persons.html'}) 14 | .otherwise({redirectTo: '/'}) 15 | }); 16 | 17 | sidebarMenu.controller("MenuCtrl", function ($scope, $location, Menu) { 18 | $scope.menu = Menu; 19 | 20 | /* 21 | See: http://stackoverflow.com/questions/12592472/how-to-highlight-a-current-menu-item-in-angularjs 22 | */ 23 | $scope.getClass = function (item) { 24 | //console.log("location.path=" + $location.path()) 25 | //console.log("item.href=" + item.href) 26 | //if ($location.path() == item.href) { 27 | if ($location.path() == item.href.substr(2)) { 28 | return "active" 29 | } else { 30 | return "" 31 | } 32 | } 33 | }); 34 | 35 | sidebarMenu.directive("menu", function () { 36 | return { 37 | restrict: "A", 38 | template: '' 42 | } 43 | }); 44 | 45 | sidebarMenu.factory('Menu', function () { 46 | var Menu = {}; 47 | Menu.items = [ 48 | { 49 | class: "", 50 | href: "/#!/index.html", 51 | //href: "/index.html", 52 | name: "Hello world" 53 | }, 54 | { 55 | class: "", 56 | href: "/#/sharing-data", 57 | //href: "/sharing-data", 58 | name: "Sharing data" 59 | }, 60 | { 61 | class: "", 62 | href: "/#/filters-and-directives", 63 | //href: "/filters-and-directives", 64 | name: "Filters & directives" 65 | }, 66 | { 67 | class: "", 68 | href: "/#/controller-and-directives", 69 | //href: "/controller-and-directives", 70 | name: "Controller & directives" 71 | }, 72 | { 73 | class: "", 74 | href: "/#/scope-isolation", 75 | //href: "/scope-isolation", 76 | name: "Scope isolation" 77 | }, 78 | { 79 | class: "", 80 | href: "/#/raffler", 81 | //href: "/raffler", 82 | name: "Raffle example" 83 | }, 84 | { 85 | class: "", 86 | href: "/#/tabs", 87 | //href: "/tabs", 88 | name: "Tabs" 89 | }, 90 | { 91 | class: "", 92 | href: "/#/persons", 93 | //href: "/persons", 94 | name: "Person Admin" 95 | } 96 | ]; 97 | return Menu; 98 | }); 99 | 100 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/persons.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Per Spilling, per@kodemaker.no 3 | */ 4 | var myApp = angular.module('persons', ['ngResource', 'ui.bootstrap'], function ($dialogProvider) { 5 | $dialogProvider.options({backdropClick: false, dialogFade: true}); 6 | }); 7 | 8 | /** 9 | * Configure the PersonsResource. In order to solve the Single Origin Policy issue in the browser 10 | * I have set up a Jetty proxy servlet to forward requests transparently to the API server. 11 | * See the web.xml file for details on that. 12 | */ 13 | myApp.factory('PersonsResource', function ($resource) { 14 | return $resource('/api/persons', {}, {}); 15 | }); 16 | 17 | myApp.factory('PersonResource', function ($resource) { 18 | return $resource('/api/persons/:id', {}, {}); 19 | }); 20 | 21 | function PersonsCtrl($scope, PersonsResource, PersonResource, $dialog, $q) { 22 | /** 23 | * Define an object that will hold data for the form. The persons list will be pre-loaded with the list of 24 | * persons from the server. The personForm.person object is bound to the person form in the HTML via the 25 | * ng-model directive. 26 | */ 27 | $scope.personForm = { 28 | show: true, 29 | person: {} 30 | } 31 | $scope.persons = PersonsResource.query(); 32 | 33 | /** 34 | * Function used to toggle the show variable between true and false, which in turn determines if the person form 35 | * should be displayed of not. 36 | */ 37 | $scope.togglePersonForm = function () { 38 | $scope.personForm.show = !$scope.personForm.show; 39 | } 40 | 41 | /** 42 | * Clear the person data from the form. 43 | */ 44 | $scope.clearForm = function () { 45 | $scope.personForm.person = {} 46 | } 47 | 48 | /** 49 | * Save a person. Make sure that a person object is present before calling the service. 50 | */ 51 | $scope.savePerson = function (person) { 52 | if (person != undefined) { 53 | /** 54 | * Here we need to ensure that the PersonsResource.query() is done after the PersonsResource.save. This 55 | * is achieved by using the $promise returned by the $resource object. 56 | */ 57 | PersonsResource.save(person).$promise.then(function() { 58 | $scope.persons = PersonsResource.query(); 59 | $scope.personForm.person = {} // clear the form 60 | }); 61 | } 62 | } 63 | 64 | /** 65 | * Set the person to be edited in the person form. 66 | */ 67 | $scope.editPerson = function (p) { 68 | $scope.personForm.person = p 69 | } 70 | 71 | /** 72 | * Delete a person. Present a modal dialog box to the user to make the user confirm that the person item really 73 | * should be deleted. 74 | */ 75 | $scope.deletePerson = function (person) { 76 | var msgBox = $dialog.messageBox('You are about to delete a person from the database', 'This cannot be undone. Are you sure?', [ 77 | {label: 'Yes', result: 'yes'}, 78 | {label: 'Cancel', result: 'no'} 79 | ]) 80 | msgBox.open().then(function (result) { 81 | if (result === 'yes') { 82 | // remove from the server and reload the person list from the server after the delete 83 | PersonResource.delete({id: person.id}).$promise.then(function() { 84 | $scope.persons = PersonsResource.query(); 85 | }); 86 | } 87 | }); 88 | } 89 | } 90 | 91 | /* 92 | $scope.kodemakerPersons = {} 93 | $scope.persons = PersonsResource.query(function (response) { 94 | angular.forEach(response, function (person) { 95 | console.log('person.name=' + person.name) 96 | }); 97 | }); 98 | */ -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/raffler.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('raffleApp', []); 2 | 3 | app.controller("RaffleCtrl", function ($scope) { 4 | $scope.entries = [ 5 | {name: "Larry"}, 6 | {name: "Curly"}, 7 | {name: "Barry"} 8 | ] 9 | 10 | // 2 versions of the addEntry() function; one without params and one with. 11 | /* 12 | $scope.addEntry = function () { 13 | $scope.entries.push($scope.newEntry) 14 | $scope.newEntry = {} 15 | } 16 | */ 17 | 18 | $scope.addEntry = function (newEntry) { 19 | $scope.entries.push(newEntry); 20 | $scope.newEntry = {} 21 | } 22 | 23 | $scope.drawWinner = function () { 24 | entry = $scope.entries[Math.floor(Math.random() * $scope.entries.length)] 25 | entry.winner = true 26 | $scope.lastWinner = entry 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/scope-isolation.js: -------------------------------------------------------------------------------- 1 | var module = angular.module('scopeIsolation', []); 2 | 3 | module.controller("ChoreCtrl", function ($scope) { 4 | $scope.logChore = function (chore) { 5 | alert(chore + " is done!"); 6 | }; 7 | $scope.callHome = function (message) { 8 | alert(message) 9 | }; 10 | $scope.ctrlFlavor = "blackberry"; 11 | $scope.ctrlFruit = "apple" 12 | }); 13 | 14 | module.directive("kid", function () { 15 | return { 16 | restrict: "E", 17 | scope: { 18 | done: "&" 19 | }, 20 | template: 21 | '
' + 22 | '
' + 23 | '' + 24 | '' + 25 | '' + 26 | '
' + 27 | '
' 28 | } 29 | }); 30 | 31 | module.directive("drink", function () { 32 | return { 33 | restrict: "E", 34 | scope: { 35 | flavor: "@" 36 | }, 37 | template: '
flavor={{flavor}}
' 38 | } 39 | }); 40 | 41 | module.directive("eat", function () { 42 | return { 43 | restrict: "E", 44 | scope: { 45 | fruit: "=" 46 | }, 47 | template: '' 48 | } 49 | }); 50 | 51 | module.directive("phone", function () { 52 | return { 53 | restrict: "E", 54 | scope: { 55 | dial: "&" 56 | }, 57 | template: 58 | '
' + 59 | '
' + 60 | '' + 61 | '' + 62 | '
' + 63 | '
' 64 | } 65 | }); 66 | 67 | 68 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/sharing-data.js: -------------------------------------------------------------------------------- 1 | var sharingData = angular.module('sharingData', []); 2 | 3 | sharingData.factory('Data', function() { 4 | return {message: "I'm data from a service"} 5 | }) 6 | 7 | // A filter function 8 | sharingData.filter('reverse', function () { 9 | return function (text) { 10 | return text.split("").reverse().join(""); 11 | } 12 | }) 13 | 14 | function SDFirstCtrl($scope, Data) { 15 | $scope.data = Data; 16 | } 17 | 18 | function SDSecondCtrl($scope, Data) { 19 | $scope.data = Data; 20 | 21 | $scope.reversedMessage = function (message) { 22 | return message.split("").reverse().join(""); 23 | } 24 | } -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/tabset.js: -------------------------------------------------------------------------------- 1 | var myApp = angular.module('tabs',[]); 2 | 3 | function MyCtrl($scope) { 4 | $scope.date= new Date(); 5 | } -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/todo.js: -------------------------------------------------------------------------------- 1 | 2 | function TodoCtrl($scope) { 3 | $scope.todos = [ 4 | {text:'learn angular', done:true}, 5 | {text:'build an angular app', done:false}]; 6 | 7 | $scope.addTodo = function() { 8 | $scope.todos.push({text:$scope.todoText, done:false}); 9 | $scope.todoText = ''; 10 | }; 11 | 12 | $scope.remaining = function() { 13 | var count = 0; 14 | angular.forEach($scope.todos, function(todo) { 15 | count += todo.done ? 0 : 1; 16 | }); 17 | return count; 18 | }; 19 | 20 | $scope.archive = function() { 21 | var oldTodos = $scope.todos; 22 | $scope.todos = []; 23 | angular.forEach(oldTodos, function(todo) { 24 | if (!todo.done) $scope.todos.push(todo); 25 | }); 26 | }; 27 | } -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/js/twitter.js: -------------------------------------------------------------------------------- 1 | var twitterApp = angular.module('twitterApp', []); 2 | 3 | twitterApp.controller("AppCtrl", function ($scope) { 4 | $scope.loadMoreTweets = function ($scope) { 5 | alert("Loading more tweets"); 6 | } 7 | 8 | $scope.deleteTweets = function ($scope) { 9 | alert("Deleting tweets"); 10 | } 11 | }) 12 | 13 | /** 14 | * A directive with a behaviour function 15 | */ 16 | twitterApp.directive("enter", function () { 17 | return function(scope, element, attrs) { 18 | element.bind("mouseenter", function () { 19 | scope.$apply(attrs.enter); 20 | }) 21 | } 22 | }) 23 | 24 | /** 25 | * A directive that will be reused by other directives 26 | */ 27 | twitterApp.directive("superhero", function () { 28 | return { 29 | restrict: "E", 30 | scope: {}, 31 | 32 | controller: function ($scope) { 33 | $scope.abilities = [] 34 | 35 | this.addStrength = function () { 36 | $scope.abilities.push("strength") 37 | } 38 | 39 | this.addSpeed = function () { 40 | $scope.abilities.push("speed") 41 | } 42 | 43 | this.addFlight = function () { 44 | $scope.abilities.push("flight") 45 | } 46 | }, 47 | 48 | link: function (scope, element) { 49 | element.addClass("btn"), 50 | element.bind("mouseenter", function () { 51 | console.log(scope.abilities) 52 | }) 53 | } 54 | } 55 | }) 56 | 57 | /** 58 | * A directive that uses (requires) the controller of another directive. 59 | */ 60 | twitterApp.directive("strength", function () { 61 | return { 62 | require: "superhero", 63 | link: function (scope, element, attrs, ctrl) { 64 | ctrl.addStrength(); 65 | } 66 | } 67 | }) 68 | 69 | twitterApp.directive("speed", function () { 70 | return { 71 | require: "superhero", 72 | link: function (scope, element, attrs, ctrl) { 73 | ctrl.addSpeed(); 74 | } 75 | } 76 | }) 77 | 78 | twitterApp.directive("flight", function () { 79 | return { 80 | require: "superhero", 81 | link: function (scope, element, attrs, ctrl) { 82 | ctrl.addFlight(); 83 | } 84 | } 85 | }) 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/partials/controller-and-directives.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
Roll over to load more tweets
5 |
Roll over to delete tweets
6 | 7 |
8 | Moving the mouse over the buttons will log a message to the console 9 | Superman 10 | The flash 11 | 12 |
13 |
-------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/partials/filters-and-directives.html: -------------------------------------------------------------------------------- 1 | 2 |

Filter example

3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
{{actor.name}}{{actor.character}}
17 |
18 | 19 |
20 |

Directive and behaviour examples

21 | 22 | 23 | 24 | 25 | 26 |
This is the place
-------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/partials/hello.html: -------------------------------------------------------------------------------- 1 |

AngularJS Hello, World!

2 | 3 |
4 |
5 | 6 | 7 | 8 |

Hello {{yourName}}!

9 | Angular can add too: {{ 1+2 }}. 10 | 11 |
12 |

Simple controller

13 | 14 |
15 | 16 | 17 |

Hello {{data.message}}!

18 |
19 |
20 |
21 |
22 |

23 | This is a little example application that I have used to learn to use the AngularJS and Dropwizard 24 | frameworks. It also serves as a project template that can be used to get a new project up and running 25 | quickly using these frameworks + Jetty & Maven. 26 |

27 |

28 | The examples have largely been taken from www.egghead.io 29 | and the Raffle example from 30 | RailsCast (subscription need). 31 |

32 |
33 |
34 |
35 | 36 |
37 |

Model scope used several places via the "dot"

38 | 39 | 40 |

{{data.message2}}

41 | 42 |
43 | 44 | 45 |

{{data.message2}}

46 |
47 | 48 |
49 | 50 | 51 |

{{data.message2}}

52 |
53 | 54 | 80 | 81 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/partials/persons.html: -------------------------------------------------------------------------------- 1 |

Simple CRUD app using RESTful backend services

2 | 3 |

4 | Note: For this example to run, make sure that the backend services in the dw-server have been 5 | started. See the README file in that module for how to do that. 6 |

7 | 8 |
9 | 10 |
11 | 12 | 16 | 18 | 20 |
21 | 22 |
23 |
24 | 25 | 26 | 27 | 28 | 33 | 34 |
35 |
36 | 37 |

List of persons

38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
OperationIdNameEmailPhone
52 | 53 | 54 | {{p.id}}{{p.name}}{{p.email}}{{p.phone}}
62 |
63 | 64 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/partials/raffler.html: -------------------------------------------------------------------------------- 1 |

Raffler

2 | 6 |
7 | 11 |
12 | 13 | 14 |
15 |
    16 |
  • 17 | {{entry.name}} 18 | WINNER 19 |
  • 20 |
21 | entries.length={{entries.length}}
22 | 23 |
-------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/partials/scope-isolation.html: -------------------------------------------------------------------------------- 1 |
2 |

Linking with '&' scope operator

3 | abc 4 | 5 |
6 |

Linking with '@' scope operator

7 | 8 | 9 | 10 |
11 |

Linking with '=' scope operator

12 | 13 | 14 | 15 |
16 |

Linking again with '&' scope operator

17 | 18 | 19 | 20 |
-------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/partials/sharing-data.html: -------------------------------------------------------------------------------- 1 |

Sharing data between controllers

2 |
3 | 4 | 5 |

{{data.message}}

6 |
7 | 8 |
9 |
10 |

Reversed message:

11 | 12 |

{{reversedMessage(data.message)}}

13 | 14 |

{{data.message|reverse}}

15 |
16 | -------------------------------------------------------------------------------- /angularjs-webapp/src/main/webapp/partials/tabs.html: -------------------------------------------------------------------------------- 1 |

Tabs example from http://angularjs.org/

2 | 3 | 4 | 5 | Date: {{ '2012-04-01' | date:'fullDate' }}
6 | Currency: {{ 123456 | currency }}
7 | Number: {{ 98765.4321 | number }}
8 |
9 | 10 |
11 |
12 | 13 |
14 |
15 |
16 |
-------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'base' 2 | 3 | allprojects { 4 | group = 'no.kodemaker.ps' 5 | version = '1.0-SNAPSHOT' 6 | } 7 | 8 | // Dependency mgmt. 9 | ext.dropwizard_version = '0.6.2' 10 | ext.jetty_version = '8.1.10.v20130312' 11 | 12 | ext.libs = [ 13 | jetty_webapp: "org.eclipse.jetty:jetty-webapp:$jetty_version", 14 | jetty_servlets: "org.eclipse.jetty:jetty-servlets:$jetty_version", 15 | servlet_jsp: 'org.glassfish.web:javax.servlet.jsp:2.2.3', 16 | dropwizard_core: "com.yammer.dropwizard:dropwizard-core:$dropwizard_version", 17 | dropwizard_jdbi: "com.yammer.dropwizard:dropwizard-jdbi:$dropwizard_version", 18 | dropwizard_db: "com.yammer.dropwizard:dropwizard-db:$dropwizard_version", 19 | h2: 'com.h2database:h2:1.3.173', 20 | jersey_client: 'com.sun.jersey:jersey-client:1.12', 21 | junit: 'junit:junit:4.8.1' 22 | ] 23 | 24 | subprojects { 25 | apply plugin: 'maven' 26 | apply plugin: 'java' 27 | sourceCompatibility = 1.7 28 | targetCompatibility = 1.7 29 | 30 | repositories { mavenCentral() } 31 | 32 | dependencies { testCompile libs.junit } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /doc/deployment_diagram.graffle/data.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ApplicationVersion 6 | 7 | com.omnigroup.OmniGraffle.MacAppStore 8 | 139.18 9 | 10 | CreationDate 11 | 2013-08-08 13:11:24 +0000 12 | Creator 13 | Per Spilling 14 | GraphDocumentVersion 15 | 8 16 | GuidesLocked 17 | NO 18 | GuidesVisible 19 | YES 20 | ImageCounter 21 | 3 22 | ImageLinkBack 23 | 24 | 25 | 26 | 27 | ImageList 28 | 29 | image2.ai 30 | image1.ai 31 | 32 | LinksVisible 33 | NO 34 | MagnetsVisible 35 | NO 36 | MasterSheets 37 | 38 | ModificationDate 39 | 2013-08-21 21:02:14 +0000 40 | Modifier 41 | Per Spilling 42 | NotesVisible 43 | NO 44 | OriginVisible 45 | NO 46 | PageBreaks 47 | YES 48 | PrintInfo 49 | 50 | NSBottomMargin 51 | 52 | float 53 | 41 54 | 55 | NSHorizonalPagination 56 | 57 | coded 58 | BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG 59 | 60 | NSLeftMargin 61 | 62 | float 63 | 18 64 | 65 | NSPaperSize 66 | 67 | size 68 | {595.28001499176025, 841.8900146484375} 69 | 70 | NSPrintReverseOrientation 71 | 72 | int 73 | 0 74 | 75 | NSRightMargin 76 | 77 | float 78 | 18 79 | 80 | NSTopMargin 81 | 82 | float 83 | 18 84 | 85 | 86 | ReadOnly 87 | NO 88 | Sheets 89 | 90 | 91 | ActiveLayerIndex 92 | 0 93 | AutoAdjust 94 | 95 | BackgroundGraphic 96 | 97 | Bounds 98 | {{0, 0}, {559.28001499176025, 782.8900146484375}} 99 | Class 100 | SolidGraphic 101 | ID 102 | 2 103 | Style 104 | 105 | shadow 106 | 107 | Draws 108 | NO 109 | 110 | stroke 111 | 112 | Draws 113 | NO 114 | 115 | 116 | 117 | BaseZoom 118 | 0 119 | CanvasOrigin 120 | {0, 0} 121 | ColumnAlign 122 | 1 123 | ColumnSpacing 124 | 36 125 | DisplayScale 126 | 1.000 cm = 1.000 cm 127 | GraphicsList 128 | 129 | 130 | Class 131 | LineGraphic 132 | Head 133 | 134 | ID 135 | 36 136 | Position 137 | 0.42412695288658142 138 | 139 | ID 140 | 39 141 | Points 142 | 143 | {360.50062033517474, 203.4115575908186} 144 | {337.5, 202.26475974917412} 145 | 146 | Style 147 | 148 | stroke 149 | 150 | HeadArrow 151 | 0 152 | Legacy 153 | 154 | LineType 155 | 1 156 | Pattern 157 | 1 158 | TailArrow 159 | 0 160 | 161 | 162 | Tail 163 | 164 | ID 165 | 15 166 | 167 | 168 | 169 | Class 170 | LineGraphic 171 | Head 172 | 173 | ID 174 | 31 175 | 176 | ID 177 | 36 178 | Points 179 | 180 | {337.5, 173} 181 | {337.50000095367432, 242} 182 | 183 | Style 184 | 185 | stroke 186 | 187 | HeadArrow 188 | FilledArrow 189 | Legacy 190 | 191 | LineType 192 | 1 193 | TailArrow 194 | 0 195 | 196 | 197 | Tail 198 | 199 | ID 200 | 30 201 | 202 | 203 | 204 | Bounds 205 | {{361, 187}, {163, 41}} 206 | Class 207 | ShapedGraphic 208 | ID 209 | 15 210 | Shape 211 | NoteShape 212 | Style 213 | 214 | fill 215 | 216 | Color 217 | 218 | b 219 | 0.4 220 | g 221 | 1 222 | r 223 | 0.8 224 | 225 | 226 | 227 | Text 228 | 229 | Text 230 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 231 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 232 | {\colortbl;\red255\green255\blue255;} 233 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc 234 | 235 | \f0\fs24 \cf0 /api proxy to localhost:9000} 236 | VerticalPad 237 | 0 238 | 239 | 240 | 241 | Bounds 242 | {{137.5, 177}, {114, 14}} 243 | Class 244 | ShapedGraphic 245 | FitText 246 | YES 247 | Flow 248 | Resize 249 | FontInfo 250 | 251 | Font 252 | Helvetica 253 | Size 254 | 12 255 | 256 | ID 257 | 47 258 | Shape 259 | Rectangle 260 | Style 261 | 262 | fill 263 | 264 | Draws 265 | NO 266 | 267 | shadow 268 | 269 | Draws 270 | NO 271 | 272 | stroke 273 | 274 | Draws 275 | NO 276 | 277 | 278 | Text 279 | 280 | Pad 281 | 0 282 | Text 283 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 284 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 285 | {\colortbl;\red255\green255\blue255;} 286 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 287 | 288 | \f0\b\fs24 \cf0 Single Origin Policy} 289 | VerticalPad 290 | 0 291 | 292 | Wrap 293 | NO 294 | 295 | 296 | Bounds 297 | {{157, 190}, {74, 69}} 298 | Class 299 | ShapedGraphic 300 | ID 301 | 46 302 | ImageID 303 | 2 304 | Shape 305 | Rectangle 306 | Style 307 | 308 | fill 309 | 310 | Draws 311 | NO 312 | 313 | shadow 314 | 315 | Draws 316 | NO 317 | 318 | stroke 319 | 320 | Draws 321 | NO 322 | 323 | 324 | 325 | 326 | Class 327 | LineGraphic 328 | Head 329 | 330 | ID 331 | 31 332 | Info 333 | 4 334 | 335 | ID 336 | 42 337 | Points 338 | 339 | {140, 112.44951629638672} 340 | {235.00000095367432, 302} 341 | 342 | Style 343 | 344 | stroke 345 | 346 | HeadArrow 347 | FilledArrow 348 | Legacy 349 | 350 | LineType 351 | 1 352 | TailArrow 353 | 0 354 | 355 | 356 | Tail 357 | 358 | ID 359 | 40 360 | Info 361 | 3 362 | 363 | 364 | 365 | Class 366 | LineGraphic 367 | Head 368 | 369 | ID 370 | 30 371 | 372 | ID 373 | 41 374 | Points 375 | 376 | {140, 112.44951629638672} 377 | {235, 113} 378 | 379 | Style 380 | 381 | stroke 382 | 383 | HeadArrow 384 | FilledArrow 385 | Legacy 386 | 387 | LineType 388 | 1 389 | TailArrow 390 | 0 391 | 392 | 393 | Tail 394 | 395 | ID 396 | 40 397 | Info 398 | 3 399 | 400 | 401 | 402 | Bounds 403 | {{56, 88.999992592773424}, {43.619720000000001, 62.074218999999999}} 404 | Class 405 | ShapedGraphic 406 | ID 407 | 13 408 | ImageID 409 | 1 410 | Shape 411 | Rectangle 412 | Style 413 | 414 | fill 415 | 416 | Draws 417 | NO 418 | 419 | shadow 420 | 421 | Draws 422 | NO 423 | 424 | stroke 425 | 426 | Draws 427 | NO 428 | 429 | 430 | 431 | 432 | Class 433 | Group 434 | Graphics 435 | 436 | 437 | Bounds 438 | {{248.00000000000003, 132}, {37.948717948717949, 12}} 439 | Class 440 | ShapedGraphic 441 | ID 442 | 23 443 | Shape 444 | Rectangle 445 | Style 446 | 447 | shadow 448 | 449 | Draws 450 | NO 451 | 452 | 453 | 454 | 455 | Bounds 456 | {{248.00000000000003, 107.00000000000001}, {37.948717948717949, 12}} 457 | Class 458 | ShapedGraphic 459 | ID 460 | 24 461 | Shape 462 | Rectangle 463 | Style 464 | 465 | shadow 466 | 467 | Draws 468 | NO 469 | 470 | 471 | 472 | 473 | Bounds 474 | {{270.76923076923089, 94.000000000000014}, {125.2307692307692, 63}} 475 | Class 476 | ShapedGraphic 477 | ID 478 | 25 479 | Magnets 480 | 481 | {0, 1} 482 | {0, -1} 483 | {1, 0} 484 | {-1, 0} 485 | 486 | Shape 487 | Rectangle 488 | Style 489 | 490 | Text 491 | 492 | Text 493 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 494 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 495 | {\colortbl;\red255\green255\blue255;} 496 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc 497 | 498 | \f0\fs24 \cf0 angularjs-webapp} 499 | VerticalPad 500 | 0 501 | 502 | 503 | 504 | ID 505 | 22 506 | 507 | 508 | Bounds 509 | {{235, 53}, {205, 120}} 510 | Class 511 | ShapedGraphic 512 | ID 513 | 30 514 | Magnets 515 | 516 | {0, 1} 517 | {0, -1} 518 | {1, 0} 519 | {-1, 0} 520 | 521 | Shape 522 | Cube 523 | Style 524 | 525 | Text 526 | 527 | Align 528 | 0 529 | Text 530 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 531 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 532 | {\colortbl;\red255\green255\blue255;} 533 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 534 | 535 | \f0\b\fs24 \cf0 Jetty, localhost:8080\ 536 | } 537 | VerticalPad 538 | 0 539 | 540 | TextPlacement 541 | 0 542 | 543 | 544 | Class 545 | Group 546 | Graphics 547 | 548 | 549 | Bounds 550 | {{256.35999345779419, 321}, {32.820512820512832, 12}} 551 | Class 552 | ShapedGraphic 553 | ID 554 | 27 555 | Shape 556 | Rectangle 557 | Style 558 | 559 | shadow 560 | 561 | Draws 562 | NO 563 | 564 | 565 | 566 | 567 | Bounds 568 | {{256.35999345779419, 296}, {32.820512820512832, 12}} 569 | Class 570 | ShapedGraphic 571 | ID 572 | 28 573 | Shape 574 | Rectangle 575 | Style 576 | 577 | shadow 578 | 579 | Draws 580 | NO 581 | 582 | 583 | 584 | 585 | Bounds 586 | {{276.05230115010187, 283}, {108.30769230769231, 63}} 587 | Class 588 | ShapedGraphic 589 | ID 590 | 29 591 | Magnets 592 | 593 | {0, 1} 594 | {0, -1} 595 | {1, 0} 596 | {-1, 0} 597 | 598 | Shape 599 | Rectangle 600 | Style 601 | 602 | Text 603 | 604 | Text 605 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 606 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 607 | {\colortbl;\red255\green255\blue255;} 608 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc 609 | 610 | \f0\fs24 \cf0 dw-server} 611 | VerticalPad 612 | 0 613 | 614 | 615 | 616 | ID 617 | 26 618 | 619 | 620 | Bounds 621 | {{235.00000095367432, 242}, {205, 120}} 622 | Class 623 | ShapedGraphic 624 | ID 625 | 31 626 | Magnets 627 | 628 | {0, 1} 629 | {0, -1} 630 | {1, 0} 631 | {-1, 0} 632 | 633 | Shape 634 | Cube 635 | Style 636 | 637 | Text 638 | 639 | Align 640 | 0 641 | Text 642 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 643 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 644 | {\colortbl;\red255\green255\blue255;} 645 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 646 | 647 | \f0\b\fs24 \cf0 Jetty, localhost:9000\ 648 | } 649 | VerticalPad 650 | 0 651 | 652 | TextPlacement 653 | 0 654 | 655 | 656 | Bounds 657 | {{37, 59.899032592773438}, {103, 105.10096740722656}} 658 | Class 659 | ShapedGraphic 660 | ID 661 | 40 662 | Magnets 663 | 664 | {0, 1} 665 | {0, -1} 666 | {1, 0} 667 | {-1, 0} 668 | 669 | Shape 670 | Cube 671 | Style 672 | 673 | Text 674 | 675 | Align 676 | 0 677 | Text 678 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 679 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 680 | {\colortbl;\red255\green255\blue255;} 681 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 682 | 683 | \f0\b\fs24 \cf0 Web browser} 684 | VerticalPad 685 | 0 686 | 687 | TextPlacement 688 | 0 689 | 690 | 691 | GridInfo 692 | 693 | HPages 694 | 1 695 | KeepToScale 696 | 697 | Layers 698 | 699 | 700 | Lock 701 | NO 702 | Name 703 | Layer 1 704 | Print 705 | YES 706 | View 707 | YES 708 | 709 | 710 | LayoutInfo 711 | 712 | Animate 713 | NO 714 | circoMinDist 715 | 18 716 | circoSeparation 717 | 0.0 718 | layoutEngine 719 | dot 720 | neatoSeparation 721 | 0.0 722 | twopiSeparation 723 | 0.0 724 | 725 | Orientation 726 | 2 727 | PrintOnePage 728 | 729 | RowAlign 730 | 1 731 | RowSpacing 732 | 36 733 | SheetTitle 734 | Canvas 2 735 | UniqueID 736 | 2 737 | VPages 738 | 1 739 | 740 | 741 | ActiveLayerIndex 742 | 0 743 | AutoAdjust 744 | 745 | BackgroundGraphic 746 | 747 | Bounds 748 | {{0, 0}, {559.28001499176025, 782.8900146484375}} 749 | Class 750 | SolidGraphic 751 | ID 752 | 2 753 | Style 754 | 755 | shadow 756 | 757 | Draws 758 | NO 759 | 760 | stroke 761 | 762 | Draws 763 | NO 764 | 765 | 766 | 767 | BaseZoom 768 | 0 769 | CanvasOrigin 770 | {0, 0} 771 | ColumnAlign 772 | 1 773 | ColumnSpacing 774 | 36 775 | DisplayScale 776 | 1.000 cm = 1.000 cm 777 | GraphicsList 778 | 779 | 780 | Class 781 | LineGraphic 782 | Head 783 | 784 | ID 785 | 36 786 | Position 787 | 0.42412695288658142 788 | 789 | ID 790 | 39 791 | Points 792 | 793 | {217.86011953891327, 174.2958643200883} 794 | {165.61860843676374, 175.43999606370926} 795 | 796 | Style 797 | 798 | stroke 799 | 800 | HeadArrow 801 | 0 802 | Legacy 803 | 804 | LineType 805 | 1 806 | Pattern 807 | 1 808 | TailArrow 809 | 0 810 | 811 | 812 | Tail 813 | 814 | ID 815 | 15 816 | 817 | 818 | 819 | Bounds 820 | {{218.35999965667725, 152}, {163, 41}} 821 | Class 822 | ShapedGraphic 823 | ID 824 | 15 825 | Shape 826 | NoteShape 827 | Style 828 | 829 | fill 830 | 831 | Color 832 | 833 | b 834 | 0.4 835 | g 836 | 1 837 | r 838 | 0.8 839 | 840 | 841 | 842 | Text 843 | 844 | Text 845 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 846 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 847 | {\colortbl;\red255\green255\blue255;} 848 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc 849 | 850 | \f0\fs24 \cf0 /api proxy to localhost:9000} 851 | VerticalPad 852 | 0 853 | 854 | 855 | 856 | Class 857 | LineGraphic 858 | Head 859 | 860 | ID 861 | 35 862 | Info 863 | 4 864 | 865 | ID 866 | 37 867 | Points 868 | 869 | {174.35999250411982, 279.5} 870 | {212.05230019642761, 279.5} 871 | 872 | Style 873 | 874 | stroke 875 | 876 | HeadArrow 877 | FilledArrow 878 | Legacy 879 | 880 | LineType 881 | 1 882 | TailArrow 883 | 0 884 | 885 | 886 | Tail 887 | 888 | ID 889 | 29 890 | Info 891 | 3 892 | 893 | 894 | 895 | Class 896 | LineGraphic 897 | Head 898 | 899 | ID 900 | 29 901 | Info 902 | 2 903 | 904 | ID 905 | 36 906 | Points 907 | 908 | {199.06460710672241, 122.00000000000001} 909 | {120.20614635027368, 248} 910 | 911 | Style 912 | 913 | stroke 914 | 915 | HeadArrow 916 | FilledArrow 917 | Legacy 918 | 919 | LineType 920 | 1 921 | TailArrow 922 | 0 923 | 924 | 925 | Tail 926 | 927 | ID 928 | 25 929 | Info 930 | 1 931 | 932 | 933 | 934 | Class 935 | Group 936 | Graphics 937 | 938 | 939 | Bounds 940 | {{192.35999250411993, 286}, {32.820512820512832, 12}} 941 | Class 942 | ShapedGraphic 943 | ID 944 | 33 945 | Shape 946 | Rectangle 947 | Style 948 | 949 | shadow 950 | 951 | Draws 952 | NO 953 | 954 | 955 | 956 | 957 | Bounds 958 | {{192.35999250411993, 261}, {32.820512820512832, 12}} 959 | Class 960 | ShapedGraphic 961 | ID 962 | 34 963 | Shape 964 | Rectangle 965 | Style 966 | 967 | shadow 968 | 969 | Draws 970 | NO 971 | 972 | 973 | 974 | 975 | Bounds 976 | {{212.05230019642764, 248}, {108.30769230769231, 63}} 977 | Class 978 | ShapedGraphic 979 | ID 980 | 35 981 | Magnets 982 | 983 | {0, 1} 984 | {0, -1} 985 | {1, 0} 986 | {-1, 0} 987 | 988 | Shape 989 | Rectangle 990 | Style 991 | 992 | Text 993 | 994 | Text 995 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 996 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 997 | {\colortbl;\red255\green255\blue255;} 998 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc 999 | 1000 | \f0\fs24 \cf0 DB} 1001 | VerticalPad 1002 | 0 1003 | 1004 | 1005 | 1006 | ID 1007 | 32 1008 | 1009 | 1010 | Class 1011 | Group 1012 | Graphics 1013 | 1014 | 1015 | Bounds 1016 | {{113.67999172210693, 97}, {37.948717948717949, 12}} 1017 | Class 1018 | ShapedGraphic 1019 | ID 1020 | 23 1021 | Shape 1022 | Rectangle 1023 | Style 1024 | 1025 | shadow 1026 | 1027 | Draws 1028 | NO 1029 | 1030 | 1031 | 1032 | 1033 | Bounds 1034 | {{113.67999172210693, 72.000000000000014}, {37.948717948717949, 12}} 1035 | Class 1036 | ShapedGraphic 1037 | ID 1038 | 24 1039 | Shape 1040 | Rectangle 1041 | Style 1042 | 1043 | shadow 1044 | 1045 | Draws 1046 | NO 1047 | 1048 | 1049 | 1050 | 1051 | Bounds 1052 | {{136.44922249133782, 59.000000000000014}, {125.2307692307692, 63}} 1053 | Class 1054 | ShapedGraphic 1055 | ID 1056 | 25 1057 | Magnets 1058 | 1059 | {0, 1} 1060 | {0, -1} 1061 | {1, 0} 1062 | {-1, 0} 1063 | 1064 | Shape 1065 | Rectangle 1066 | Style 1067 | 1068 | Text 1069 | 1070 | Text 1071 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 1072 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1073 | {\colortbl;\red255\green255\blue255;} 1074 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc 1075 | 1076 | \f0\fs24 \cf0 angularjs-webapp} 1077 | VerticalPad 1078 | 0 1079 | 1080 | 1081 | 1082 | ID 1083 | 22 1084 | 1085 | 1086 | Bounds 1087 | {{100.67999172210693, 18}, {205, 120}} 1088 | Class 1089 | ShapedGraphic 1090 | ID 1091 | 30 1092 | Magnets 1093 | 1094 | {0, 1} 1095 | {0, -1} 1096 | {1, 0} 1097 | {-1, 0} 1098 | 1099 | Shape 1100 | Cube 1101 | Style 1102 | 1103 | Text 1104 | 1105 | Align 1106 | 0 1107 | Text 1108 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 1109 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1110 | {\colortbl;\red255\green255\blue255;} 1111 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 1112 | 1113 | \f0\b\fs24 \cf0 Jetty, localhost:8080\ 1114 | } 1115 | VerticalPad 1116 | 0 1117 | 1118 | TextPlacement 1119 | 0 1120 | 1121 | 1122 | Class 1123 | Group 1124 | Graphics 1125 | 1126 | 1127 | Bounds 1128 | {{46.359992504119845, 286}, {32.820512820512832, 12}} 1129 | Class 1130 | ShapedGraphic 1131 | ID 1132 | 27 1133 | Shape 1134 | Rectangle 1135 | Style 1136 | 1137 | shadow 1138 | 1139 | Draws 1140 | NO 1141 | 1142 | 1143 | 1144 | 1145 | Bounds 1146 | {{46.359992504119845, 261}, {32.820512820512832, 12}} 1147 | Class 1148 | ShapedGraphic 1149 | ID 1150 | 28 1151 | Shape 1152 | Rectangle 1153 | Style 1154 | 1155 | shadow 1156 | 1157 | Draws 1158 | NO 1159 | 1160 | 1161 | 1162 | 1163 | Bounds 1164 | {{66.052300196427524, 248}, {108.30769230769231, 63}} 1165 | Class 1166 | ShapedGraphic 1167 | ID 1168 | 29 1169 | Magnets 1170 | 1171 | {0, 1} 1172 | {0, -1} 1173 | {1, 0} 1174 | {-1, 0} 1175 | 1176 | Shape 1177 | Rectangle 1178 | Style 1179 | 1180 | Text 1181 | 1182 | Text 1183 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 1184 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1185 | {\colortbl;\red255\green255\blue255;} 1186 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc 1187 | 1188 | \f0\fs24 \cf0 dw-server} 1189 | VerticalPad 1190 | 0 1191 | 1192 | 1193 | 1194 | ID 1195 | 26 1196 | 1197 | 1198 | Bounds 1199 | {{25, 207}, {356.3599853515625, 120}} 1200 | Class 1201 | ShapedGraphic 1202 | ID 1203 | 31 1204 | Magnets 1205 | 1206 | {0, 1} 1207 | {0, -1} 1208 | {1, 0} 1209 | {-1, 0} 1210 | 1211 | Shape 1212 | Cube 1213 | Style 1214 | 1215 | Text 1216 | 1217 | Align 1218 | 0 1219 | Text 1220 | {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf390 1221 | \cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} 1222 | {\colortbl;\red255\green255\blue255;} 1223 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 1224 | 1225 | \f0\b\fs24 \cf0 Jetty, localhost:9000\ 1226 | } 1227 | VerticalPad 1228 | 0 1229 | 1230 | TextPlacement 1231 | 0 1232 | 1233 | 1234 | GridInfo 1235 | 1236 | HPages 1237 | 1 1238 | KeepToScale 1239 | 1240 | Layers 1241 | 1242 | 1243 | Lock 1244 | NO 1245 | Name 1246 | Layer 1 1247 | Print 1248 | YES 1249 | View 1250 | YES 1251 | 1252 | 1253 | LayoutInfo 1254 | 1255 | Animate 1256 | NO 1257 | circoMinDist 1258 | 18 1259 | circoSeparation 1260 | 0.0 1261 | layoutEngine 1262 | dot 1263 | neatoSeparation 1264 | 0.0 1265 | twopiSeparation 1266 | 0.0 1267 | 1268 | Orientation 1269 | 2 1270 | PrintOnePage 1271 | 1272 | RowAlign 1273 | 1 1274 | RowSpacing 1275 | 36 1276 | SheetTitle 1277 | Canvas 1 1278 | UniqueID 1279 | 1 1280 | VPages 1281 | 1 1282 | 1283 | 1284 | SmartAlignmentGuidesActive 1285 | YES 1286 | SmartDistanceGuidesActive 1287 | YES 1288 | UseEntirePage 1289 | 1290 | WindowInfo 1291 | 1292 | CurrentSheet 1293 | 0 1294 | ExpandedCanvases 1295 | 1296 | Frame 1297 | {{567, -0}, {759, 878}} 1298 | ListView 1299 | 1300 | OutlineWidth 1301 | 142 1302 | RightSidebar 1303 | 1304 | ShowRuler 1305 | 1306 | Sidebar 1307 | 1308 | SidebarWidth 1309 | 120 1310 | VisibleRegion 1311 | {{-32, 0}, {624, 739}} 1312 | Zoom 1313 | 1 1314 | ZoomValues 1315 | 1316 | 1317 | Canvas 1 1318 | 1 1319 | 1 1320 | 1321 | 1322 | Canvas 2 1323 | 1 1324 | 1 1325 | 1326 | 1327 | 1328 | 1329 | 1330 | -------------------------------------------------------------------------------- /doc/deployment_diagram.graffle/image1.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perspilling/angularjs-dropwizard-example/7d4e1169757ead3e008627b9455fae6e84eab292/doc/deployment_diagram.graffle/image1.ai -------------------------------------------------------------------------------- /doc/deployment_diagram.graffle/image2.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perspilling/angularjs-dropwizard-example/7d4e1169757ead3e008627b9455fae6e84eab292/doc/deployment_diagram.graffle/image2.ai -------------------------------------------------------------------------------- /doc/deployment_diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perspilling/angularjs-dropwizard-example/7d4e1169757ead3e008627b9455fae6e84eab292/doc/deployment_diagram.jpg -------------------------------------------------------------------------------- /dw-server/README: -------------------------------------------------------------------------------- 1 | This is basically the example from: http://dropwizard.codahale.com/getting-started/ 2 | 3 | Build it: 4 | 5 | > mvn package 6 | 7 | To start the Dropwizard Restful web services: 8 | 9 | > sh ./start_server.sh 10 | -------------------------------------------------------------------------------- /dw-server/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { 4 | name 'Gradle Shadow' 5 | url 'http://dl.bintray.com/content/johnrengelman/gradle-plugins' 6 | } 7 | } 8 | dependencies { 9 | classpath 'org.gradle.plugins:shadow:0.7.4' 10 | } 11 | } 12 | apply plugin: 'shadow' 13 | 14 | // Configure shadow (port of maven shade to gradle) 15 | shadow { 16 | exclude 'META-INF/*.DSA' 17 | exclude 'META-INF/*.RSA' 18 | exclude 'META-INF/*.SF' 19 | } 20 | build.dependsOn 'shadow' // wire shadow task into build cycle 21 | 22 | dependencies { 23 | compile libs.dropwizard_core, libs.dropwizard_jdbi, libs.dropwizard_db, libs.h2 24 | testCompile libs.jersey_client 25 | } 26 | 27 | jar { 28 | manifest { 29 | attributes 'Main-Class': 'no.kodemaker.ps.dw.eventservice.EventService' 30 | } 31 | } 32 | 33 | task runDwServer(type: JavaExec) { 34 | dependsOn 'shadow' 35 | description = "Run the server (on port 9000)" 36 | main = '-jar' 37 | args = [project.shadow.shadowJar, 'server', file('dw-server.yml')] 38 | } 39 | -------------------------------------------------------------------------------- /dw-server/dw-server.yml: -------------------------------------------------------------------------------- 1 | # 2 | # For defaults, see: http://dropwizard.codahale.com/manual/core/#configuration-defaults 3 | # 4 | template: Hello, %s! 5 | defaultName: Stranger 6 | 7 | 8 | # Avoid using the default 8080 port for RESTFul services 9 | http: 10 | port: 9000 -------------------------------------------------------------------------------- /dw-server/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | no.kodemaker.ps 7 | angularjs-dropwizard-example 8 | 1.0-SNAPSHOT 9 | 10 | 11 | dw-server 12 | 1.0-SNAPSHOT 13 | jar 14 | 15 | dw-server 16 | 17 | 18 | UTF-8 19 | UTF-8 20 | 21 | 22 | 23 | 24 | com.yammer.dropwizard 25 | dropwizard-core 26 | 27 | 28 | com.yammer.dropwizard 29 | dropwizard-jdbi 30 | 31 | 32 | com.yammer.dropwizard 33 | dropwizard-db 34 | 35 | 36 | com.h2database 37 | h2 38 | 1.3.173 39 | 40 | 41 | 42 | com.sun.jersey 43 | jersey-client 44 | 1.12 45 | test 46 | 47 | 48 | 49 | junit 50 | junit 51 | test 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-shade-plugin 61 | 1.4 62 | 63 | true 64 | 65 | 66 | *:* 67 | 68 | META-INF/*.SF 69 | META-INF/*.DSA 70 | META-INF/*.RSA 71 | 72 | 73 | 74 | 75 | 76 | 77 | package 78 | 79 | shade 80 | 81 | 82 | 83 | 85 | 87 | no.kodemaker.ps.dw.eventservice.EventService 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /dw-server/src/main/java/no/kodemaker/ps/dw/eventservice/EventConfiguration.java: -------------------------------------------------------------------------------- 1 | package no.kodemaker.ps.dw.eventservice; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.yammer.dropwizard.config.Configuration; 5 | import org.hibernate.validator.constraints.NotEmpty; 6 | 7 | public class EventConfiguration extends Configuration { 8 | @NotEmpty 9 | @JsonProperty 10 | private String template; 11 | 12 | /* 13 | @Valid 14 | @NotNull 15 | @JsonProperty 16 | private DatabaseConfiguration databaseConfiguration = new DatabaseConfiguration(); 17 | */ 18 | 19 | @NotEmpty 20 | @JsonProperty 21 | private String defaultName = "Stranger"; 22 | 23 | public String getTemplate() { 24 | return template; 25 | } 26 | 27 | public String getDefaultName() { 28 | return defaultName; 29 | } 30 | 31 | /* 32 | public DatabaseConfiguration getDatabaseConfiguration() { 33 | return databaseConfiguration; 34 | } 35 | */ 36 | } -------------------------------------------------------------------------------- /dw-server/src/main/java/no/kodemaker/ps/dw/eventservice/EventService.java: -------------------------------------------------------------------------------- 1 | package no.kodemaker.ps.dw.eventservice; 2 | 3 | import com.yammer.dropwizard.Service; 4 | import com.yammer.dropwizard.config.Bootstrap; 5 | import com.yammer.dropwizard.config.Environment; 6 | import no.kodemaker.ps.dw.eventservice.health.TemplateHealthCheck; 7 | import no.kodemaker.ps.dw.eventservice.persistence.PersonDao; 8 | import no.kodemaker.ps.dw.eventservice.representations.Person; 9 | import no.kodemaker.ps.dw.eventservice.resources.HelloWorldResource; 10 | import no.kodemaker.ps.dw.eventservice.resources.PersonsResource; 11 | import org.h2.jdbcx.JdbcConnectionPool; 12 | import org.skife.jdbi.v2.DBI; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collections; 16 | import java.util.List; 17 | 18 | /** 19 | * This main-class will be used by the start_server.sh script to start the server. It can also be 20 | * started up in the IDE, just remember to set the correct working directory and provide the expected 21 | * parameters: server dw-server.yml 22 | */ 23 | public class EventService extends Service { 24 | 25 | private static List persons; 26 | 27 | static { 28 | persons = Collections.synchronizedList(new ArrayList()); 29 | persons.add(new Person("Per", "per@kodemaker.no", "12345678")); 30 | persons.add(new Person("Magnus", "magnus@kodemaker.no")); 31 | persons.add(new Person("Ronny", "ronny@kodemaker.no")); 32 | persons.add(new Person("August", "august@kodemaker.no")); 33 | persons.add(new Person("Helge", "helge.jensen@finn.no")); 34 | } 35 | 36 | public static void main(String[] args) throws Exception { 37 | new EventService().run(args); 38 | } 39 | 40 | @Override 41 | public void initialize(Bootstrap bootstrap) { 42 | bootstrap.setName("dw-server"); // name must match the yaml config file 43 | } 44 | 45 | @Override 46 | public void run(EventConfiguration conf, Environment env) throws ClassNotFoundException { 47 | String template = conf.getTemplate(); 48 | String defaultName = conf.getDefaultName(); 49 | 50 | //DBIFactory factory = new DBIFactory(); 51 | //final DBI jdbi = factory.build(env, conf.getDatabaseConfiguration(), "postgresql"); 52 | // using in-memory data base here for simplicity 53 | JdbcConnectionPool jdbcConnectionPool = JdbcConnectionPool.create("jdbc:h2:mem:test", "username", "password"); 54 | DBI jdbi = new DBI(jdbcConnectionPool); 55 | PersonDao personDao = jdbi.onDemand(PersonDao.class); 56 | personDao.createPersonTable(); 57 | seedTheDatabase(personDao); 58 | 59 | env.addResource(new PersonsResource(personDao)); 60 | env.addResource(new HelloWorldResource(template, defaultName)); 61 | env.addHealthCheck(new TemplateHealthCheck(template)); 62 | } 63 | 64 | private void seedTheDatabase(PersonDao personDao) { 65 | for (Person p : persons) { 66 | personDao.insert(p); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /dw-server/src/main/java/no/kodemaker/ps/dw/eventservice/health/TemplateHealthCheck.java: -------------------------------------------------------------------------------- 1 | package no.kodemaker.ps.dw.eventservice.health; 2 | 3 | import com.yammer.metrics.core.HealthCheck; 4 | 5 | public class TemplateHealthCheck extends HealthCheck { 6 | private final String template; 7 | 8 | public TemplateHealthCheck(String template) { 9 | super("template"); 10 | this.template = template; 11 | } 12 | 13 | @Override 14 | protected Result check() throws Exception { 15 | // the following template string is expected: Hello, %s! 16 | final String saying = String.format(template, "TEST"); 17 | if (!saying.contains("TEST")) { 18 | return Result.unhealthy("template doesn't include a name"); 19 | } 20 | return Result.healthy(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /dw-server/src/main/java/no/kodemaker/ps/dw/eventservice/persistence/PersonDao.java: -------------------------------------------------------------------------------- 1 | package no.kodemaker.ps.dw.eventservice.persistence; 2 | 3 | import no.kodemaker.ps.dw.eventservice.representations.Person; 4 | import org.skife.jdbi.v2.sqlobject.Bind; 5 | import org.skife.jdbi.v2.sqlobject.BindBean; 6 | import org.skife.jdbi.v2.sqlobject.SqlQuery; 7 | import org.skife.jdbi.v2.sqlobject.SqlUpdate; 8 | import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * @author Per Spilling 14 | */ 15 | @RegisterMapper(PersonMapper.class) 16 | public interface PersonDao { 17 | @SqlUpdate("create table PERSON (id int auto_increment primary key, name varchar(80), email varchar(80), phone varchar(20))") 18 | void createPersonTable(); 19 | 20 | @SqlUpdate("insert into PERSON (name, email, phone) values (:name, :email, :phone)") 21 | void insert(@BindBean Person person); 22 | 23 | @SqlUpdate("update PERSON set name = :p.name, email = :p.email, phone = :p.phone where id = :p.id") 24 | void update(@BindBean("p") Person person); 25 | 26 | @SqlQuery("select * from PERSON where id = :id") 27 | Person findById(@Bind("id") int id); 28 | 29 | @SqlQuery("select * from PERSON") 30 | List getAll(); 31 | 32 | @SqlUpdate("delete from PERSON where id = :it") 33 | void deleteById(@Bind int id); 34 | 35 | @SqlUpdate("delete from PERSON where email = :it") 36 | void deleteByEmail(@Bind String email); 37 | } 38 | -------------------------------------------------------------------------------- /dw-server/src/main/java/no/kodemaker/ps/dw/eventservice/persistence/PersonMapper.java: -------------------------------------------------------------------------------- 1 | package no.kodemaker.ps.dw.eventservice.persistence; 2 | 3 | import no.kodemaker.ps.dw.eventservice.representations.Person; 4 | import org.skife.jdbi.v2.StatementContext; 5 | import org.skife.jdbi.v2.tweak.ResultSetMapper; 6 | 7 | import java.sql.ResultSet; 8 | import java.sql.SQLException; 9 | 10 | /** 11 | * @author Per Spilling 12 | */ 13 | public class PersonMapper implements ResultSetMapper { 14 | @Override 15 | public Person map(int i, ResultSet rs, StatementContext statementContext) throws SQLException { 16 | return new Person(rs.getInt("id"), rs.getString("name"), rs.getString("email"), rs.getString("phone")); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /dw-server/src/main/java/no/kodemaker/ps/dw/eventservice/representations/Person.java: -------------------------------------------------------------------------------- 1 | package no.kodemaker.ps.dw.eventservice.representations; 2 | 3 | /** 4 | * @author Per Spilling 5 | */ 6 | public class Person { 7 | private Integer id; // PK 8 | private String name; 9 | private String email; 10 | private String phone; 11 | 12 | public Person() { 13 | } 14 | 15 | public Person(String name, String email) { 16 | this.name = name; 17 | this.email = email; 18 | } 19 | 20 | public Person(String name, String email, String phone) { 21 | this.name = name; 22 | this.email = email; 23 | this.phone = phone; 24 | } 25 | 26 | public Person(Integer id, String name, String email, String phone) { 27 | this.id = id; 28 | this.name = name; 29 | this.email = email; 30 | this.phone = phone; 31 | } 32 | 33 | public Integer getId() { 34 | return id; 35 | } 36 | 37 | public String getName() { 38 | return name; 39 | } 40 | 41 | public void setName(String name) { 42 | this.name = name; 43 | } 44 | 45 | public String getEmail() { 46 | return email; 47 | } 48 | 49 | public void setEmail(String email) { 50 | this.email = email; 51 | } 52 | 53 | public String getPhone() { 54 | return phone; 55 | } 56 | 57 | public void setPhone(String phone) { 58 | this.phone = phone; 59 | } 60 | 61 | @Override 62 | public boolean equals(Object o) { 63 | if (this == o) return true; 64 | if (o == null || getClass() != o.getClass()) return false; 65 | 66 | Person person = (Person) o; 67 | 68 | if (email != null ? !email.equals(person.email) : person.email != null) return false; 69 | if (id != null ? !id.equals(person.id) : person.id != null) return false; 70 | if (name != null ? !name.equals(person.name) : person.name != null) return false; 71 | if (phone != null ? !phone.equals(person.phone) : person.phone != null) return false; 72 | 73 | return true; 74 | } 75 | 76 | @Override 77 | public int hashCode() { 78 | int result = id != null ? id.hashCode() : 0; 79 | result = 31 * result + (name != null ? name.hashCode() : 0); 80 | result = 31 * result + (email != null ? email.hashCode() : 0); 81 | result = 31 * result + (phone != null ? phone.hashCode() : 0); 82 | return result; 83 | } 84 | 85 | public boolean isValid() { 86 | return name != null && email != null; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /dw-server/src/main/java/no/kodemaker/ps/dw/eventservice/representations/Saying.java: -------------------------------------------------------------------------------- 1 | package no.kodemaker.ps.dw.eventservice.representations; 2 | 3 | /** 4 | * Used for responding to hello requests. 5 | */ 6 | public class Saying { 7 | private final long id; 8 | private final String content; 9 | 10 | public Saying(long id, String content) { 11 | this.id = id; 12 | this.content = content; 13 | } 14 | 15 | public long getId() { 16 | return id; 17 | } 18 | 19 | public String getContent() { 20 | return content; 21 | } 22 | } -------------------------------------------------------------------------------- /dw-server/src/main/java/no/kodemaker/ps/dw/eventservice/resources/HelloWorldResource.java: -------------------------------------------------------------------------------- 1 | package no.kodemaker.ps.dw.eventservice.resources; 2 | 3 | import com.google.common.base.Optional; 4 | import com.yammer.metrics.annotation.Timed; 5 | import no.kodemaker.ps.dw.eventservice.representations.Saying; 6 | 7 | import javax.ws.rs.GET; 8 | import javax.ws.rs.Path; 9 | import javax.ws.rs.Produces; 10 | import javax.ws.rs.QueryParam; 11 | import javax.ws.rs.core.MediaType; 12 | import java.util.concurrent.atomic.AtomicLong; 13 | 14 | @Path("/hello-world") 15 | @Produces(MediaType.APPLICATION_JSON) 16 | public class HelloWorldResource { 17 | private final String template; 18 | private final String defaultName; 19 | private final AtomicLong counter; 20 | 21 | public HelloWorldResource(String template, String defaultName) { 22 | this.template = template; 23 | this.defaultName = defaultName; 24 | this.counter = new AtomicLong(); 25 | } 26 | 27 | @GET 28 | @Timed 29 | public Saying sayHello(@QueryParam("name") Optional name) { 30 | return new Saying(counter.incrementAndGet(), 31 | String.format(template, name.or(defaultName))); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dw-server/src/main/java/no/kodemaker/ps/dw/eventservice/resources/PersonsResource.java: -------------------------------------------------------------------------------- 1 | package no.kodemaker.ps.dw.eventservice.resources; 2 | 3 | import com.yammer.metrics.annotation.Timed; 4 | import no.kodemaker.ps.dw.eventservice.persistence.PersonDao; 5 | import no.kodemaker.ps.dw.eventservice.representations.Person; 6 | 7 | import javax.ws.rs.*; 8 | import javax.ws.rs.core.MediaType; 9 | import javax.ws.rs.core.Response; 10 | import java.util.List; 11 | 12 | /** 13 | * The resource used to handle Person requests. 14 | * 15 | * @author Per Spilling 16 | */ 17 | @Path("/persons") 18 | @Produces(MediaType.APPLICATION_JSON) 19 | @Consumes(MediaType.APPLICATION_JSON) 20 | public class PersonsResource { 21 | private PersonDao personDao; 22 | 23 | public PersonsResource(PersonDao dao) { 24 | personDao = dao; 25 | } 26 | 27 | @GET 28 | @Path("/{id}") 29 | @Timed 30 | public Person getPerson(@PathParam("id") Integer id) { 31 | Person p = personDao.findById(id); 32 | if (p != null) { 33 | return p; 34 | } else { 35 | throw new WebApplicationException(Response.Status.NOT_FOUND); 36 | } 37 | } 38 | 39 | @GET 40 | @Timed 41 | public List listPersons() { 42 | return personDao.getAll(); 43 | } 44 | 45 | @POST 46 | @Timed 47 | public void save(Person person) { 48 | if (person != null && person.isValid()) { 49 | if (person.getId() != null) { 50 | personDao.update(person); 51 | } else { 52 | personDao.insert(person); 53 | } 54 | } 55 | } 56 | 57 | @DELETE 58 | @Path("/{id}") 59 | @Timed 60 | @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN}) 61 | public void deletePerson(@PathParam("id") Integer id) { 62 | /** 63 | * Note: AngularJS $resource will send a DELETE request as content-type test/plain for some reason; 64 | * so therefore we must add MediaType.TEXT_PLAIN here. 65 | */ 66 | if (personDao.findById(id) != null) { 67 | personDao.deleteById(id); 68 | } else { 69 | throw new WebApplicationException(Response.Status.NOT_FOUND); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /dw-server/src/test/java/no/kodemaker/example/dw/hello/client/HelloWorldClient.java: -------------------------------------------------------------------------------- 1 | package no.kodemaker.example.dw.hello.client; 2 | 3 | import com.sun.jersey.api.client.Client; 4 | import com.sun.jersey.api.client.WebResource; 5 | 6 | import java.text.DecimalFormat; 7 | 8 | public class HelloWorldClient { 9 | static public void main(String[] args) { 10 | Client client = Client.create(); 11 | WebResource resource = client.resource("http://eagle.local:8080/hello-world"); 12 | int num_of_requests = 10000; 13 | long startTime = System.currentTimeMillis(); 14 | for (int i = 0; i < num_of_requests; i++) { 15 | String result = resource.queryParam("name", "Per").get(String.class); 16 | System.out.println("result=" + result); 17 | } 18 | long endTime = System.currentTimeMillis(); 19 | float duration = endTime - startTime; 20 | String seconds = new DecimalFormat("#.####").format(duration/1000); 21 | System.out.println("Duration = " + seconds.toString()); 22 | System.out.println("Request/sec = " + Float.toString((num_of_requests/duration)*1000)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /dw-server/start_server.sh: -------------------------------------------------------------------------------- 1 | java -jar target/dw-server-1.0-SNAPSHOT.jar server dw-server.yml -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perspilling/angularjs-dropwizard-example/7d4e1169757ead3e008627b9455fae6e84eab292/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Aug 24 17:47:02 CEST 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.7-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | no.kodemaker.ps 6 | angularjs-dropwizard-example 7 | 1.0-SNAPSHOT 8 | pom 9 | 10 | angularjs-dropwizard-example 11 | 12 | 13 | Kodemaker Systemutvikling AS 14 | http://www.kodemaker.no/ 15 | 16 | 17 | 18 | dw-server 19 | angularjs-webapp 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | UTF-8 28 | 29 | yyyy-MM-dd HH:mm 30 | 1.7 31 | 1.7 32 | 0.6.2 33 | 9.1-901.jdbc4 34 | 35 | 40 | 9.4.41.v20210516 41 | 42 | 43 | 2.2.3 44 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 57 | 58 | com.yammer.dropwizard 59 | dropwizard-core 60 | ${dw.version} 61 | 62 | 63 | 64 | 65 | com.yammer.dropwizard 66 | dropwizard-jdbi 67 | ${dw.version} 68 | 69 | 70 | com.yammer.dropwizard 71 | dropwizard-db 72 | ${dw.version} 73 | 74 | 75 | 76 | 81 | 82 | org.eclipse.jetty 83 | jetty-webapp 84 | ${jetty.version} 85 | 86 | 87 | 88 | 89 | org.eclipse.jetty 90 | jetty-servlets 91 | ${jetty.version} 92 | 93 | 94 | 95 | 96 | org.glassfish.web 97 | javax.servlet.jsp 98 | ${glassfish.javax.version} 99 | 100 | 101 | 102 | 103 | postgresql 104 | postgresql 105 | ${postgresql.version} 106 | 107 | 108 | 109 | 110 | com.google.guava 111 | guava 112 | 13.0.1 113 | 114 | 115 | 116 | 117 | junit 118 | junit 119 | 4.13.1 120 | test 121 | 122 | 123 | 124 | org.mockito 125 | mockito-all 126 | 1.9.0 127 | test 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | false 142 | src/main/java 143 | 144 | ** 145 | 146 | 147 | **/*.java 148 | 149 | 150 | 151 | false 152 | src/main/resources 153 | 154 | ** 155 | 156 | 157 | 158 | 159 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | false 173 | src/test/resources 174 | 175 | 176 | false 177 | src/test/java 178 | 179 | ** 180 | 181 | 182 | **/*.java 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | org.apache.maven.plugins 191 | maven-compiler-plugin 192 | 2.3.2 193 | 194 | ${targetJdk} 195 | ${targetJdk} 196 | UTF-8 197 | 198 | 199 | 200 | 201 | org.apache.maven.plugins 202 | maven-deploy-plugin 203 | 2.7 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 228 | 229 | 230 | 231 | 232 | 238 | 239 | 255 | 256 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':dw-server', ':angularjs-webapp' 2 | 3 | --------------------------------------------------------------------------------