BUILDING A RESTFUL WEB APP WITH
ANGULAR.JS AND BEAR.SUNDAY
RICHARD MCINTYRE
@MACKSTAR
GOAL RESTFUL CMS
FRONT-END ANGULAR.JS
BACK-END BEAR.SUNDAY
WHY ANOTHER CMS?
▸ Building RESOURCES
▸ Power through simplicity
▸ Easy overrides with AOP
▸ Kill plugin culture
▸ Build r...
CONTINUED...
▸ Embed in any PHP project
▸ Testable
▸ Relationships between resources / Hypermedia
▸ Templating through TWIG
BEAR.SUNDAY
▸ REST
▸ DI
▸ AOP
In most MVC application frameworks,
CRUD and OOP paradigms are mapped
to HTTP methods and resources. But the
opposite is n...
EVERYTHING IS A RESOURCE
Request: /api/resources/index?_start=1
Request Method: GET
Status Code: 200 OK
{
"resources": [
{
"id": "16",
"slug": "2332",
"type": "blog",
"title": "2332",
"type_name": "Blog",
"title_label": "Title...
Request: /api/resources/types
Request Method: POST
Status Code: 200 OK
{
"title_label": "Title",
"resource_fields": [
{
"field_type": {
"id": "1",
"name": "String",
"slug": "string"
},
"multipl...
Request: /api/menus/links?menu=primary
Request Method: GET
Status Code: 200 OK
{
"links": [
{
"id": "6",
"name": "Link 1",
"url": "/url1",
"type": "url",
"resource": null,
"resource_type": null,
"weigh...
namespace MackstarSpoutAdminResourceAppResources;
/**
* Resources Index
*
* @Db
*/
class Index extends ResourceObject
{
/*...
CUSTOM RESOURCES CAN CALL OTHER
RESOURCES
public function onGet($id = null)
{
$this['types'] = $this->resource->get->uri('...
OVERRIDES THROUGH AOP
▸ Validation
▸ Permissions
▸ Template Switching
▸ Other custom events
DEPENDENCIES
INJECTED
namespace MackstarSpoutAdminInterceptorUsers;
use RayAopMethodInterceptor;
use RayAopMethodInvocation;
use RayDiDiInject;
...
BIND USING MODULES
private function installUserSessionAppender()
{
$session = $this->requestInjection('MackstarSpoutAdminInterceptorUsersSess...
TESTS
namespace MackstarSpoutAdminTestInterceptorValidators;
use MackstarSpoutAdminInterceptorValidatorsUserValidator;
class Use...
FRONT-END INTEGRATION
{{
resource |
app://spout/resources/detail?type=photo-gallery&slug=spout-is-live |
detail-blog.twig
...
PAGINATION
{{
resource |
app://spout/resources/index?type=blog |
index-blog.twig |
paged(1)
}}
MENU
{{
resource |
app://spout/menus?slug=primary |
primary-menu.twig
}}
ARCHITECTURE
ANGULAR.JS
ADMIN
BE YOUR OWN
CONSUMER
COMPONENTS
▸ Routes
▸ Directives
▸ Controllers
▸ Services
2 WAY DATA
BINDING
TEMPLATE
<input type="email" ng-model="login.email" required>
CONTROLLER
scope.$watch('login.email', function () {
console...
ROUTESANGULAR-UI ROUTER
app.config(['$stateProvider', function($stateProvider) {
$stateProvider.state('login', {
url: "/login",
controller: 'Login...
DIRECTIVES<SP-THUMBNAIL />
app.directive('spThumbnail', function (Restangular) {
return {
restrict: 'E',
template: "<img src='/img/spinner.gif' ng-cl...
DEPENDENCY
INJECTION
Declare
var module = angular.module('restangular', []);
module.provider('Restangular', function() {}
Implement
var app = a...
TESTS
describe('Roles Directive', function () {
var scope,
$element;
beforeEach(function () {
module('Application');
angular.moc...
DEMO
STUFF TO DO
▸ Security
▸ DB/API Schema lock-down
▸ Create as composer component / Assetic
▸ More field types (locations/ti...
GET INVOLVEDGITHUB.COM/MACKSTAR/SPOUT
Spout - Building a RESTful web app with Angular.js and BEAR.Sunday
Spout - Building a RESTful web app with Angular.js and BEAR.Sunday
Spout - Building a RESTful web app with Angular.js and BEAR.Sunday
Spout - Building a RESTful web app with Angular.js and BEAR.Sunday
Upcoming SlideShare
Loading in …5
×

Spout - Building a RESTful web app with Angular.js and BEAR.Sunday

2,297
-1

Published on

Spout is a RESTful CMS created with Angular.js and BEAR.Sunday, we are just getting started but here is your your chance to get involved in a new CMS project.

Published in: Software, Technology, Business
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,297
On Slideshare
0
From Embeds
0
Number of Embeds
7
Actions
Shares
0
Downloads
10
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Spout - Building a RESTful web app with Angular.js and BEAR.Sunday

  1. 1. BUILDING A RESTFUL WEB APP WITH ANGULAR.JS AND BEAR.SUNDAY
  2. 2. RICHARD MCINTYRE @MACKSTAR
  3. 3. GOAL RESTFUL CMS FRONT-END ANGULAR.JS BACK-END BEAR.SUNDAY
  4. 4. WHY ANOTHER CMS? ▸ Building RESOURCES ▸ Power through simplicity ▸ Easy overrides with AOP ▸ Kill plugin culture ▸ Build re-usable libraries
  5. 5. CONTINUED... ▸ Embed in any PHP project ▸ Testable ▸ Relationships between resources / Hypermedia ▸ Templating through TWIG
  6. 6. BEAR.SUNDAY ▸ REST ▸ DI ▸ AOP
  7. 7. In most MVC application frameworks, CRUD and OOP paradigms are mapped to HTTP methods and resources. But the opposite is not true. - Akihito Koriyama
  8. 8. EVERYTHING IS A RESOURCE Request: /api/resources/index?_start=1 Request Method: GET Status Code: 200 OK
  9. 9. { "resources": [ { "id": "16", "slug": "2332", "type": "blog", "title": "2332", "type_name": "Blog", "title_label": "Title" } ], "_model": "resources", "_pager": { "maxPerPage": 5, "current": "1", "total": 1, "hasNext": false, "hasPrevious": false }, "_links": { "self": { "href": "spout/app/resources/index/?_start=1" } } }
  10. 10. Request: /api/resources/types Request Method: POST Status Code: 200 OK
  11. 11. { "title_label": "Title", "resource_fields": [ { "field_type": { "id": "1", "name": "String", "slug": "string" }, "multiple": 0, "weight": 1, "label": "Body", "slug": "body" } ], "name": "Page", "slug": "page" }
  12. 12. Request: /api/menus/links?menu=primary Request Method: GET Status Code: 200 OK
  13. 13. { "links": [ { "id": "6", "name": "Link 1", "url": "/url1", "type": "url", "resource": null, "resource_type": null, "weight": "999", "depth": null, "parent_id": "0", "menu": "primary" }, { "id": "7", "name": "Link 2", "url": "/url2", "type": "url", "resource": null, "resource_type": null, "weight": "999", "depth": null, "parent_id": "0", "menu": "primary" } ], "_model": "links" }
  14. 14. namespace MackstarSpoutAdminResourceAppResources; /** * Resources Index * * @Db */ class Index extends ResourceObject { /** * @Link(rel="type", href="app://self/resources/detail?type={slug}&slug={slug}") * @DbPager(5) */ public function onGet() { $sql = "SELECT {$this->table}.*, type.name as type_name, type.title_label FROM {$this->table} "; $sql .= "INNER JOIN resource_types AS type "; $sql .= "ON type.slug = {$this->table}.type"; $stmt = $this->db->query($sql); $this['resources'] = $stmt->fetchAll(PDO::FETCH_ASSOC); return $this; }
  15. 15. CUSTOM RESOURCES CAN CALL OTHER RESOURCES public function onGet($id = null) { $this['types'] = $this->resource->get->uri('app://self/resources/types') } RESOURCES OF A TYPE HAVE RESOURCE AND RESOURCE INDEX RELATIONSHIPS
  16. 16. OVERRIDES THROUGH AOP ▸ Validation ▸ Permissions ▸ Template Switching ▸ Other custom events
  17. 17. DEPENDENCIES INJECTED
  18. 18. namespace MackstarSpoutAdminInterceptorUsers; use RayAopMethodInterceptor; use RayAopMethodInvocation; use RayDiDiInject; use SymfonyComponentHttpFoundationSessionSession as PhpSession; class Session implements MethodInterceptor { private $session; /** * @Inject */ public function setSession(PhpSession $session) { $this->session = $session; } public function invoke(MethodInvocation $invocation) { $response = $invocation->proceed(); $response->body['_user'] = $this->session->get('user'); return $response; }
  19. 19. BIND USING MODULES
  20. 20. private function installUserSessionAppender() { $session = $this->requestInjection('MackstarSpoutAdminInterceptorUsersSession'); $this->bindInterceptor( $this->matcher->subclassesOf('BEARResourceResourceObject'), $this->matcher->startsWith('onGet'), [$session] ); }
  21. 21. TESTS
  22. 22. namespace MackstarSpoutAdminTestInterceptorValidators; use MackstarSpoutAdminInterceptorValidatorsUserValidator; class UserValidatorTest extends PHPUnit_Framework_TestCase { public function testErrorsWhenNoEmailIsPassedIn() { ... } }
  23. 23. FRONT-END INTEGRATION {{ resource | app://spout/resources/detail?type=photo-gallery&slug=spout-is-live | detail-blog.twig }}
  24. 24. PAGINATION {{ resource | app://spout/resources/index?type=blog | index-blog.twig | paged(1) }}
  25. 25. MENU {{ resource | app://spout/menus?slug=primary | primary-menu.twig }}
  26. 26. ARCHITECTURE
  27. 27. ANGULAR.JS ADMIN
  28. 28. BE YOUR OWN CONSUMER
  29. 29. COMPONENTS ▸ Routes ▸ Directives ▸ Controllers ▸ Services
  30. 30. 2 WAY DATA BINDING
  31. 31. TEMPLATE <input type="email" ng-model="login.email" required> CONTROLLER scope.$watch('login.email', function () { console.log("Login Email Changed To:" + scope.login.email); });
  32. 32. ROUTESANGULAR-UI ROUTER
  33. 33. app.config(['$stateProvider', function($stateProvider) { $stateProvider.state('login', { url: "/login", controller: 'LoginCtrl', templateUrl: '/js/templates/login/index.html', resolve: { authentication: ['Restangular', function (Restangular) { return Restangular.all('users/authenticate'); }] } }) .state('logout', { url: "/logout", controller: "LogoutCtrl", resolve: { authenticate: ['Restangular', function (Restangular) { return Restangular.all('users/authenticate'); }] } }); }]);
  34. 34. DIRECTIVES<SP-THUMBNAIL />
  35. 35. app.directive('spThumbnail', function (Restangular) { return { restrict: 'E', template: "<img src='/img/spinner.gif' ng-click='select()' />", scope: { media: "=media"}, replace: true, link: function(scope, element, attrs) { var src = '/uploads/media/' + scope.media.directory + '/140x140_' + scope.media.file, img = new Image(); function loadImage() { element[0].src = src; } img.src = src; img.onerror = function() { Restangular.all('media/resize').post( {media: scope.media, height: 140, width: 140} ).then(function() { loadImage(); }); }; ...
  36. 36. DEPENDENCY INJECTION
  37. 37. Declare var module = angular.module('restangular', []); module.provider('Restangular', function() {} Implement var app = angular.module('myApp', ['restangular']); app.controller('MyController', ['Restangular', function (Restangular) { Restangular.all('users/authenticate').get().then(function(auth) { ... }); }]);
  38. 38. TESTS
  39. 39. describe('Roles Directive', function () { var scope, $element; beforeEach(function () { module('Application'); angular.mock.inject( function ($rootScope, $compile) { var element = angular.element('<roles-selector></roles-selector>'); scope = $rootScope; scope.roles = [{"id": 1, "name": "Admin"},{"id": 2, "name": "Contributor"}]; $compile(element)(scope); scope.$digest(); $element = $(element); } ); }); it('should have a select menu', function () { expect($element.prop("tagName")).toEqual('SELECT'); expect($element.find("option").length).toBe(2); });
  40. 40. DEMO
  41. 41. STUFF TO DO ▸ Security ▸ DB/API Schema lock-down ▸ Create as composer component / Assetic ▸ More field types (locations/times/md etc) ▸ Blocks ▸ Get others input
  42. 42. GET INVOLVEDGITHUB.COM/MACKSTAR/SPOUT
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×