Skip to content

Instantly share code, notes, and snippets.

@primathon
Last active August 29, 2015 14:08

Revisions

  1. primathon revised this gist Oct 24, 2014. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    <!--
    Some words about what's going on here: http://colinmccann.com/angularjs-api-frontend
    GitHub Gist: https://gist.github.com/primathon/4ccb35ad6ddece4af1db
    Live Plunkr Demo: http://embed.plnkr.co/o6VgT2OCwdGFhAH0QP5u/preview
    Live Plunkr Demo: http://embed.plnkr.co/fUIHDw/preview
    -->
    <!doctype html>
    <html lang="en" ng-app="app">
  2. primathon revised this gist Oct 24, 2014. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,5 @@
    <!--
    Some words about what's going on here: http://colinmccann.com/angularjs-api-frontend
    GitHub Gist: https://gist.github.com/primathon/4ccb35ad6ddece4af1db
    Live Plunkr Demo: http://embed.plnkr.co/o6VgT2OCwdGFhAH0QP5u/preview
    -->
  3. primathon revised this gist Oct 24, 2014. 1 changed file with 190 additions and 186 deletions.
    376 changes: 190 additions & 186 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,7 @@
    <!--
    GitHub Gist: https://gist.github.com/primathon/4ccb35ad6ddece4af1db
    Live Plunkr Demo: http://embed.plnkr.co/o6VgT2OCwdGFhAH0QP5u/preview
    -->
    <!doctype html>
    <html lang="en" ng-app="app">
    <head>
    @@ -37,245 +41,245 @@

    <script type="text/javascript">

    /**
    * Efforts have been made to follow best practices and guidelines as laid out here: https://github.com/johnpapa/angularjs-styleguide
    * That said, I've included everything in one file for the sake of readability. There are still quite a few tweaks that could be
    * made to this in order to conform to the style guide more accurately. First and foremost, this is a functional demonstration.
    */
    /**
    * Efforts have been made to follow best practices and guidelines as laid out here: https://github.com/johnpapa/angularjs-styleguide
    * That said, I've included everything in one file for the sake of readability. There are still quite a few tweaks that could be
    * made to this in order to conform to the style guide more accurately. First and foremost, this is a functional demonstration.
    */

    'use strict';
    'use strict';

    /**
    * Create our Angular instance and load our modules
    */
    var app = angular.module("app", [
    'ngSanitize', 'ngAnimate',
    'ui.router', 'ui.bootstrap',
    'angular-loading-bar', 'angular-growl',
    'restangular'
    ]);
    /**
    * Create our Angular instance and load our modules
    */
    var app = angular.module("app", [
    'ngSanitize', 'ngAnimate',
    'ui.router', 'ui.bootstrap',
    'angular-loading-bar', 'angular-growl',
    'restangular'
    ]);

    (function () {
    (function () {

    'use strict';
    'use strict';

    /**
    * Define application routes (well... route)
    */
    app.config(function ($stateProvider, $urlRouterProvider) {
    /**
    * Define application routes (well... route)
    */
    app.config(function ($stateProvider, $urlRouterProvider) {

    $stateProvider
    $stateProvider
    .state('posts', {
    url: "/posts",
    controller: "PostsController"
    });
    $urlRouterProvider.otherwise("/posts");
    $urlRouterProvider.otherwise("/posts");

    });

    /**
    * Configure Restangular to use a dummy API
    */
    app.config(function (RestangularProvider) {
    RestangularProvider.setBaseUrl("http://jsonplaceholder.typicode.com");
    });

    });
    /**
    * Create PostsController
    *
    * @require PostsService
    */
    app.controller('PostsController', ['PostsService', function (PostsService) {

    /**
    * Configure Restangular to use a dummy API
    * This utilizes "controller-as" syntax (which you can see referenced in the HTML). For more info:
    * - http://toddmotto.com/digging-into-angulars-controller-as-syntax/
    * - http://www.johnpapa.net/angularjss-controller-as-and-the-vm-variable/
    */
    app.config(function (RestangularProvider) {
    RestangularProvider.setBaseUrl("http://jsonplaceholder.typicode.com");
    });
    var vm = this;

    vm.posts = getPosts();
    vm.deletePost = deletePost;
    vm.showPost = showPost;
    vm.storePost = storePost;
    vm.updatePost = updatePost;

    /**
    * Create PostsController
    * It would be rare that you're ever dealing with creating a new post and updating existing posts on the same page,
    * but in this instance, it keeps things much more readable to hold the data in two separate objects.
    *
    * @require PostsService
    * Also, I'm hard-coding a user_id in here; this would normally be taken care of elsewhere in your application.
    */
    app.controller('PostsController', ['PostsService', function (PostsService) {
    vm.new_post = { title: null, body: null, user_id: 12345 };
    vm.update_post = { title: null, body: null, id: null };

    /**
    * This utilizes "controller-as" syntax (which you can see referenced in the HTML). For more info:
    * - http://toddmotto.com/digging-into-angulars-controller-as-syntax/
    * - http://www.johnpapa.net/angularjss-controller-as-and-the-vm-variable/
    */
    var vm = this;
    // GET /resource | return a collection of resources | index | HTTP/1.1 200 OK | idempotent
    function getPosts() {
    return PostsService.getPosts();
    }

    vm.posts = getPosts();
    vm.deletePost = deletePost;
    vm.showPost = showPost;
    vm.storePost = storePost;
    vm.updatePost = updatePost;
    // DELETE /resource/{id} | delete the resource | destroy | HTTP/1.1 204 No Content
    function deletePost(postId) {
    PostsService.deletePost(postId);
    }

    /**
    * It would be rare that you're ever dealing with creating a new post and updating existing posts on the same page,
    * but in this instance, it keeps things much more readable to hold the data in two separate objects.
    *
    * Also, I'm hard-coding a user_id in here; this would normally be taken care of elsewhere in your application.
    */
    vm.new_post = { title: null, body: null, user_id: 12345 };
    vm.update_post = { title: null, body: null, id: null };
    // GET /resource/{id} | return a single resource instance | show | HTTP/1.1 200 OK | idempotent
    function showPost(postId) {
    PostsService.showPost(postId);
    }

    // GET /resource | return a collection of resources | index | HTTP/1.1 200 OK | idempotent
    function getPosts() {
    return PostsService.getPosts();
    }
    // POST /resource | create a new resource in a collection | store | HTTP/1.1 201 Created
    function storePost() {
    PostsService.storePost(this.new_post);
    }

    // DELETE /resource/{id} | delete the resource | destroy | HTTP/1.1 204 No Content
    function deletePost(postId) {
    PostsService.deletePost(postId);
    }
    // PUT /resource/{id} | update a resource | update | HTTP/1.1 204 No Content | idempotent
    function updatePost() {
    PostsService.updatePost(this.update_post);
    }

    // GET /resource/{id} | return a single resource instance | show | HTTP/1.1 200 OK | idempotent
    function showPost(postId) {
    PostsService.showPost(postId);
    }
    }]);

    // POST /resource | create a new resource in a collection | store | HTTP/1.1 201 Created
    function storePost() {
    PostsService.storePost(this.new_post);
    }
    /**
    * Configure PostsService
    *
    * @require Restangular
    * @require growl (actually, quite optional, but it lets you see what's going on)
    */
    app.service('PostsService', ['Restangular', 'growl', function (Restangular, growl) {

    // PUT /resource/{id} | update a resource | update | HTTP/1.1 204 No Content | idempotent
    function updatePost() {
    PostsService.updatePost(this.update_post);
    }
    /**
    * I've made every attempt to conform to the standards set forth by {json:api}, "A standard for building APIs in JSON"
    * - http://jsonapi.org/format/
    *
    * It is definitely in your best interest to read as much as possible and get very familiar with the concepts they introduce.
    *
    * Also note that most of the methods here include a big chunk of code for the angular-growl notifications. In production,
    * you would likely not be using these, but they're extremely helpful for determining what's going on while building your API.
    */

    }]);
    /**
    * Return a collection of resources
    *
    * @request HTTP/1.1 GET /resource
    * @return HTTP/1.1 200 OK
    */
    this.getPosts = function () {
    return Restangular.all('posts').getList().$object;
    };

    /**
    * Configure PostsService
    * Return a single resource instance
    *
    * @require Restangular
    * @require growl (actually, quite optional, but it lets you see what's going on)
    * @request HTTP/1.1 GET /resource/{id}
    * @return HTTP/1.1 200 OK
    */
    app.service('PostsService', ['Restangular', 'growl', function (Restangular, growl) {
    this.showPost = function (postId) {
    return Restangular.one('posts', postId).get().then(function (post) {

    // Begin growl response popup ---------------------------------------
    post = post.plain();
    var resp = "GET /posts/" + postId + "<br><br>";
    resp += "HTTP/1.1 200 OK<br><br>";
    Object.keys(post).forEach(function (k, i) {
    resp += '<strong>' + k + '</strong>: "' + post[k] + '",<br>';
    });
    growl.addInfoMessage(resp, {ttl: 5000, enableHtml: true});
    // End growl popup --------------------------------------------------

    /**
    * I've made every attempt to conform to the standards set forth by {json:api}, "A standard for building APIs in JSON"
    * - http://jsonapi.org/format/
    *
    * It is definitely in your best interest to read as much as possible and get very familiar with the concepts they introduce.
    *
    * Also note that most of the methods here include a big chunk of code for the angular-growl notifications. In production,
    * you would likely not be using these, but they're extremely helpful for determining what's going on while building your API.
    */
    });
    };

    /**
    * Return a collection of resources
    *
    * @request HTTP/1.1 GET /resource
    * @return HTTP/1.1 200 OK
    */
    this.getPosts = function () {
    return Restangular.all('posts').getList().$object;
    };

    /**
    * Return a single resource instance
    *
    * @request HTTP/1.1 GET /resource/{id}
    * @return HTTP/1.1 200 OK
    */
    this.showPost = function (postId) {
    return Restangular.one('posts', postId).get().then(function (post) {
    /**
    * Delete the resource
    *
    * @request HTTP/1.1 DELETE /resource/{id}
    * @return HTTP/1.1 204 No Content
    */
    this.deletePost = function (postId) {
    return Restangular.one('posts', postId).remove().then(function () {

    // Begin growl response popup ---------------------------------------
    post = post.plain();
    var resp = "GET /posts/" + postId + "<br><br>";
    resp += "HTTP/1.1 200 OK<br><br>";
    Object.keys(post).forEach(function (k, i) {
    resp += '<strong>' + k + '</strong>: "' + post[k] + '",<br>';
    });
    growl.addInfoMessage(resp, {ttl: 5000, enableHtml: true});
    // End growl popup --------------------------------------------------
    /**
    * NOTE: it is dependent on the specific details of your implementation how you want to remove the deleted entities from your
    * data model. For this example, no posts are removed, as they're not actually being deleted from the demo API.
    */

    });
    };
    // Begin growl response popup ---------------------------------------
    var resp = "DELETE /posts/" + postId + "<br><br>";
    resp += "HTTP/1.1 204 No Content<br><br>";
    resp += "Post ID #" + postId + " successfully deleted";
    growl.addErrorMessage(resp, {ttl: 5000, enableHtml: true});
    // End growl popup --------------------------------------------------

    /**
    * Delete the resource
    *
    * @request HTTP/1.1 DELETE /resource/{id}
    * @return HTTP/1.1 204 No Content
    */
    this.deletePost = function (postId) {
    return Restangular.one('posts', postId).remove().then(function () {
    });
    };

    /**
    * NOTE: it is dependent on the specific details of your implementation how you want to remove the deleted entities from your
    * data model. For this example, no posts are removed, as they're not actually being deleted from the demo API.
    */
    /**
    * Create a new resource in a collection
    *
    * @request HTTP/1.1 POST /resource
    * @return HTTP/1.1 201 Created
    */
    this.storePost = function (newPost) {

    // Begin growl response popup ---------------------------------------
    var resp = "DELETE /posts/" + postId + "<br><br>";
    resp += "HTTP/1.1 204 No Content<br><br>";
    resp += "Post ID #" + postId + " successfully deleted";
    growl.addErrorMessage(resp, {ttl: 5000, enableHtml: true});
    // End growl popup --------------------------------------------------
    if ((newPost.title == null) || (newPost.body == null)) return false;

    });
    };
    return Restangular.all("posts").post(newPost).then(function (post) {

    /**
    * Create a new resource in a collection
    *
    * @request HTTP/1.1 POST /resource
    * @return HTTP/1.1 201 Created
    */
    this.storePost = function (newPost) {
    /**
    * NOTE: Creating a new resource entity SHOULD return HTTP 201 Created, but the demo API returns 200 OK.
    */

    if ((newPost.title == null) || (newPost.body == null)) return false;
    // Begin growl response popup ---------------------------------------
    post = post.plain();
    var resp = "POST /posts<br><br>";
    resp += "HTTP/1.1 201 Created<br><br>";
    Object.keys(post).forEach(function (k, i) {
    resp += '<strong>' + k + '</strong>: "' + post[k] + '",<br>';
    });
    growl.addSuccessMessage(resp, {ttl: 5000, enableHtml: true});
    // End growl popup --------------------------------------------------

    return Restangular.all("posts").post(newPost).then(function (post) {
    });
    };

    /**
    * NOTE: Creating a new resource entity SHOULD return HTTP 201 Created, but the demo API returns 200 OK.
    */
    /**
    * Update an existing resource
    *
    * @request HTTP/1.1 PUT /resource/{id}
    * @return HTTP/1.1 204 No Content
    */
    this.updatePost = function (updatePost) {

    // Begin growl response popup ---------------------------------------
    post = post.plain();
    var resp = "POST /posts<br><br>";
    resp += "HTTP/1.1 201 Created<br><br>";
    Object.keys(post).forEach(function (k, i) {
    resp += '<strong>' + k + '</strong>: "' + post[k] + '",<br>';
    });
    growl.addSuccessMessage(resp, {ttl: 5000, enableHtml: true});
    // End growl popup --------------------------------------------------
    if ((updatePost.id == null) || (updatePost.title == null) || (updatePost.body == null)) return false;

    });
    };
    var postId = updatePost.id;

    /**
    * Update an existing resource
    * NOTE: Updating an existing resource SHOULD return HTTP 204 No Content, but the demo API returns 200 OK with the updated entity.
    *
    * @request HTTP/1.1 PUT /resource/{id}
    * @return HTTP/1.1 204 No Content
    * NOTE 2: We're using customPUT() instead of put() to eliminate the redundant GET request that occurs when you select an element
    * and then start performing operations on it. Using customPUT() allows us to just send the object directly to the intended endpoint.
    */
    this.updatePost = function (updatePost) {

    if ((updatePost.id == null) || (updatePost.title == null) || (updatePost.body == null)) return false;

    var postId = updatePost.id;

    /**
    * NOTE: Updating an existing resource SHOULD return HTTP 204 No Content, but the demo API returns 200 OK with the updated entity.
    *
    * NOTE 2: We're using customPUT() instead of put() to eliminate the redundant GET request that occurs when you select an element
    * and then start performing operations on it. Using customPUT() allows us to just send the object directly to the intended endpoint.
    */

    return Restangular.one('posts', postId).customPUT(updatePost).then(function (post) {

    // Begin growl response popup ---------------------------------------
    post = post.plain();
    var resp = "PUT /posts/" + postId + "<br><br>";
    resp += "HTTP/1.1 204 No Content<br><br>";
    Object.keys(post).forEach(function (k, i) {
    resp += '<strong>' + k + '</strong>: "' + post[k] + '",<br>';
    });
    growl.addInfoMessage(resp, {ttl: 5000, enableHtml: true});
    // End growl popup --------------------------------------------------
    return Restangular.one('posts', postId).customPUT(updatePost).then(function (post) {

    // Begin growl response popup ---------------------------------------
    post = post.plain();
    var resp = "PUT /posts/" + postId + "<br><br>";
    resp += "HTTP/1.1 204 No Content<br><br>";
    Object.keys(post).forEach(function (k, i) {
    resp += '<strong>' + k + '</strong>: "' + post[k] + '",<br>';
    });
    };
    growl.addInfoMessage(resp, {ttl: 5000, enableHtml: true});
    // End growl popup --------------------------------------------------

    });
    };

    }]);
    }]);

    })();
    })();

    </script>

    @@ -346,4 +350,4 @@ <h2>Viewing all posts</h2>
    </div>

    </body>
    </html>
    </html>
  4. primathon created this gist Oct 24, 2014.
    349 changes: 349 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,349 @@
    <!doctype html>
    <html lang="en" ng-app="app">
    <head>

    <meta charset="UTF-8">
    <title>AngularJS API Frontend "hello world" Example</title>

    <!-- of course we're using Bootstrap -->
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"/>
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css"/>

    <!--
    AngularJS 1.2.20 + modules:
    - ui.router - https://github.com/angular-ui/ui-router
    - ui.bootstrap - https://github.com/angular-ui/bootstrap
    - Restangular - https://github.com/mgonto/restangular - requires underscore.js
    - angular-loading-bar - https://github.com/chieffancypants/angular-loading-bar
    - angular-growl - https://github.com/marcorinck/angular-growl
    - ngSanitize - https://docs.angularjs.org/api/ngSanitize - (optional) for showing HTML in angular-growl
    - ngAnimate - https://docs.angularjs.org/api/ngAnimate - (optional) for animating opacity change in angular-growl
    -->
    <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.11/angular-ui-router.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.10.0/ui-bootstrap.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/restangular/1.4.0/restangular.min.js"></script>
    <script src="//cdn.rawgit.com/chieffancypants/angular-loading-bar/master/src/loading-bar.js"></script>
    <script src="//cdn.rawgit.com/marcorinck/angular-growl/master/build/angular-growl.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular-sanitize.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular-animate.min.js"></script>

    <!-- Stylesheets for angular-loading-bar and angular-growl -->
    <link href="//cdn.rawgit.com/chieffancypants/angular-loading-bar/master/src/loading-bar.css" rel="stylesheet" type="text/css">
    <link href="//cdn.rawgit.com/marcorinck/angular-growl/master/build/angular-growl.min.css" rel="stylesheet" type="text/css">

    <base href="/">

    <script type="text/javascript">

    /**
    * Efforts have been made to follow best practices and guidelines as laid out here: https://github.com/johnpapa/angularjs-styleguide
    * That said, I've included everything in one file for the sake of readability. There are still quite a few tweaks that could be
    * made to this in order to conform to the style guide more accurately. First and foremost, this is a functional demonstration.
    */

    'use strict';

    /**
    * Create our Angular instance and load our modules
    */
    var app = angular.module("app", [
    'ngSanitize', 'ngAnimate',
    'ui.router', 'ui.bootstrap',
    'angular-loading-bar', 'angular-growl',
    'restangular'
    ]);

    (function () {

    'use strict';

    /**
    * Define application routes (well... route)
    */
    app.config(function ($stateProvider, $urlRouterProvider) {

    $stateProvider
    .state('posts', {
    url: "/posts",
    controller: "PostsController"
    });
    $urlRouterProvider.otherwise("/posts");

    });

    /**
    * Configure Restangular to use a dummy API
    */
    app.config(function (RestangularProvider) {
    RestangularProvider.setBaseUrl("http://jsonplaceholder.typicode.com");
    });

    /**
    * Create PostsController
    *
    * @require PostsService
    */
    app.controller('PostsController', ['PostsService', function (PostsService) {

    /**
    * This utilizes "controller-as" syntax (which you can see referenced in the HTML). For more info:
    * - http://toddmotto.com/digging-into-angulars-controller-as-syntax/
    * - http://www.johnpapa.net/angularjss-controller-as-and-the-vm-variable/
    */
    var vm = this;

    vm.posts = getPosts();
    vm.deletePost = deletePost;
    vm.showPost = showPost;
    vm.storePost = storePost;
    vm.updatePost = updatePost;

    /**
    * It would be rare that you're ever dealing with creating a new post and updating existing posts on the same page,
    * but in this instance, it keeps things much more readable to hold the data in two separate objects.
    *
    * Also, I'm hard-coding a user_id in here; this would normally be taken care of elsewhere in your application.
    */
    vm.new_post = { title: null, body: null, user_id: 12345 };
    vm.update_post = { title: null, body: null, id: null };

    // GET /resource | return a collection of resources | index | HTTP/1.1 200 OK | idempotent
    function getPosts() {
    return PostsService.getPosts();
    }

    // DELETE /resource/{id} | delete the resource | destroy | HTTP/1.1 204 No Content
    function deletePost(postId) {
    PostsService.deletePost(postId);
    }

    // GET /resource/{id} | return a single resource instance | show | HTTP/1.1 200 OK | idempotent
    function showPost(postId) {
    PostsService.showPost(postId);
    }

    // POST /resource | create a new resource in a collection | store | HTTP/1.1 201 Created
    function storePost() {
    PostsService.storePost(this.new_post);
    }

    // PUT /resource/{id} | update a resource | update | HTTP/1.1 204 No Content | idempotent
    function updatePost() {
    PostsService.updatePost(this.update_post);
    }

    }]);

    /**
    * Configure PostsService
    *
    * @require Restangular
    * @require growl (actually, quite optional, but it lets you see what's going on)
    */
    app.service('PostsService', ['Restangular', 'growl', function (Restangular, growl) {

    /**
    * I've made every attempt to conform to the standards set forth by {json:api}, "A standard for building APIs in JSON"
    * - http://jsonapi.org/format/
    *
    * It is definitely in your best interest to read as much as possible and get very familiar with the concepts they introduce.
    *
    * Also note that most of the methods here include a big chunk of code for the angular-growl notifications. In production,
    * you would likely not be using these, but they're extremely helpful for determining what's going on while building your API.
    */

    /**
    * Return a collection of resources
    *
    * @request HTTP/1.1 GET /resource
    * @return HTTP/1.1 200 OK
    */
    this.getPosts = function () {
    return Restangular.all('posts').getList().$object;
    };

    /**
    * Return a single resource instance
    *
    * @request HTTP/1.1 GET /resource/{id}
    * @return HTTP/1.1 200 OK
    */
    this.showPost = function (postId) {
    return Restangular.one('posts', postId).get().then(function (post) {

    // Begin growl response popup ---------------------------------------
    post = post.plain();
    var resp = "GET /posts/" + postId + "<br><br>";
    resp += "HTTP/1.1 200 OK<br><br>";
    Object.keys(post).forEach(function (k, i) {
    resp += '<strong>' + k + '</strong>: "' + post[k] + '",<br>';
    });
    growl.addInfoMessage(resp, {ttl: 5000, enableHtml: true});
    // End growl popup --------------------------------------------------

    });
    };

    /**
    * Delete the resource
    *
    * @request HTTP/1.1 DELETE /resource/{id}
    * @return HTTP/1.1 204 No Content
    */
    this.deletePost = function (postId) {
    return Restangular.one('posts', postId).remove().then(function () {

    /**
    * NOTE: it is dependent on the specific details of your implementation how you want to remove the deleted entities from your
    * data model. For this example, no posts are removed, as they're not actually being deleted from the demo API.
    */

    // Begin growl response popup ---------------------------------------
    var resp = "DELETE /posts/" + postId + "<br><br>";
    resp += "HTTP/1.1 204 No Content<br><br>";
    resp += "Post ID #" + postId + " successfully deleted";
    growl.addErrorMessage(resp, {ttl: 5000, enableHtml: true});
    // End growl popup --------------------------------------------------

    });
    };

    /**
    * Create a new resource in a collection
    *
    * @request HTTP/1.1 POST /resource
    * @return HTTP/1.1 201 Created
    */
    this.storePost = function (newPost) {

    if ((newPost.title == null) || (newPost.body == null)) return false;

    return Restangular.all("posts").post(newPost).then(function (post) {

    /**
    * NOTE: Creating a new resource entity SHOULD return HTTP 201 Created, but the demo API returns 200 OK.
    */

    // Begin growl response popup ---------------------------------------
    post = post.plain();
    var resp = "POST /posts<br><br>";
    resp += "HTTP/1.1 201 Created<br><br>";
    Object.keys(post).forEach(function (k, i) {
    resp += '<strong>' + k + '</strong>: "' + post[k] + '",<br>';
    });
    growl.addSuccessMessage(resp, {ttl: 5000, enableHtml: true});
    // End growl popup --------------------------------------------------

    });
    };

    /**
    * Update an existing resource
    *
    * @request HTTP/1.1 PUT /resource/{id}
    * @return HTTP/1.1 204 No Content
    */
    this.updatePost = function (updatePost) {

    if ((updatePost.id == null) || (updatePost.title == null) || (updatePost.body == null)) return false;

    var postId = updatePost.id;

    /**
    * NOTE: Updating an existing resource SHOULD return HTTP 204 No Content, but the demo API returns 200 OK with the updated entity.
    *
    * NOTE 2: We're using customPUT() instead of put() to eliminate the redundant GET request that occurs when you select an element
    * and then start performing operations on it. Using customPUT() allows us to just send the object directly to the intended endpoint.
    */

    return Restangular.one('posts', postId).customPUT(updatePost).then(function (post) {

    // Begin growl response popup ---------------------------------------
    post = post.plain();
    var resp = "PUT /posts/" + postId + "<br><br>";
    resp += "HTTP/1.1 204 No Content<br><br>";
    Object.keys(post).forEach(function (k, i) {
    resp += '<strong>' + k + '</strong>: "' + post[k] + '",<br>';
    });
    growl.addInfoMessage(resp, {ttl: 5000, enableHtml: true});
    // End growl popup --------------------------------------------------

    });
    };

    }]);

    })();

    </script>

    <style>
    /* ui.bootstrap styling */
    .nav, .pagination, .carousel, .panel-title a, a:not([href]) { cursor: pointer; }
    /* angular-growl messages offset */
    .growl { top: 20px; right: 20px; width: 350px; z-index: 9999; opacity: 0.85; }
    </style>

    </head>

    <body>

    <div growl></div>

    <div class="container">

    <h1>AngularJS API Frontend "hello world" Example</h1>

    <!-- See above for more on the "controller as" syntax -->
    <div ng-controller="PostsController as vm">

    <hr>

    <h2>Create a new post</h2>

    <form class="form-inline" role="form">
    <div class="form-group">
    <input ng-model="vm.new_post.title" type="text" class="form-control" placeholder="Post Title" required>
    </div>
    <div class="form-group">
    <input ng-model="vm.new_post.body" type="text" class="form-control" placeholder="Post Body" required>
    </div>
    <input type="submit" ng-click="vm.storePost()" class="btn btn-success" value="Create Post">
    </form>

    <hr>

    <h2>Update existing post</h2>

    <form class="form-inline" role="form">
    <div class="form-group">
    <input ng-model="vm.update_post.id" type="text" class="form-control" placeholder="ID" style="width: 45px;" required>
    </div>
    <div class="form-group">
    <input ng-model="vm.update_post.title" type="text" class="form-control" placeholder="Post Title" required>
    </div>
    <div class="form-group">
    <input ng-model="vm.update_post.body" type="text" class="form-control" placeholder="Post Body" required>
    </div>
    <input type="submit" ng-click="vm.updatePost()" class="btn btn-success" value="Update Post">
    </form>

    <hr>

    <h2>Viewing all posts</h2>
    <ul class="list-group">
    <li class="list-group-item" ng-repeat="post in vm.posts">
    <a class="btn btn-danger btn-xs pull-right" ng-click="vm.deletePost(post.id)" style="margin-left: 5px;">delete</a>
    <a class="btn btn-primary btn-xs pull-right" ng-click="vm.showPost(post.id)">load info</a>
    <p style="margin:0;" ng-click="vm.showPost(post.id)"><a style="font-weight: bold;">{{ post.title }}</a></p>
    </li>
    </ul>

    </div>

    </div>

    </body>
    </html>