Spout - Building a RESTful web app with Angular.js and BEAR.Sunday
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

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

on

  • 1,524 views

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.

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.

Statistics

Views

Total Views
1,524
Views on SlideShare
1,347
Embed Views
177

Actions

Likes
3
Downloads
5
Comments
0

5 Embeds 177

https://twitter.com 123
http://iteman.tumblr.com 28
http://www.slideee.com 24
http://www.tumblr.com 1
http://www.google.com 1

Accessibility

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

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

  • 1. BUILDING A RESTFUL WEB APP WITH ANGULAR.JS AND BEAR.SUNDAY
  • 2. RICHARD MCINTYRE @MACKSTAR
  • 3. GOAL RESTFUL CMS FRONT-END ANGULAR.JS BACK-END BEAR.SUNDAY
  • 4. WHY ANOTHER CMS? ▸ Building RESOURCES ▸ Power through simplicity ▸ Easy overrides with AOP ▸ Kill plugin culture ▸ Build re-usable libraries
  • 5. CONTINUED... ▸ Embed in any PHP project ▸ Testable ▸ Relationships between resources / Hypermedia ▸ Templating through TWIG
  • 6. BEAR.SUNDAY ▸ REST ▸ DI ▸ AOP
  • 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. EVERYTHING IS A RESOURCE Request: /api/resources/index?_start=1 Request Method: GET Status Code: 200 OK
  • 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. Request: /api/resources/types Request Method: POST Status Code: 200 OK
  • 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. Request: /api/menus/links?menu=primary Request Method: GET Status Code: 200 OK
  • 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. 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. 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. OVERRIDES THROUGH AOP ▸ Validation ▸ Permissions ▸ Template Switching ▸ Other custom events
  • 17. DEPENDENCIES INJECTED
  • 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. BIND USING MODULES
  • 20. private function installUserSessionAppender() { $session = $this->requestInjection('MackstarSpoutAdminInterceptorUsersSession'); $this->bindInterceptor( $this->matcher->subclassesOf('BEARResourceResourceObject'), $this->matcher->startsWith('onGet'), [$session] ); }
  • 21. TESTS
  • 22. namespace MackstarSpoutAdminTestInterceptorValidators; use MackstarSpoutAdminInterceptorValidatorsUserValidator; class UserValidatorTest extends PHPUnit_Framework_TestCase { public function testErrorsWhenNoEmailIsPassedIn() { ... } }
  • 23. FRONT-END INTEGRATION {{ resource | app://spout/resources/detail?type=photo-gallery&slug=spout-is-live | detail-blog.twig }}
  • 24. PAGINATION {{ resource | app://spout/resources/index?type=blog | index-blog.twig | paged(1) }}
  • 25. MENU {{ resource | app://spout/menus?slug=primary | primary-menu.twig }}
  • 26. ARCHITECTURE
  • 27. ANGULAR.JS ADMIN
  • 28. BE YOUR OWN CONSUMER
  • 29. COMPONENTS ▸ Routes ▸ Directives ▸ Controllers ▸ Services
  • 30. 2 WAY DATA BINDING
  • 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. ROUTESANGULAR-UI ROUTER
  • 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. DIRECTIVES<SP-THUMBNAIL />
  • 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. DEPENDENCY INJECTION
  • 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. TESTS
  • 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. DEMO
  • 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. GET INVOLVEDGITHUB.COM/MACKSTAR/SPOUT