logo

Gotta catch 'em all, with D8 and AngularJS

logos

We are

  • Drupal developers
  • Employed by Entity[One]
  • Pokémon enthusiasts
  • Two guys that bring their crazy ideas to life in Drupal environments
  • Follow our adventures on twitter!
    • @fonsvandamme
    • @robinwhatup

Pokémon locator

Pokémon locator

Pokémon locator

  • Drupal 8 (♥_♥)
  • Shows
    • Pokéstops
    • Active lures + Top 3 in neighbourhood
    • Gyms

Pokémon locator

  • One of the first maps around
  • First version online after 3 days
  • Incremental improvement based on user feedback
  • Server couldn't handle the traffic

Pokémon locator

Pokémon locator first 24h

Pokémon locator

  • After a while:
    • Players started using solutions that violate the TOS
    • 10k unique/day -> 300-500 unique/day
    • Solutions that violate the TOS come and go

But..

    We had fun and learned a lot!

    Pokémon hunt

Today's topics

  • Acquia Dev Desktop
  • Custom content entity
  • Guzzle
  • AngularJS & Templating
  • Leaflet.js & Google maps
  • Resources
  • Theming
  • Security

Acquia Dev Desktop

Acquia Dev Desktop

Acquia Dev Desktop

  • Local development
  • Comparable to wamp, mamp or xampp stack
  • Comes with:
    • Apache
    • PHP (5.x, 7.x versions)
    • MySQL
    • Xmail server (windows only)
    • Drush
  • Setup of new and existing D7 and D8 websites
  • Possibility to integrate with Acquia Cloud

More info: https://fonsvandamme.be/blog/local-drupal-development-acquia-dev-desktop

Custom entity

Custom entity - Drupal Console

  • CLI for Drupal based on Symfony console
  • Create modules, entities, forms, plugins...
            
                drupal generate:entity:content
            
            

Drupal Console

Console Entity

Custom entity

Entity class uses annotation

              
              /**
              * @ContentEntityType(
              *   id = "pokemon_marker",
              *   label = @Translation("Pokemon marker"),
              *   ...
              *   entity_keys = {
              *     "id" = "id",
              *     "label" = "name",
              *     "uuid" = "uuid",
              *     "uid" = "user_id",
              *     "langcode" = "langcode",
              *     "status" = "status",
              *   },
              *   links = {
              *     "canonical" = "/admin/structure/pokemon/marker/{pokemon_marker}",
              *     "add-form" = "/admin/structure/pokemon/marker/add",
              *     "edit-form" = "/admin/structure/pokemon/marker/{pokemon_marker}/edit",
              *     "delete-form" = "/admin/structure/pokemon/marker/{pokemon_marker}/delete",
              *     "collection" = "/admin/structure/pokemon/marker",
              *   },
              * )
              */
              
            

Custom entity

Define fields

              
                  $fields['type'] = BaseFieldDefinition::create('list_string')
                    ->setLabel(t('Type'))
                    ->setTranslatable(FALSE)
                    ->setRequired(TRUE)
                    ->setDefaultValue('gym')
                    ->setSettings(array(
                        'allowed_values' => array(
                        'gym' => 'Gym',
                        'pokestop' => 'Pokestop',
                        'lure' => 'Lure',
                      ),
                    ))
                    ->setRevisionable(FALSE)
                    ->setDisplayOptions('form', array(
                        'type' => 'list_string',
                        'weight' => 5,
                        'settings' => array(
                        'display_label' => TRUE,
                      ),
                  ));
              
            

Edits after module install: drush entity-updates

Custom entity - Marker types

  • Marker type factory defines all marker types
                    
                      public static function getAllMarkerTypes() {
                       return array(
                         'gym' => new MarkerGym(),
                         'pokestop' => new MarkerPokestop(),
                         'lure' => new MarkerLure(),
                         'gym-new' => new MarkerGymNew(),
                         'pokestop-new' => new MarkerPokestopNew(),
                         'lure-new' => new MarkerLureNew()
                       );
                      }
                    
                    
  • Create new class extending MarkerBase to add new marker
  • Better solution: Built in Plugin system
  • User friendly: also custom entity

Guzzle

Guzzle

  • PHP http client to send requests (GET, POST, PUT,...)
  • Comes with Drupal 8 core
  • Replaces drupal_http_request()

Guzzle

Inject http client into class by defining this in pokemon.services.yml

               
                 pokemon.manager.import
                   class: Drupal\pokemon\Manager\MarkerImportManager
                   arguments: ['@http_client']
               
            

MarkerImportManager constructor

               
                  public function __construct(Client $client) {
                     $this->httpClient = $client;
                  }
               
            

Guzzle

Execute request

               
                $response = $this->httpClient->get('uri', [
                  'headers'=> [
                    'Content-Type'=> 'application/json',
                    'additional-header'=> 'value'
                  ],
                  'auth'=>  ['username', 'password'],
                ]);
               
            

Process response

               
                $status_code = $reponse->getStatusCode();

                if ($status_code == 200) {
                  $response_contents = $reponse->getBody()->getContents();
                  // Do something with response.
                  ...  
                }
               
            

AngularJS & Templating

AngularJS

AngularJS

  • Decouple DOM manipulation from application logic
  • Decouple the client side of an application from the server side

Templating

  • Drupal 8 core templating (twig)
  • Drupal 8 core caching
  • Custom (nested) render arrays for complete control

=> Semi-decoupled application

Templating - Structure

  • Page
    • Sidebar
      • Branding
    • Content
      • Map
      • Social media

Libraries

  • Multiple libraries dependent on each other
  • Easy manageable
              
              pokemon.leaflet:
                version: 1.x
                css:
                    theme:
                    ... leaflet dependent css    
                js:
                  ... leaflet library and plugins
              pokemon.angular:
                ... angular libray files and custom directives
              pokemon.angular.app:
                ...
                dependencies:
                  - pokemon/pokemon.angular
              pokemon.angular.leaflet:
                ...
                dependencies:
                  - pokemon/pokemon.leaflet
                  - core/underscore
                  - core/drupal.debounce
                  - pokemon/pokemon.angular.app
              
            

Render array

Readable and compact bacause of

  • Nested render arrays
  • Dependable libraries
              
              return array(
                '#theme' => 'pokemon_page',
                '#content' => $content,
                '#sidebar' => $sidebar,
                '#attached' => array(
                  'library' => array(
                    'pokemon/pokemon.angular.leaflet',
                  ),
                )
              );
              
            

AngularJS - structure

Angular structure

AngularJS - Binding

HTML:

              
                  
              
            

Javascript:

              
                if(checkStuff()){
                  $scope.mapFactory.hasToZoomIn = false;
                  $scope.buttonText = "You can click it now";
                }
              
              

AngularJS - Binding

HTML:

              
                  Pokéstop
              
            

Javascript:

              
                // Adds pokestop marker to map.
                $scope.addPokestop = function () {
                 // Do fancy things.
                };
              
            

AngularJS - Binding

HTML:

              
        {{ 'Reset location' | t }}
              
            

AngularJS - Leaflet.js Binding

HTML:

              
  
              
            

Javascript:

              
                angular.extend($scope, {
                  defaults: {
                    maxZoom: 18, minZoom: 8
                  },
                  center: {
                    lat: 51.038, lng: 3.721
                  },
                  layers: {
                  ...
                  },
                  markers {
                  ...
                  }
                });
              
              

AngularJS and Twig

  • {{ my_variable}} = reserved char sequence in Twig
  • {{ my_variable}} also used in angularJS

Conflict, Solutions?

  • In angularJS, edit start and end interpolation tags, eg "{[{"
  • Use {{ '{{my_variable}}' }} notation in twig

Leaftlet.js and Google maps

Leaflet

Leaftlet.js and Google maps

  • An open-source JavaScript library for mobile-friendly interactive map
  • A lot of community built plugins
  • Well-documented API
  • Map providers (Google, OSM, ...)

Leaftlet.js and Google maps

Used an angular directive to integrate Leaflet.js and AngularJS

               
                // Init leaflet map.
                angular.extend($scope, {
                  defaults: {
                    maxZoom: 18,
                    minZoom: 8
                  },
                  center: {
                    lat: 51.038,
                    lng: 3.721,
                    zoom: 17,
                    autoDiscover: true
                  },
                  events: {
                    marker: {
                      enable: ['dragend'],
                      logic: 'emit'
                    },
                    map: {
                      enable: ['zoomend', 'move'],
                      logic: 'emit'
                    }
                  },
                  ...
                });
                 
            

Leaftlet.js and Google maps

  • Google API key
  • Google Maps library
  • Google Maps Leaflet plugin
              
              // Init leaflet map.
              angular.extend($scope, {
                ...
                layers: {
                  baselayers: {
                    googleRoadmap: {
                      name: 'Google Map',
                      layerType: 'ROADMAP',
                      type: 'google'
                    }
                  },
                ...    
                }
              });
              
            

Leaftlet.js and Markercluster

  • Contributed plugin
  • Useful when there are a lot of markers on a small area
  • Configurable and integrated in angular directive
              
              // Init leaflet map.
              angular.extend($scope, {
                layers: {
                  ...
                  overlays: {
                    ourAwesomeMarkers: {
                      name: "Markers",
                      type: "markercluster",
                      visible: true,
                      layerOptions: {
                        disableClusteringAtZoom: 18
                      }
                    }
                  }
                }
              });
              
            

Leaftlet.js and Markers

  • Load all the things on page load! (What were we thinking?)
  • Load markers in viewport
              
              // On zoom end, load markers
              $scope.$on('leafletDirectiveMap.pokemon-map.zoomend', _.debounce(function (event, args) {
                $scope.loadMarkers();
              }, 500));

              // On move load markers
              $scope.$on('leafletDirectiveMap.pokemon-map.move', _.debounce(function (event, args) {
                $scope.loadMarkers();
              }, 500));
              
            

Leaftlet.js and Markers

Even better: Use a TileLayer

  • Maps are made up of many small, square images called tiles
  • Tiles are typically 256×256 pixels and are placed side-by-side in order to create the illusion of a very large seamless image
  • Each tile has a
    • z coordinate describing its zoom level
    • x and y coordinates describing its position within a square grid for that zoom level
    • z/x/y notation

Leaftlet.js and Markers

Why tiles?

  • Tiled maps cache efficiently
  • Tiled maps load progressively

Let leaflet do all th heavy lifting

Source: https://www.mapbox.com/help/how-web-maps-work/

Resources

REST

Resources - The wrong way

  1. Make a controller with JSON response... :(
  2. Google "Drupal 8 custom resource" to get more info
  3. Find out Wim Leers has strong arguments against this approach Source: https://www.chapterthree.com/blog/custom-restful-api-drupal-8
    • Custom paths per method
    • No access control
    • No CSRF protection
    • Not using the API that the REST module in Drupal 8 provides
  4. Start over again

Resources - The right way

Possible to create a resource with Drupal Console

Console Resource

Resources - The right way

Resource class uses annotation

               
                 /**
                  * @RestResource(
                  *   id = "pokemon_marker",
                  *   label = @Translation("Pokemon marker"),
                  *   uri_paths = {
                  *     "canonical" = "/api/marker/{z}/{x}/{y}"
                  *   }
                  * )
                  */
                 class PokemonMarker extends ResourceBase {
                    ...
                 }
              
            

Resources - The right way

Implement get() function in resource class

              
              /**
               * Responds to GET requests.
               */
              public function get($z, $x, $y) {
                if ($something = $this->somethingWrong()) {
                 // Return 403. 
                 throw new AccessDeniedHttpException($something);
                }
                
                // Fetch all markers within tile.
                $markers = $this->manager->getMarkers($z, $x, $y);

                return new ResourceResponse($markers);
              }
              
            

Caching problems for anonymous users: used "KillSwitch" to disable cache.

Resources - Rest UI

Rest UI

Resources - Permissions

Permissions

Theming

SSL thumb

Theming - Custom theme

Based on Classy

poké poké

Theming - Setup info file

pokepoke.info.yml

                    
                        name: pokepoke
                        type: theme
                        description: 'Subtheme for Poke Poke!'
                        core: 8.x
                        package: custom
                        base theme: classy
                        libraries:
                            - pokepoke/base
                    
            

Theming - Setup libraries file

pokepoke.libraries.yml

              
                  base:
                      version: 1.0
                      css:
                          theme:
                              css/fontawesome/font-awesome.css: {}
                              css/pokemon.css: {}
                      js:
                          js/bootstrap.js: {}
                          js/jquery.touchSwipe.min.js: {}
                          js/pokepoke.js: {}
                      dependencies:
                          - core/jquery
                          - core/jquery.once
                          - core/drupal
                          - core/drupalSettings
              
            

Theming - Use sass

config.rb

              
                  require 'susy'
                  require 'breakpoint'
                  require 'sass-globbing'

                  http_path = "/"
                  css_dir = "css"
                  sass_dir = "scss"
                  images_dir = "img"
                  javascripts_dir = "js"
                  output_style = :expanded
                  relative_assets = true
                  line_comments = true
              
            

Theming - Use sass

Gemlock file

              
                  source 'https://rubygems.org'

                  gem 'compass'
                  gem "breakpoint", "~>2.4.0"
                  gem 'sass-rails', '~> 5.0.0'
                  gem 'susy'
                  gem 'compass-rails', '~> 2.0.0'
                  gem 'font-awesome-sass'
                  gem 'sass-globbing'
              
            

Theming - Use sass

Compile all the things

There are probably better ways to set this all up with Grunt or Gulp?!

Theming - Result

Compile all the things

The mobile friendly result after hours of headbanging and using the mighty Google

Security

SSL thumb

Security

CPanel

CPanel

Security

Certificate

Security

SSL overview

Security

Edit your .htaccess

            
              # Force SSL 
              RewriteCond %{HTTPS} off [OR] 
              RewriteCond %{HTTP_HOST} ^www.my-website\.be* 
              RewriteRule ^(.*)$ https://my-website.be/$1 [L,R=301]
            
            

More info: https://fonsvandamme.be/blog/using-ssl-drupal-website

That's all folks!

Thank you for your attention, are there any questions?

Clap clap
logos