diff --git a/README.md b/README.md
index 6eab094..1e1ec27 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,8 @@
-angular-rest-springsecurity
-===========================
+angular-rest-springsecurity with Restangular
+============================================
-[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=shoxrocks&url=http://sorst.net/github/angular-rest-springsecurity&title=AngularJS REST Spring Security Example&language=&tags=github&category=software)
-
-[![Donate](http://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=W9NAXW8YAZ4D6&item_name=Angular REST SpringSecurity Example Donation¤cy_code=EUR)
+This is a fork from https://github.com/philipsorst/angular-rest-springsecurity. I needed this exact functionality but with Restangular
+For this fork to work you need to download Restangular and underscore.
An example AngularJS Application that uses a Spring Security protected Jersey REST backend based on Hibernate/JPA.
@@ -25,7 +24,8 @@ Any feedback is welcome, and I will incorporate useful pull requests.
Technologies
------------
-* [AngularJS](http://angularjs.org/)
+* [AngularJS](http://angularjs.org/)
+* [Restangular](https://github.com/mgonto/restangular)
* [Bootstrap](http://getbootstrap.com/)
* [Jersey](https://jersey.java.net/)
* [Spring Security](http://projects.spring.io/spring-security/)
diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html
index 355d150..e806049 100644
--- a/src/main/webapp/index.html
+++ b/src/main/webapp/index.html
@@ -58,6 +58,8 @@
+
+
diff --git a/src/main/webapp/js/app.js b/src/main/webapp/js/app.js
index e6b4814..d176540 100644
--- a/src/main/webapp/js/app.js
+++ b/src/main/webapp/js/app.js
@@ -1,6 +1,6 @@
-angular.module('exampleApp', ['ngRoute', 'ngCookies', 'exampleApp.services'])
+angular.module('exampleApp', ['ngRoute', 'ngCookies', 'exampleApp.services','restangular'])
.config(
- [ '$routeProvider', '$locationProvider', '$httpProvider', function($routeProvider, $locationProvider, $httpProvider) {
+ [ '$routeProvider', '$locationProvider', '$httpProvider','RestangularProvider', function($routeProvider, $locationProvider, $httpProvider, RestangularProvider) {
$routeProvider.when('/create', {
templateUrl: 'partials/create.html',
@@ -63,8 +63,10 @@ angular.module('exampleApp', ['ngRoute', 'ngCookies', 'exampleApp.services'])
return config || $q.when(config);
}
};
- }
- );
+ });
+
+
+ RestangularProvider.setBaseUrl('rest')
} ]
@@ -101,9 +103,9 @@ angular.module('exampleApp', ['ngRoute', 'ngCookies', 'exampleApp.services'])
var authToken = $cookieStore.get('authToken');
if (authToken !== undefined) {
$rootScope.authToken = authToken;
- UserService.get(function(user) {
+ UserService.get("").then(function(user) {
$rootScope.user = user;
- $location.path(originalPath);
+ $location.path("/");
});
}
@@ -113,11 +115,12 @@ angular.module('exampleApp', ['ngRoute', 'ngCookies', 'exampleApp.services'])
function IndexController($scope, NewsService) {
- $scope.newsEntries = NewsService.query();
-
+ NewsService.getList().then(function(entries){
+ $scope.newsEntries = entries;
+ });
$scope.deleteEntry = function(newsEntry) {
- newsEntry.$remove(function() {
- $scope.newsEntries = NewsService.query();
+ newsEntry.remove().then(function() {
+ $scope.newsEntries = NewsService.getList().$object
});
};
};
@@ -125,10 +128,12 @@ function IndexController($scope, NewsService) {
function EditController($scope, $routeParams, $location, NewsService) {
- $scope.newsEntry = NewsService.get({id: $routeParams.id});
-
+ NewsService.get($routeParams.id).then(function(enrty){
+ $scope.newsEntry = enrty;
+ });
+
$scope.save = function() {
- $scope.newsEntry.$save(function() {
+ $scope.newsEntry.post().then(function() {
$location.path('/');
});
};
@@ -137,10 +142,9 @@ function EditController($scope, $routeParams, $location, NewsService) {
function CreateController($scope, $location, NewsService) {
- $scope.newsEntry = new NewsService();
-
+
$scope.save = function() {
- $scope.newsEntry.$save(function() {
+ NewsService.post($scope.newsEntry).then(function() {
$location.path('/');
});
};
@@ -150,39 +154,34 @@ function CreateController($scope, $location, NewsService) {
function LoginController($scope, $rootScope, $location, $cookieStore, UserService) {
$scope.rememberMe = false;
-
+
$scope.login = function() {
- UserService.authenticate($.param({username: $scope.username, password: $scope.password}), function(authenticationResult) {
+
+ UserService.customPOST($.param({username: $scope.username, password: $scope.password}),"authenticate",{},{'Content-Type': 'application/x-www-form-urlencoded'}).then(function(authenticationResult) {
var authToken = authenticationResult.token;
$rootScope.authToken = authToken;
+
if ($scope.rememberMe) {
$cookieStore.put('authToken', authToken);
}
- UserService.get(function(user) {
+ UserService.get("").then(function(user) {
$rootScope.user = user;
$location.path("/");
});
+
});
};
};
-var services = angular.module('exampleApp.services', ['ngResource']);
+var services = angular.module('exampleApp.services', []);
-services.factory('UserService', function($resource) {
+services.factory('UserService', function(Restangular) {
- return $resource('rest/user/:action', {},
- {
- authenticate: {
- method: 'POST',
- params: {'action' : 'authenticate'},
- headers : {'Content-Type': 'application/x-www-form-urlencoded'}
- },
- }
- );
+ return Restangular.all('user');
});
-services.factory('NewsService', function($resource) {
+services.factory('NewsService', function(Restangular) {
- return $resource('rest/news/:id', {id: '@id'});
+ return Restangular.all('news');
});
\ No newline at end of file
diff --git a/src/main/webapp/js/restangular.js b/src/main/webapp/js/restangular.js
new file mode 100644
index 0000000..47e5339
--- /dev/null
+++ b/src/main/webapp/js/restangular.js
@@ -0,0 +1,1305 @@
+/**
+ * Restful Resources service for AngularJS apps
+ * @version v1.4.0 - 2014-04-25 * @link https://github.com/mgonto/restangular
+ * @author Martin Gontovnikas
+ * @license MIT License, http://www.opensource.org/licenses/MIT
+ */(function() {
+
+var module = angular.module('restangular', []);
+
+module.provider('Restangular', function() {
+ // Configuration
+ var Configurer = {};
+ Configurer.init = function(object, config) {
+ /**
+ * Those are HTTP safe methods for which there is no need to pass any data with the request.
+ */
+
+ object.configuration = config;
+
+ var safeMethods= ["get", "head", "options", "trace", "getlist"];
+ config.isSafe = function(operation) {
+ return _.contains(safeMethods, operation.toLowerCase());
+ };
+
+ var absolutePattern = /^https?:\/\//i;
+ config.isAbsoluteUrl = function(string) {
+ return _.isUndefined(config.absoluteUrl) || _.isNull(config.absoluteUrl) ?
+ string && absolutePattern.test(string) :
+ config.absoluteUrl;
+ };
+
+ config.absoluteUrl = _.isUndefined(config.absoluteUrl) ? true : config.absoluteUrl;
+ object.setSelfLinkAbsoluteUrl = function(value) {
+ config.absoluteUrl = value;
+ };
+ /**
+ * This is the BaseURL to be used with Restangular
+ */
+ config.baseUrl = _.isUndefined(config.baseUrl) ? "" : config.baseUrl;
+ object.setBaseUrl = function(newBaseUrl) {
+ config.baseUrl = /\/$/.test(newBaseUrl)
+ ? newBaseUrl.substring(0, newBaseUrl.length-1)
+ : newBaseUrl;
+ return this;
+ };
+
+ /**
+ * Sets the extra fields to keep from the parents
+ */
+ config.extraFields = config.extraFields || [];
+ object.setExtraFields = function(newExtraFields) {
+ config.extraFields = newExtraFields;
+ return this;
+ };
+
+ /**
+ * Some default $http parameter to be used in EVERY call
+ **/
+ config.defaultHttpFields = config.defaultHttpFields || {};
+ object.setDefaultHttpFields = function(values) {
+ config.defaultHttpFields = values;
+ return this;
+ };
+
+ config.withHttpValues = function(httpLocalConfig, obj) {
+ return _.defaults(obj, httpLocalConfig, config.defaultHttpFields);
+ };
+
+ config.encodeIds = _.isUndefined(config.encodeIds) ? true : config.encodeIds;
+ object.setEncodeIds = function(encode) {
+ config.encodeIds = encode;
+ };
+
+ config.defaultRequestParams = config.defaultRequestParams || {
+ get: {},
+ post: {},
+ put: {},
+ remove: {},
+ common: {}
+ };
+
+ object.setDefaultRequestParams = function(param1, param2) {
+ var methods = [],
+ params = param2 || param1;
+ if (!_.isUndefined(param2)) {
+ if (_.isArray(param1)) {
+ methods = param1;
+ } else {
+ methods.push(param1);
+ }
+ } else {
+ methods.push('common');
+ }
+
+ _.each(methods, function (method) {
+ config.defaultRequestParams[method] = params;
+ });
+ return this;
+ };
+
+ object.requestParams = config.defaultRequestParams;
+
+
+ config.defaultHeaders = config.defaultHeaders || {};
+ object.setDefaultHeaders = function(headers) {
+ config.defaultHeaders = headers;
+ object.defaultHeaders = config.defaultHeaders;
+ return this;
+ };
+
+ object.defaultHeaders = config.defaultHeaders;
+
+ /**
+ * Method overriders will set which methods are sent via POST with an X-HTTP-Method-Override
+ **/
+ config.methodOverriders = config.methodOverriders || [];
+ object.setMethodOverriders = function(values) {
+ var overriders = _.extend([], values);
+ if (config.isOverridenMethod('delete', overriders)) {
+ overriders.push("remove");
+ }
+ config.methodOverriders = overriders;
+ return this;
+ };
+
+ config.jsonp = _.isUndefined(config.jsonp) ? false : config.jsonp;
+ object.setJsonp = function(active) {
+ config.jsonp = active;
+ };
+
+ config.isOverridenMethod = function(method, values) {
+ var search = values || config.methodOverriders;
+ return !_.isUndefined(_.find(search, function(one) {
+ return one.toLowerCase() === method.toLowerCase();
+ }));
+ };
+
+ /**
+ * Sets the URL creator type. For now, only Path is created. In the future we'll have queryParams
+ **/
+ config.urlCreator = config.urlCreator || "path";
+ object.setUrlCreator = function(name) {
+ if (!_.has(config.urlCreatorFactory, name)) {
+ throw new Error("URL Path selected isn't valid");
+ }
+
+ config.urlCreator = name;
+ return this;
+ };
+
+ /**
+ * You can set the restangular fields here. The 3 required fields for Restangular are:
+ *
+ * id: Id of the element
+ * route: name of the route of this element
+ * parentResource: the reference to the parent resource
+ *
+ * All of this fields except for id, are handled (and created) by Restangular. By default,
+ * the field values will be id, route and parentResource respectively
+ */
+ config.restangularFields = config.restangularFields || {
+ id: "id",
+ route: "route",
+ parentResource: "parentResource",
+ restangularCollection: "restangularCollection",
+ cannonicalId: "__cannonicalId",
+ etag: "restangularEtag",
+ selfLink: "href",
+ get: "get",
+ getList: "getList",
+ put: "put",
+ post: "post",
+ remove: "remove",
+ head: "head",
+ trace: "trace",
+ options: "options",
+ patch: "patch",
+ getRestangularUrl: "getRestangularUrl",
+ getRequestedUrl: "getRequestedUrl",
+ putElement: "putElement",
+ addRestangularMethod: "addRestangularMethod",
+ getParentList: "getParentList",
+ clone: "clone",
+ ids: "ids",
+ httpConfig: '_$httpConfig',
+ reqParams: 'reqParams',
+ one: 'one',
+ all: 'all',
+ several: 'several',
+ oneUrl: 'oneUrl',
+ allUrl: 'allUrl',
+ customPUT: 'customPUT',
+ customPOST: 'customPOST',
+ customDELETE: 'customDELETE',
+ customGET: 'customGET',
+ customGETLIST: 'customGETLIST',
+ customOperation: 'customOperation',
+ doPUT: 'doPUT',
+ doPOST: 'doPOST',
+ doDELETE: 'doDELETE',
+ doGET: 'doGET',
+ doGETLIST: 'doGETLIST',
+ fromServer: 'fromServer',
+ withConfig: 'withConfig',
+ withHttpConfig: 'withHttpConfig',
+ singleOne: 'singleOne',
+ plain: 'plain',
+ save: 'save'
+ };
+ object.setRestangularFields = function(resFields) {
+ config.restangularFields =
+ _.extend(config.restangularFields, resFields);
+ return this;
+ };
+
+ config.isRestangularized = function(obj) {
+ return !!obj[config.restangularFields.one] || !!obj[config.restangularFields.all];
+ };
+
+ config.setFieldToElem = function(field, elem, value) {
+ var properties = field.split('.');
+ var idValue = elem;
+ _.each(_.initial(properties), function(prop) {
+ idValue[prop] = {};
+ idValue = idValue[prop];
+ });
+ idValue[_.last(properties)] = value;
+ return this;
+ };
+
+ config.getFieldFromElem = function(field, elem) {
+ var properties = field.split('.');
+ var idValue = elem;
+ _.each(properties, function(prop) {
+ if (idValue) {
+ idValue = idValue[prop];
+ }
+ });
+ return angular.copy(idValue);
+ };
+
+ config.setIdToElem = function(elem, id) {
+ config.setFieldToElem(config.restangularFields.id, elem, id);
+ return this;
+ };
+
+ config.getIdFromElem = function(elem) {
+ return config.getFieldFromElem(config.restangularFields.id, elem);
+ };
+
+ config.isValidId = function(elemId) {
+ return "" !== elemId && !_.isUndefined(elemId) && !_.isNull(elemId);
+ };
+
+ config.setUrlToElem = function(elem, url, route) {
+ config.setFieldToElem(config.restangularFields.selfLink, elem, url);
+ return this;
+ };
+
+ config.getUrlFromElem = function(elem) {
+ return config.getFieldFromElem(config.restangularFields.selfLink, elem);
+ };
+
+ config.useCannonicalId = _.isUndefined(config.useCannonicalId) ? false : config.useCannonicalId;
+ object.setUseCannonicalId = function(value) {
+ config.useCannonicalId = value;
+ return this;
+ };
+
+ config.getCannonicalIdFromElem = function(elem) {
+ var cannonicalId = elem[config.restangularFields.cannonicalId];
+ var actualId = config.isValidId(cannonicalId) ?
+ cannonicalId : config.getIdFromElem(elem);
+ return actualId;
+ };
+
+ /**
+ * Sets the Response parser. This is used in case your response isn't directly the data.
+ * For example if you have a response like {meta: {'meta'}, data: {name: 'Gonto'}}
+ * you can extract this data which is the one that needs wrapping
+ *
+ * The ResponseExtractor is a function that receives the response and the method executed.
+ */
+
+ config.responseInterceptors = config.responseInterceptors || [];
+
+ config.defaultResponseInterceptor = function(data, operation,
+ what, url, response, deferred) {
+ return data;
+ };
+
+ config.responseExtractor = function(data, operation,
+ what, url, response, deferred) {
+ var interceptors = angular.copy(config.responseInterceptors);
+ interceptors.push(config.defaultResponseInterceptor);
+ var theData = data;
+ _.each(interceptors, function(interceptor) {
+ theData = interceptor(theData, operation,
+ what, url, response, deferred);
+ });
+ return theData;
+ };
+
+ object.addResponseInterceptor = function(extractor) {
+ config.responseInterceptors.push(extractor);
+ return this;
+ };
+
+ object.setResponseInterceptor = object.addResponseInterceptor;
+ object.setResponseExtractor = object.addResponseInterceptor;
+
+ /**
+ * Response interceptor is called just before resolving promises.
+ */
+
+
+ /**
+ * Request interceptor is called before sending an object to the server.
+ */
+ config.requestInterceptors = config.requestInterceptors || [];
+
+ config.defaultInterceptor = function(element, operation,
+ path, url, headers, params, httpConfig) {
+ return {
+ element: element,
+ headers: headers,
+ params: params,
+ httpConfig: httpConfig
+ };
+ };
+
+ config.fullRequestInterceptor = function(element, operation,
+ path, url, headers, params, httpConfig) {
+ var interceptors = angular.copy(config.requestInterceptors);
+ var defaultRequest = config.defaultInterceptor(element, operation, path, url, headers, params, httpConfig);
+ return _.reduce(interceptors, function(request, interceptor) {
+ return _.extend(request, interceptor(request.element, operation,
+ path, url, request.headers, request.params, request.httpConfig));
+ }, defaultRequest);
+ };
+
+ object.addRequestInterceptor = function(interceptor) {
+ config.requestInterceptors.push(function(elem, operation, path, url, headers, params, httpConfig) {
+ return {
+ headers: headers,
+ params: params,
+ element: interceptor(elem, operation, path, url),
+ httpConfig: httpConfig
+ };
+ });
+ return this;
+ };
+
+ object.setRequestInterceptor = object.addRequestInterceptor;
+
+ object.addFullRequestInterceptor = function(interceptor) {
+ config.requestInterceptors.push(interceptor);
+ return this;
+ };
+
+ object.setFullRequestInterceptor = object.addFullRequestInterceptor;
+
+ config.errorInterceptor = config.errorInterceptor || function() {};
+
+ object.setErrorInterceptor = function(interceptor) {
+ config.errorInterceptor = interceptor;
+ return this;
+ };
+
+ config.onBeforeElemRestangularized = config.onBeforeElemRestangularized || function(elem) {
+ return elem;
+ };
+ object.setOnBeforeElemRestangularized = function(post) {
+ config.onBeforeElemRestangularized = post;
+ return this;
+ };
+
+ /**
+ * This method is called after an element has been "Restangularized".
+ *
+ * It receives the element, a boolean indicating if it's an element or a collection
+ * and the name of the model
+ *
+ */
+ config.onElemRestangularized = config.onElemRestangularized || function(elem) {
+ return elem;
+ };
+ object.setOnElemRestangularized = function(post) {
+ config.onElemRestangularized = post;
+ return this;
+ };
+
+ config.shouldSaveParent = config.shouldSaveParent || function() {
+ return true;
+ };
+ object.setParentless = function(values) {
+ if (_.isArray(values)) {
+ config.shouldSaveParent = function(route) {
+ return !_.contains(values, route);
+ };
+ } else if (_.isBoolean(values)) {
+ config.shouldSaveParent = function() {
+ return !values;
+ };
+ }
+ return this;
+ };
+
+ /**
+ * This lets you set a suffix to every request.
+ *
+ * For example, if your api requires that for JSon requests you do /users/123.json, you can set that
+ * in here.
+ *
+ *
+ * By default, the suffix is null
+ */
+ config.suffix = _.isUndefined(config.suffix) ? null : config.suffix;
+ object.setRequestSuffix = function(newSuffix) {
+ config.suffix = newSuffix;
+ return this;
+ };
+
+ /**
+ * Add element transformers for certain routes.
+ */
+ config.transformers = config.transformers || {};
+ object.addElementTransformer = function(type, secondArg, thirdArg) {
+ var isCollection = null;
+ var transformer = null;
+ if (arguments.length === 2) {
+ transformer = secondArg;
+ } else {
+ transformer = thirdArg;
+ isCollection = secondArg;
+ }
+
+ var typeTransformers = config.transformers[type];
+ if (!typeTransformers) {
+ typeTransformers = config.transformers[type] = [];
+ }
+
+ typeTransformers.push(function(coll, elem) {
+ if (_.isNull(isCollection) || (coll == isCollection)) {
+ return transformer(elem);
+ }
+ return elem;
+ });
+
+ return object;
+ };
+
+ object.extendCollection = function(route, fn) {
+ return object.addElementTransformer(route, true, fn);
+ };
+
+ object.extendModel = function(route, fn) {
+ return object.addElementTransformer(route, false, fn);
+ };
+
+ config.transformElem = function(elem, isCollection, route, Restangular, force) {
+ if (!force && !config.transformLocalElements && !elem[config.restangularFields.fromServer]) {
+ return elem;
+ }
+ var typeTransformers = config.transformers[route];
+ var changedElem = elem;
+ if (typeTransformers) {
+ _.each(typeTransformers, function(transformer) {
+ changedElem = transformer(isCollection, changedElem);
+ });
+ }
+ return config.onElemRestangularized(changedElem,
+ isCollection, route, Restangular);
+ };
+
+ config.transformLocalElements = _.isUndefined(config.transformLocalElements) ? false : config.transformLocalElements;
+ object.setTransformOnlyServerElements = function(active) {
+ config.transformLocalElements = !active;
+ }
+
+ config.fullResponse = _.isUndefined(config.fullResponse) ? false : config.fullResponse;
+ object.setFullResponse = function(full) {
+ config.fullResponse = full;
+ return this;
+ };
+
+
+
+
+
+ //Internal values and functions
+ config.urlCreatorFactory = {};
+
+ /**
+ * Base URL Creator. Base prototype for everything related to it
+ **/
+
+ var BaseCreator = function() {
+ };
+
+ BaseCreator.prototype.setConfig = function(config) {
+ this.config = config;
+ return this;
+ };
+
+ BaseCreator.prototype.parentsArray = function(current) {
+ var parents = [];
+ while(current) {
+ parents.push(current);
+ current = current[this.config.restangularFields.parentResource];
+ }
+ return parents.reverse();
+ };
+
+ function RestangularResource(config, $http, url, configurer) {
+ var resource = {};
+ _.each(_.keys(configurer), function(key) {
+ var value = configurer[key];
+
+ // Add default parameters
+ value.params = _.extend({}, value.params,
+ config.defaultRequestParams[value.method.toLowerCase()]);
+ // We don't want the ? if no params are there
+ if (_.isEmpty(value.params)) {
+ delete value.params;
+ }
+
+ if (config.isSafe(value.method)) {
+
+ resource[key] = function() {
+ return $http(_.extend(value, {
+ url: url
+ }));
+ };
+
+ } else {
+
+ resource[key] = function(data) {
+ return $http(_.extend(value, {
+ url: url,
+ data: data
+ }));
+ };
+
+ }
+ });
+
+ return resource;
+ }
+
+ BaseCreator.prototype.resource = function(current, $http, localHttpConfig, callHeaders, callParams, what, etag, operation) {
+
+ var params = _.defaults(callParams || {}, this.config.defaultRequestParams.common);
+ var headers = _.defaults(callHeaders || {}, this.config.defaultHeaders);
+
+ if (etag) {
+ if (!config.isSafe(operation)) {
+ headers['If-Match'] = etag;
+ } else {
+ headers['If-None-Match'] = etag;
+ }
+ }
+
+ var url = this.base(current);
+
+ if (what) {
+ var add = '';
+ if (!/\/$/.test(url)) {
+ add += '/';
+ }
+ add += what;
+ url += add;
+ }
+
+ if (this.config.suffix
+ && url.indexOf(this.config.suffix, url.length - this.config.suffix.length) === -1
+ && !this.config.getUrlFromElem(current)) {
+ url += this.config.suffix;
+ }
+
+ current[this.config.restangularFields.httpConfig] = undefined;
+
+
+ return RestangularResource(this.config, $http, url, {
+ getList: this.config.withHttpValues(localHttpConfig,
+ {method: 'GET',
+ params: params,
+ headers: headers}),
+
+ get: this.config.withHttpValues(localHttpConfig,
+ {method: 'GET',
+ params: params,
+ headers: headers}),
+
+ jsonp: this.config.withHttpValues(localHttpConfig,
+ {method: 'jsonp',
+ params: params,
+ headers: headers}),
+
+ put: this.config.withHttpValues(localHttpConfig,
+ {method: 'PUT',
+ params: params,
+ headers: headers}),
+
+ post: this.config.withHttpValues(localHttpConfig,
+ {method: 'POST',
+ params: params,
+ headers: headers}),
+
+ remove: this.config.withHttpValues(localHttpConfig,
+ {method: 'DELETE',
+ params: params,
+ headers: headers}),
+
+ head: this.config.withHttpValues(localHttpConfig,
+ {method: 'HEAD',
+ params: params,
+ headers: headers}),
+
+ trace: this.config.withHttpValues(localHttpConfig,
+ {method: 'TRACE',
+ params: params,
+ headers: headers}),
+
+ options: this.config.withHttpValues(localHttpConfig,
+ {method: 'OPTIONS',
+ params: params,
+ headers: headers}),
+
+ patch: this.config.withHttpValues(localHttpConfig,
+ {method: 'PATCH',
+ params: params,
+ headers: headers})
+ });
+ };
+
+ /**
+ * This is the Path URL creator. It uses Path to show Hierarchy in the Rest API.
+ * This means that if you have an Account that then has a set of Buildings, a URL to a building
+ * would be /accounts/123/buildings/456
+ **/
+ var Path = function() {
+ };
+
+ Path.prototype = new BaseCreator();
+
+ Path.prototype.base = function(current) {
+ var __this = this;
+ return _.reduce(this.parentsArray(current), function(acum, elem) {
+ var elemUrl;
+ var elemSelfLink = __this.config.getUrlFromElem(elem);
+ if (elemSelfLink) {
+ if (__this.config.isAbsoluteUrl(elemSelfLink)) {
+ return elemSelfLink;
+ } else {
+ elemUrl = elemSelfLink;
+ }
+ } else {
+ elemUrl = elem[__this.config.restangularFields.route];
+
+ if (elem[__this.config.restangularFields.restangularCollection]) {
+ var ids = elem[__this.config.restangularFields.ids];
+ if (ids) {
+ elemUrl += "/" + ids.join(",");
+ }
+ } else {
+ var elemId;
+ if (__this.config.useCannonicalId) {
+ elemId = __this.config.getCannonicalIdFromElem(elem);
+ } else {
+ elemId = __this.config.getIdFromElem(elem);
+ }
+
+ if (config.isValidId(elemId) && !elem.singleOne) {
+ elemUrl += "/" + (__this.config.encodeIds ? encodeURIComponent(elemId) : elemId);
+ }
+ }
+ }
+
+ return acum.replace(/\/$/, "") + "/" + elemUrl;
+
+ }, this.config.baseUrl);
+ };
+
+
+
+ Path.prototype.fetchUrl = function(current, what) {
+ var baseUrl = this.base(current);
+ if (what) {
+ baseUrl += "/" + what;
+ }
+ return baseUrl;
+ };
+
+ Path.prototype.fetchRequestedUrl = function(current, what) {
+ var url = this.fetchUrl(current, what);
+ var params = current[config.restangularFields.reqParams];
+
+ // From here on and until the end of fetchRequestedUrl,
+ // the code has been kindly borrowed from angular.js
+ // The reason for such code bloating is coherence:
+ // If the user were to use this for cache management, the
+ // serialization of parameters would need to be identical
+ // to the one done by angular for cache keys to match.
+ function sortedKeys(obj) {
+ var keys = [];
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ keys.push(key);
+ }
+ }
+ return keys.sort();
+ }
+
+ function forEachSorted(obj, iterator, context) {
+ var keys = sortedKeys(obj);
+ for ( var i = 0; i < keys.length; i++) {
+ iterator.call(context, obj[keys[i]], keys[i]);
+ }
+ return keys;
+ }
+
+ function encodeUriQuery(val, pctEncodeSpaces) {
+ return encodeURIComponent(val).
+ replace(/%40/gi, '@').
+ replace(/%3A/gi, ':').
+ replace(/%24/g, '$').
+ replace(/%2C/gi, ',').
+ replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
+ }
+
+ if (!params) return url;
+ var parts = [];
+ forEachSorted(params, function(value, key) {
+ if (value == null || value == undefined) return;
+ if (!angular.isArray(value)) value = [value];
+
+ angular.forEach(value, function(v) {
+ if (angular.isObject(v)) {
+ v = angular.toJson(v);
+ }
+ parts.push(encodeUriQuery(key) + '=' +
+ encodeUriQuery(v));
+ });
+ });
+ return url + (this.config.suffix || '') + ((url.indexOf('?') === -1) ? '?' : '&') + parts.join('&');
+ };
+
+
+
+ config.urlCreatorFactory.path = Path;
+
+ };
+
+ var globalConfiguration = {};
+
+ Configurer.init(this, globalConfiguration);
+
+
+
+
+ this.$get = ['$http', '$q', function($http, $q) {
+
+ function createServiceForConfiguration(config) {
+ var service = {};
+
+ var urlHandler = new config.urlCreatorFactory[config.urlCreator]();
+ urlHandler.setConfig(config);
+
+ function restangularizeBase(parent, elem, route, reqParams, fromServer) {
+ elem[config.restangularFields.route] = route;
+ elem[config.restangularFields.getRestangularUrl] = _.bind(urlHandler.fetchUrl, urlHandler, elem);
+ elem[config.restangularFields.getRequestedUrl] = _.bind(urlHandler.fetchRequestedUrl, urlHandler, elem);
+ elem[config.restangularFields.addRestangularMethod] = _.bind(addRestangularMethodFunction, elem);
+ elem[config.restangularFields.clone] = _.bind(copyRestangularizedElement, elem, elem);
+ elem[config.restangularFields.reqParams] = _.isEmpty(reqParams) ? null : reqParams;
+ elem[config.restangularFields.withHttpConfig] = _.bind(withHttpConfig, elem);
+ elem[config.restangularFields.plain] = _.bind(stripRestangular, elem, elem);
+
+ // RequestLess connection
+ elem[config.restangularFields.one] = _.bind(one, elem, elem);
+ elem[config.restangularFields.all] = _.bind(all, elem, elem);
+ elem[config.restangularFields.several] = _.bind(several, elem, elem);
+ elem[config.restangularFields.oneUrl] = _.bind(oneUrl, elem, elem);
+ elem[config.restangularFields.allUrl] = _.bind(allUrl, elem, elem);
+
+ elem[config.restangularFields.fromServer] = !!fromServer;
+
+ if (parent && config.shouldSaveParent(route)) {
+ var parentId = config.getIdFromElem(parent);
+ var parentUrl = config.getUrlFromElem(parent);
+
+ var restangularFieldsForParent = _.union(
+ _.values( _.pick(config.restangularFields, ['route', 'singleOne', 'parentResource']) ),
+ config.extraFields
+ );
+ var parentResource = _.pick(parent, restangularFieldsForParent);
+
+ if (config.isValidId(parentId)) {
+ config.setIdToElem(parentResource, parentId);
+ }
+ if (config.isValidId(parentUrl)) {
+ config.setUrlToElem(parentResource, parentUrl);
+ }
+
+ elem[config.restangularFields.parentResource] = parentResource;
+ } else {
+ elem[config.restangularFields.parentResource] = null;
+ }
+ return elem;
+ }
+
+
+
+ function one(parent, route, id, singleOne) {
+ if (_.isNumber(route) || _.isNumber(parent)) {
+ var error = "You're creating a Restangular entity with the number "
+ error += "instead of the route or the parent. You can't call .one(12)";
+ throw new Error(error);
+ }
+ var elem = {};
+ config.setIdToElem(elem, id);
+ config.setFieldToElem(config.restangularFields.singleOne, elem, singleOne);
+ return restangularizeElem(parent, elem , route, false);
+ }
+
+
+ function all(parent, route) {
+ return restangularizeCollection(parent, [] , route, false);
+ }
+
+ function several(parent, route, ids) {
+ var collection = [];
+ collection[config.restangularFields.ids] =
+ Array.prototype.splice.call(arguments, 2);
+ return restangularizeCollection(parent, collection , route, false);
+ }
+
+ function oneUrl(parent, route, url) {
+ if (!route) {
+ throw new Error("Route is mandatory when creating new Restangular objects.");
+ }
+ var elem = {};
+ config.setUrlToElem(elem, url, route);
+ return restangularizeElem(parent, elem , route, false);
+ }
+
+
+ function allUrl(parent, route, url) {
+ if (!route) {
+ throw new Error("Route is mandatory when creating new Restangular objects.");
+ }
+ var elem = {};
+ config.setUrlToElem(elem, url, route);
+ return restangularizeCollection(parent, elem , route, false);
+ }
+ // Promises
+ function restangularizePromise(promise, isCollection, valueToFill) {
+ promise.call = _.bind(promiseCall, promise);
+ promise.get = _.bind(promiseGet, promise);
+ promise[config.restangularFields.restangularCollection] = isCollection;
+ if (isCollection) {
+ promise.push = _.bind(promiseCall, promise, "push");
+ }
+ promise.$object = valueToFill;
+ return promise;
+ }
+
+ function promiseCall(method) {
+ var deferred = $q.defer();
+ var callArgs = arguments;
+ var filledValue = {};
+ this.then(function(val) {
+ var params = Array.prototype.slice.call(callArgs, 1);
+ var func = val[method];
+ func.apply(val, params);
+ filledValue = val;
+ deferred.resolve(val);
+ });
+ return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue);
+ }
+
+ function promiseGet(what) {
+ var deferred = $q.defer();
+ var filledValue = {};
+ this.then(function(val) {
+ filledValue = val[what];
+ deferred.resolve(filledValue);
+ });
+ return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue);
+ }
+
+ function resolvePromise(deferred, response, data, filledValue) {
+
+ _.extend(filledValue, data);
+
+ // Trigger the full response interceptor.
+ if (config.fullResponse) {
+ return deferred.resolve(_.extend(response, {
+ data: data
+ }));
+ } else {
+ deferred.resolve(data);
+ }
+ }
+
+
+ // Elements
+
+ function stripRestangular(elem) {
+ if (_.isArray(elem)) {
+ var array = [];
+ _.each(elem, function(value) {
+ array.push(stripRestangular(value));
+ });
+ return array;
+ } else {
+ return _.omit(elem, _.values(_.omit(config.restangularFields, 'id')));
+ }
+
+
+ }
+
+ function addCustomOperation(elem) {
+ elem[config.restangularFields.customOperation] = _.bind(customFunction, elem);
+ _.each(["put", "post", "get", "delete"], function(oper) {
+ _.each(["do", "custom"], function(alias) {
+ var callOperation = oper === 'delete' ? 'remove' : oper;
+ var name = alias + oper.toUpperCase();
+ var callFunction;
+
+ if (callOperation !== 'put' && callOperation !== 'post') {
+ callFunction = customFunction;
+ } else {
+ callFunction = function(operation, elem, path, params, headers) {
+ return _.bind(customFunction, this)(operation, path, params, headers, elem);
+ };
+ }
+ elem[name] = _.bind(callFunction, elem, callOperation);
+ });
+ });
+ elem[config.restangularFields.customGETLIST] = _.bind(fetchFunction, elem);
+ elem[config.restangularFields.doGETLIST] = elem[config.restangularFields.customGETLIST];
+ }
+
+ function copyRestangularizedElement(fromElement, toElement) {
+ var copiedElement = angular.copy(fromElement, toElement);
+ return restangularizeElem(copiedElement[config.restangularFields.parentResource],
+ copiedElement, copiedElement[config.restangularFields.route], true);
+ }
+
+ function restangularizeElem(parent, element, route, fromServer, collection, reqParams) {
+ var elem = config.onBeforeElemRestangularized(element, false, route);
+
+ var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer);
+
+ if (config.useCannonicalId) {
+ localElem[config.restangularFields.cannonicalId] = config.getIdFromElem(localElem);
+ }
+
+ if (collection) {
+ localElem[config.restangularFields.getParentList] = function() {
+ return collection;
+ };
+ }
+
+ localElem[config.restangularFields.restangularCollection] = false;
+ localElem[config.restangularFields.get] = _.bind(getFunction, localElem);
+ localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem);
+ localElem[config.restangularFields.put] = _.bind(putFunction, localElem);
+ localElem[config.restangularFields.post] = _.bind(postFunction, localElem);
+ localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem);
+ localElem[config.restangularFields.head] = _.bind(headFunction, localElem);
+ localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem);
+ localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem);
+ localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem);
+ localElem[config.restangularFields.save] = _.bind(save, localElem);
+
+ addCustomOperation(localElem);
+ return config.transformElem(localElem, false, route, service, true);
+ }
+
+ function restangularizeCollection(parent, element, route, fromServer, reqParams) {
+ var elem = config.onBeforeElemRestangularized(element, true, route);
+
+ var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer);
+ localElem[config.restangularFields.restangularCollection] = true;
+ localElem[config.restangularFields.post] = _.bind(postFunction, localElem, null);
+ localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem);
+ localElem[config.restangularFields.head] = _.bind(headFunction, localElem);
+ localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem);
+ localElem[config.restangularFields.putElement] = _.bind(putElementFunction, localElem);
+ localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem);
+ localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem);
+ localElem[config.restangularFields.get] = _.bind(getById, localElem);
+ localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem, null);
+
+ addCustomOperation(localElem);
+ return config.transformElem(localElem, true, route, service, true);
+ }
+
+ function restangularizeCollectionAndElements(parent, element, route) {
+ var collection = restangularizeCollection(parent, element, route, false);
+ _.each(collection, function(elem) {
+ restangularizeElem(parent, elem, route, false);
+ });
+ return collection;
+ }
+
+ function getById(id, reqParams, headers){
+ return this.customGET(id.toString(), reqParams, headers);
+ }
+
+ function putElementFunction(idx, params, headers) {
+ var __this = this;
+ var elemToPut = this[idx];
+ var deferred = $q.defer();
+ var filledArray = [];
+ filledArray = config.transformElem(filledArray, true, elemToPut[config.restangularFields.route], service)
+ elemToPut.put(params, headers).then(function(serverElem) {
+ var newArray = copyRestangularizedElement(__this);
+ newArray[idx] = serverElem;
+ filledArray = newArray;
+ deferred.resolve(newArray);
+ }, function(response) {
+ deferred.reject(response);
+ });
+
+ return restangularizePromise(deferred.promise, true, filledArray);
+ }
+
+ function parseResponse(resData, operation, route, fetchUrl, response, deferred) {
+ var data = config.responseExtractor(resData, operation, route, fetchUrl, response, deferred);
+ var etag = response.headers("ETag");
+ if (data && etag) {
+ data[config.restangularFields.etag] = etag;
+ }
+ return data;
+ }
+
+
+ function fetchFunction(what, reqParams, headers) {
+ var __this = this;
+ var deferred = $q.defer();
+ var operation = 'getList';
+ var url = urlHandler.fetchUrl(this, what);
+ var whatFetched = what || __this[config.restangularFields.route];
+
+ var request = config.fullRequestInterceptor(null, operation,
+ whatFetched, url, headers || {}, reqParams || {}, this[config.restangularFields.httpConfig] || {});
+
+ var filledArray = [];
+ filledArray = config.transformElem(filledArray, true, whatFetched, service)
+
+ var method = "getList";
+
+ if (config.jsonp) {
+ method = "jsonp";
+ }
+
+ urlHandler.resource(this, $http, request.httpConfig, request.headers, request.params, what,
+ this[config.restangularFields.etag], operation)[method]().then(function(response) {
+ var resData = response.data;
+ var fullParams = response.config.params;
+ var data = parseResponse(resData, operation, whatFetched, url, response, deferred);
+
+ // support empty response for getList() calls (some APIs respond with 204 and empty body)
+ if (_.isUndefined(data) || "" === data) {
+ data = []
+ }
+ if (!_.isArray(data)) {
+ throw new Error("Response for getList SHOULD be an array and not an object or something else");
+ }
+ var processedData = _.map(data, function(elem) {
+ if (!__this[config.restangularFields.restangularCollection]) {
+ return restangularizeElem(__this, elem, what, true, data);
+ } else {
+ return restangularizeElem(__this[config.restangularFields.parentResource],
+ elem, __this[config.restangularFields.route], true, data);
+ }
+
+ });
+
+ processedData = _.extend(data, processedData);
+
+ if (!__this[config.restangularFields.restangularCollection]) {
+ resolvePromise(deferred, response, restangularizeCollection(__this, processedData, what, true, fullParams), filledArray);
+ } else {
+ resolvePromise(deferred, response, restangularizeCollection(__this[config.restangularFields.parentResource], processedData, __this[config.restangularFields.route], true, fullParams), filledArray);
+ }
+ }, function error(response) {
+ if (response.status === 304 && __this[config.restangularFields.restangularCollection]) {
+ resolvePromise(deferred, response, __this, filledArray);
+ } else if ( config.errorInterceptor(response, deferred) !== false ) {
+ deferred.reject(response);
+ }
+ });
+
+ return restangularizePromise(deferred.promise, true, filledArray);
+ }
+
+ function withHttpConfig(httpConfig) {
+ this[config.restangularFields.httpConfig] = httpConfig;
+ return this;
+ }
+
+ function save(params, headers) {
+ if (this[config.restangularFields.fromServer]) {
+ return this[config.restangularFields.put](params, headers);
+ } else {
+ return _.bind(elemFunction, this)("post", undefined, params, undefined, headers);
+ }
+ }
+
+ function elemFunction(operation, what, params, obj, headers) {
+ var __this = this;
+ var deferred = $q.defer();
+ var resParams = params || {};
+ var route = what || this[config.restangularFields.route];
+ var fetchUrl = urlHandler.fetchUrl(this, what);
+
+ var callObj = obj || this;
+ // fallback to etag on restangular object (since for custom methods we probably don't explicitly specify the etag field)
+ var etag = callObj[config.restangularFields.etag] || (operation != "post" ? this[config.restangularFields.etag] : null);
+
+ if (_.isObject(callObj) && config.isRestangularized(callObj)) {
+ callObj = stripRestangular(callObj);
+ }
+ var request = config.fullRequestInterceptor(callObj, operation, route, fetchUrl,
+ headers || {}, resParams || {}, this[config.restangularFields.httpConfig] || {});
+
+ var filledObject = {};
+ filledObject = config.transformElem(filledObject, false, route, service);
+
+ var okCallback = function(response) {
+ var resData = response.data;
+ var fullParams = response.config.params;
+ var elem = parseResponse(resData, operation, route, fetchUrl, response, deferred);
+ if (elem) {
+
+ if (operation === "post" && !__this[config.restangularFields.restangularCollection]) {
+ resolvePromise(deferred, response, restangularizeElem(__this, elem, what, true, null, fullParams), filledObject);
+ } else {
+ data = restangularizeElem(__this[config.restangularFields.parentResource], elem, __this[config.restangularFields.route], true, null, fullParams)
+ data[config.restangularFields.singleOne] = __this[config.restangularFields.singleOne]
+ resolvePromise(deferred, response, data, filledObject);
+ }
+
+ } else {
+ resolvePromise(deferred, response, undefined, filledObject);
+ }
+ };
+
+ var errorCallback = function(response) {
+ if (response.status === 304 && config.isSafe(operation)) {
+ resolvePromise(deferred, response, __this, filledObject);
+ } else if ( config.errorInterceptor(response, deferred) !== false ) {
+ deferred.reject(response);
+ }
+ };
+ // Overring HTTP Method
+ var callOperation = operation;
+ var callHeaders = _.extend({}, request.headers);
+ var isOverrideOperation = config.isOverridenMethod(operation);
+ if (isOverrideOperation) {
+ callOperation = 'post';
+ callHeaders = _.extend(callHeaders, {'X-HTTP-Method-Override': operation === 'remove' ? 'DELETE' : operation});
+ } else if (config.jsonp && callOperation === 'get') {
+ callOperation = 'jsonp';
+ }
+
+ if (config.isSafe(operation)) {
+ if (isOverrideOperation) {
+ urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params,
+ what, etag, callOperation)[callOperation]({}).then(okCallback, errorCallback);
+ } else {
+ urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params,
+ what, etag, callOperation)[callOperation]().then(okCallback, errorCallback);
+ }
+ } else {
+ urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params,
+ what, etag, callOperation)[callOperation](request.element).then(okCallback, errorCallback);
+ }
+
+ return restangularizePromise(deferred.promise, false, filledObject);
+ }
+
+ function getFunction(params, headers) {
+ return _.bind(elemFunction, this)("get", undefined, params, undefined, headers);
+ }
+
+ function deleteFunction(params, headers) {
+ return _.bind(elemFunction, this)("remove", undefined, params, undefined, headers);
+ }
+
+ function putFunction(params, headers) {
+ return _.bind(elemFunction, this)("put", undefined, params, undefined, headers);
+ }
+
+ function postFunction(what, elem, params, headers) {
+ return _.bind(elemFunction, this)("post", what, params, elem, headers);
+ }
+
+ function headFunction(params, headers) {
+ return _.bind(elemFunction, this)("head", undefined, params, undefined, headers);
+ }
+
+ function traceFunction(params, headers) {
+ return _.bind(elemFunction, this)("trace", undefined, params, undefined, headers);
+ }
+
+ function optionsFunction(params, headers) {
+ return _.bind(elemFunction, this)("options", undefined, params, undefined, headers);
+ }
+
+ function patchFunction(elem, params, headers) {
+ return _.bind(elemFunction, this)("patch", undefined, params, elem, headers);
+ }
+
+ function customFunction(operation, path, params, headers, elem) {
+ return _.bind(elemFunction, this)(operation, path, params, elem, headers);
+ }
+
+ function addRestangularMethodFunction(name, operation, path, defaultParams, defaultHeaders, defaultElem) {
+ var bindedFunction;
+ if (operation === 'getList') {
+ bindedFunction = _.bind(fetchFunction, this, path);
+ } else {
+ bindedFunction = _.bind(customFunction, this, operation, path);
+ }
+
+ var createdFunction = function(params, headers, elem) {
+ var callParams = _.defaults({
+ params: params,
+ headers: headers,
+ elem: elem
+ }, {
+ params: defaultParams,
+ headers: defaultHeaders,
+ elem: defaultElem
+ });
+ return bindedFunction(callParams.params, callParams.headers, callParams.elem);
+ };
+
+ if (config.isSafe(operation)) {
+ this[name] = createdFunction;
+ } else {
+ this[name] = function(elem, params, headers) {
+ return createdFunction(params, headers, elem);
+ };
+ }
+
+ }
+
+ function withConfigurationFunction(configurer) {
+ var newConfig = angular.copy(_.omit(config, 'configuration'));
+ Configurer.init(newConfig, newConfig);
+ configurer(newConfig);
+ return createServiceForConfiguration(newConfig);
+ }
+
+ function toService(route, parent) {
+ var serv = {};
+ var collection = (parent || service).all(route);
+ serv.one = _.bind(one, (parent || service), parent, route);
+ serv.post = _.bind(collection.post, collection);
+ serv.getList = _.bind(collection.getList, collection);
+ return serv;
+ }
+
+
+ Configurer.init(service, config);
+
+ service.copy = _.bind(copyRestangularizedElement, service);
+
+ service.service = _.bind(toService, service);
+
+ service.withConfig = _.bind(withConfigurationFunction, service);
+
+ service.one = _.bind(one, service, null);
+
+ service.all = _.bind(all, service, null);
+
+ service.several = _.bind(several, service, null);
+
+ service.oneUrl = _.bind(oneUrl, service, null);
+
+ service.allUrl = _.bind(allUrl, service, null);
+
+ service.stripRestangular = _.bind(stripRestangular, service);
+
+ service.restangularizeElement = _.bind(restangularizeElem, service);
+
+ service.restangularizeCollection = _.bind(restangularizeCollectionAndElements, service);
+
+ return service;
+ }
+
+ return createServiceForConfiguration(globalConfiguration);
+
+ }];
+ }
+);
+
+})();
diff --git a/src/main/webapp/js/underscore-min.js b/src/main/webapp/js/underscore-min.js
new file mode 100644
index 0000000..3434d6c
--- /dev/null
+++ b/src/main/webapp/js/underscore-min.js
@@ -0,0 +1,6 @@
+// Underscore.js 1.6.0
+// http://underscorejs.org
+// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+// Underscore may be freely distributed under the MIT license.
+(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?void(this._wrapped=n):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.6.0";var A=j.each=j.forEach=function(n,t,e){if(null==n)return n;if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return;return n};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var O="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},j.find=j.detect=function(n,t,r){var e;return k(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var k=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:k(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,j.property(t))},j.where=function(n,t){return j.filter(n,j.matches(t))},j.findWhere=function(n,t){return j.find(n,j.matches(t))},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);var e=-1/0,u=-1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;o>u&&(e=n,u=o)}),e},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);var e=1/0,u=1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;u>o&&(e=n,u=o)}),e},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=j.values(n)),n[j.random(n.length-1)]):j.shuffle(n).slice(0,Math.max(0,t))};var E=function(n){return null==n?j.identity:j.isFunction(n)?n:j.property(n)};j.sortBy=function(n,t,r){return t=E(t),j.pluck(j.map(n,function(n,e,u){return{value:n,index:e,criteria:t.call(r,n,e,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=E(r),A(t,function(i,a){var o=r.call(e,i,a,t);n(u,o,i)}),u}};j.groupBy=F(function(n,t,r){j.has(n,t)?n[t].push(r):n[t]=[r]}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=E(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])t?[]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.partition=function(n,t){var r=[],e=[];return A(n,function(n){(t(n)?r:e).push(n)}),[r,e]},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.contains(t,n)})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===j&&(e[u]=arguments[r++]);for(;r=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u),e=u=null):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o,c=function(){var l=j.now()-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u),i=u=null))};return function(){i=this,u=arguments,a=j.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u),i=u=null),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return j.partial(t,n)},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=function(n){if(!j.isObject(n))return[];if(w)return w(n);var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o)&&"constructor"in n&&"constructor"in t)return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.constant=function(n){return function(){return n}},j.property=function(n){return function(t){return t[n]}},j.matches=function(n){return function(t){if(t===n)return!0;for(var r in n)if(n[r]!==t[r])return!1;return!0}},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},j.now=Date.now||function(){return(new Date).getTime()};var T={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};T.unescape=j.invert(T.escape);var I={escape:new RegExp("["+j.keys(T.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(T.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(I[n],function(t){return T[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),"function"==typeof define&&define.amd&&define("underscore",[],function(){return j})}).call(this);
+//# sourceMappingURL=underscore-min.map
\ No newline at end of file
diff --git a/src/main/webapp/partials/index.html b/src/main/webapp/partials/index.html
index 2d85dce..49cd45e 100644
--- a/src/main/webapp/partials/index.html
+++ b/src/main/webapp/partials/index.html
@@ -14,4 +14,6 @@ News
{{newsEntry.date | date}}
{{newsEntry.content}}
+
+{{newsEntry}}
\ No newline at end of file