Build A Drupal-free Theme with 8's REST API and Javascript



Build A Drupal-free Theme with 8's REST API and Javascript

0 1


dc-austin-dft


On Github rcaracaus / dc-austin-dft

Build A Drupal-free Theme with 8's REST API and Javascript

rcaracaus
themer @ PreviousNext
Disclaimer: I am not an AngularJS developer
  • I am a Drupal themer
  • At DrupalSouth in Wellington...
  • Important that we understand the basics of how these new technologies work

<DIVITIS>

This markup needs 4+ template overrides to remove/alter.
<div class="block-views-markup first">
    <div class="view">
        <div class="views-row views-row-first">
            <div class="node content">
                <div class="field field-recipe-title">
                    This is a Recipe's title
                </div>
                <div class="field field-recipe-body">
                    This is body content of recipe.
                </div>
            </div>
        ...
        
  • block.tpl
  • views--foo.tpl
  • node.tpl
  • field.tpl
  • Not even getting into preprocess_hooks or config.
  • Twig didn't fix our markup problem.
  • Dream markup will never happen because there are benefits to markup hell.

Lean markup, scalable css.

<article class="recipe">
    <div class="heading--primary">{{ recipe.title }}</div>
    <div class="bodyText--light">{{ recipe.body }}</div>
</article>

Web component:
<recipe></recipe>
  • What if there was a way to write markup and classes first and fetch data later?
  • Download Mothership!
  • OOCSS, BEM syntax. Maybe you don't want a bunch divs

  • Angular can help you work pull in data.
  • Backbone.js and Ember.js can also help you do this.
  • Templating and fetching data is only part of Angular's super human strength

Raw data please.

Web Services and REST

  • A web service exposes raw Drupal data at certain URLs (endpoints) of your Drupal site.
  • REST is an architectural pattern guiding our interaction with this data.
More on REST from linclark and _klausi
  • First exposure to API's and web services in Drupal
  • API and endpoints "in a box" with petitions distro.
  • Drupal 7 services contrib module

D8 Web Services Modules

Configure D8 REST

  • configure a rest endpoint
  • don't forget permissions

Postman!

  • Demo local postman (node/1) after setting up endpoint

Enable CORS

header('Access-Control-Allow-Origin: *');
D8 CORS contrib module
  • To share across domins you need CORS - Cross-origin resource sharing.
  • Chat briefly about headers.
  • Posting becomes more complicated... Pre-flight request.
Drupal 8 Server
/node/{nid}
/views-node-listing
Client-side Theme
Node Detail Page /node/{nid} endpoint/foo
Home page /views-listing
  • Let's see how these two sites will interact.
  • We need to move raw data from Drupal into our Angular theme.
  • Briefly show demo site. -- Fix Node pages.
  • But we need to learn a bit more about Angular..

Angular Directives list

Angular Directives

<!-- index.html -->
<html ng-app="clientTheme">
    <body>
        <div ng-contoller="FormCtrl">
            <a href="" ng-click="showForm = !showForm">Click to show form!</a>
            <form ng-show="showForm"></form>
        </div>

    <script src="js/angular.js"></script>
    <script src="js/app.js"></script>
    </body>
</html>
//app.js
var theme = angular.module('clientTheme', []);

theme.controller('FormCtrl', function($scope) {
    $scope.showForm = false;
});
  • index.html
  • app.js
  • angular.js
http://plnkr.co/edit/r1jXoOYfyRaWXE3k3KHO

$Scope

 [{
    "title": "Burrito",
    "body": "Very yum..."
}] 

Controller

Controller defines $scope.
// app.js
theme.controller('recipeCtrl',
function($scope) {
  $scope.recipe.title = 'Burrito';
  $scope.recipe.body = 'Very yum...';
});
            

$scope to Markup

ng-controller binds $scope to <div>
<!-- index.html -->
<div ng-controller="recipeCtrl">
    <b>{{ recipe.title }}</b><br>
    <em>{{ recipe.body }}</em>
</div>
{{ recipe.meNoUnderstand }}
            
BurritoVery yum...

Raw data to themed data.

Local Data File

recipes.json
    [{
        "id" : 1,
        "name": "Tacos de Guerrero"
    },
    {
        "id" : 2,
        "name": "Burritos de Jalisco"
    },
    ... more recipes
    ]
        

Service

$http.get('recipes.json')

$Scope

[{
    "id" : 1,
    "name": "Tacos de Guerrero"
},
{
    "id" : 2,
    "name": "Burritos de Jalisco"
},
... more recipes
]

Controller

recipesCtrl defines $scope.
// defined by recipeCtrl
$scope.recipes
    = $http.get('recipes.json');
          

Markup

ng-controller binds $scope to <div>
<div ng-controller="recipesCtrl">
    {{ recipes }}
</div>
            

ng-repeat

    <ul ng-controller="recipesCtrl">
        <li ng-repeat="recipe in recipes">
            {{recipe.id}} - {{recipe.name}}
        </li>
    </ul>
    ...

http://plnkr.co/edit/a7wVJBTraXKmmaObDJXk

Fetch raw data with a service


theme.factory('recipeService', function($http) {
    return {
        getRecipes: function(callback) {
            $http.get('recipes.json').success(callback);
        }
    }
});

  • Live templating Demo

Then controller uses service to fetch data

theme.controller('recipesCtrl', function($scope, recipeService) {
    recipeService.getRecipes(function(data) {
        $scope.recipe = data;
    });
});
        
http://plnkr.co/edit/a7wVJBTraXKmmaObDJXk
  • Added app alias for eaiser reference.

GET and REST

theme.config(function($httpProvider){
    $httpProvider.defaults.headers.common['Accept'] = 'application/hal+json';
    // delete $httpProvider.defaults.headers.common['X-Requested-With'];
});
        
  • The Restful approach is to have one resource, multiple formats.
  • We need "Accept" "application/hal+json" header on requests.
  • X-Requested-With depending on which version of angular you have.

GET a node from Drupal site

theme.factory('nodeService', function($http) {
    return {
        getNode: function(nid, callback) {
            $http.get('http://mydrupalsite.com/node/' + nid).success(callback);
        }
    }
}
        
http://plnkr.co/edit/2y2pnDs9mHalL5MJqmVe

Routing

  • Pathauto, pathalias, and menu system is usesless.. unless we use Rest to export config.
  • Single-page apps used to use hashbang URL's (anchors).
  • pushState was developed from HTML5's History API
  • I am pushing it trying to mimic Drupal's functionality.

Set up Angular routes

<head>
    <script src="js/angular.js"></script>
    <script src="js/angular-route.js"></script>
    <script src="app.js"></script>
</head>

<body>
    <a href="/">Home</a>
    <a href="/contact">Contact</a>
    <a href="/about">About</a>

    // ng-view is like $content in page.tpl
    <div ng-view></div>


</body>
        
  • Discuss dependencies and ng-route.. mention bower.

Set up Angular routes

var theme = angular.module('clientTheme', ['ngRoute']);
clientTheme.config(function($routeProvider) {
    $routeProvider
    .when('/', {
        templateUrl : 'templates/home.html',
        controller : 'mainController'
    })
    .when('/contact', {
        templateUrl : 'templates/contact.html',
        controller : 'contactController'
    });
    .when('/about-us', {
        templateUrl : 'templates/about.html',
        controller : 'aboutController'
    });
});
       
  • Routes are specified from a single location. Routing table.
  • Nodes are not assumed to be pages by default.
http://plnkr.co/edit/O61t3k4mguAkV354XZaK 

Arguments in Angular

  • Arguments are known as $routeparams in Angular
  • These can be specified as a wildcard, similar to Drupal's (%) sign.

Arguments with Angular via $routeparams

Node detail page:  node/1  vs.  node/2

clientTheme.config(function($routeProvider) {
    $routeProvider
    .when("/node/:nid", {
        templateUrl : "node.html" ,
        controller : "NodeCtrl"
    });
});

app.controller("NodeCtrl", function($scope, $routeParams) {
    $scope.nid = $routeParams.nid;
});
http://plnkr.co/edit/pJCzB8vYfN6W23MsHQNr
  • :nid is like % sign in Drupal views or tokens, serves as a wildcard route.
  • In our controller we can assign :nid in the URL to the route and prints out on page. See example.

Node detail pages with $routeparams

app.factory('dftService', function($http) {
    return {
        getNode: function(nid, callback) {
            $http.get('http://mydrupalsite.com/node/' + nid)
                                        .success(callback);
        }
    }
});
        
Controller
app.controller('NodeCtrl',
            function($scope, dftService, $routeParams) {
    dftService.getNode($routeParams.nid, function(data) {
        $scope.node = data;
    });
});
        
http://plnkr.co/edit/3ekxhyl9kZdVIwVBRT40
  • This is why we abstracted into a service, we might want to feed it an argument. NID
  • Now in our controller, since we know $routeparams we can feed that into our service.
  • Demo on live site.

GET a view

  • Views are powerful when combined with Web Services.
  • Live demo
  • Similar to recipes.json -- we can use ng-repeat

Filtering, sorting and searching data with Angular

ng-repeat="recipe in recipes | filter:{field_tags:searchTermText}"
  • Live demo
  • Using categories via taxonomy to create angular filters.

GET custom block onto homepage > DBLOGresource

  • Live demo if internet working
Posting, editing, and authentication
  • Just like GET, you can also POST, one of main advantages of DRUPAL 8 REST

Should we use a client-side theme?

  • I would not use Angular for a content heavy website.
  • The purpose was to show you some ideas around client-side templating.
  • Other MVC frameworks offer similar features. Backbone more stripped down but in core.

Routing

  • Originally used hashbang URLs until history API came out
  • SEO.. still shaky, but some good results. Prerender.io
  • Google analytics.

Learning curves

  • Using a client-side theme means no Drupal setups, no LAMP.
  • Angular was originally developed for designers. Now it is for developers. More curves.

Templating

  • Dev/themer analogy.
  • Drupal 8 div crazyness is necessary sometimes, it injects logic into theme.

Thanks!