Client-side Rendering with AngularJS

Preview:

DESCRIPTION

The OpenStack Horizon project provides a web-based User Interface to OpenStack services. It is constructed in two parts: (1) a core set of libraries for implementing a Dashboard; (2) the dashboard implementation that uses the core set of libraries. Horizon uses python django — server side technology Django is a wonderful framework, but a little dated. Pre-dates the rise in client-side and single page applications. Javascript is used for enhancing the user experience In the time since Horizon was first architected, there have been major advances in the design, and best practices for web applications. In particular, the use of more sophisticated and robust client-side javascript frameworks like BackboneJS, AngularJS, MeteorJS, have come to the fore. These applications provide a much more responsive user experience, much cleaner separation between the client and server, enable configuration driven interfaces, and facilitate more modular testing. This in turn, results in shorter development cycles, more testable software, and above all, a better user experience. In this presentation, we share some of our recent work in re-architecting parts of Horizon to take advantage of these new technologies. We discuss some of the technologies we use, our application architecture, and some of the pitfalls to avoid.

Citation preview

Client-side Rendering with AngularJS

OpenStack Summit, Paris

David Lapsley

@devlaps, dlapsley@cisco.com

November 3, 2014

Client-side rendering in production

Client-side Rendering

“Full” dataset search

Cache up to 1K records client-side“Full” pagination

Real-time DataUpdates every 5s

Increased platform visibility

Every node instrumented

Historical Metrics

Up to 1 year of data

Increased platform visibility Every node instrumented

Convenient access

OpenStack Horizon

Architecture

Django Stack

Horizon Stack

Horizon Stack Extended

AngularJS

Core concepts

● Model View Controller framework

● Client-side templates

● Data binding

● Dependency injection

Hello Worldindex.html

<html ng-app>

<head>

<script src="angular.js"></script>

<script src="controllers.js"></script>

</head>

<body>

<div ng-controller='HelloController'>

<p>{{greeting.text}}, World</p>

<button ng-click="action()">Alert</button>

</div>

</body>

</html>

controllers.js

function HelloController($scope) {

$scope.greeting = { text: 'Hello' };

$scope.action = function() {

alert('Action!');

};

}

AngularJS

By: Brad Green; Shyam Seshadri

Publisher: O'Reilly Media, Inc.

Pub. Date: April 23, 2013

Hello World

Hello World

Adding a new Horizon feature

with AngularJS

Directory Structureopenstacksummit/

hypervisors/

__init__.py

panel.py

urls.py

views.py

tables.py

tests.py

templates/openstacksummit/hypervisors/

index.html

static/openstacksummit/hypervisors/js/

hypervisors-controller.js

rest/nova/

__init__.py

hypervisor.py

instance.py

REST Resource: hypervisors.pyclass HypervisorResource(resource.BaseNovaResource):

pk = fields.CharField(attribute="pk", _("Primary Key"), hidden=True)

hypervisor_hostname = fields.CharField(attribute='hypervisor_hostname',

sortable=True,

searchable=True)

actions = fields.ActionsField(attribute='actions',

actions=[HypervisorViewLiveStats,

HypervisorEnableAction,

HypervisorDisableAction],

title=_("Actions"),

sortable=True)

class Meta:

authorization = auth.RestAuthorization()

list_allowed_methods = ['get']

resource_name = '^hypervisor'

field_order = ['pk', 'hypervisor_hostname', 'hypervisor_type', 'vcpus',

'vcpus_used', 'memory_mb', 'memory_mb_used',

'running_vms', 'state', 'status', 'actions']

Controller: hypervisor-controller.js

horizonApp.controller('TableController',

function($scope, $http) {

$scope.headers = headers;

$scope.title = title;

$http.get('/rest/api/v1/nova/instance/').success(

function(data, status, headers, config) {

$scope.instances = transform(data.objects);

});

});

horizonApp.controller('ActionDropdownController',

function($scope) {

$scope.status = {

isopen: false

};

$scope.toggleDropdown = function($event) {

$event.preventDefault();

$event.stopPropagation();

$scope.status.isopen = !$scope.status.isopen;

};

$scope.action = function(action, id) {

// Perform action.

};

});

View: index.html

{% extends 'base.html' %}

{% load i18n horizon humanize sizeformat %}

{% block title %}{% trans 'Hypervisors' %}{% endblock %}

{% block page_header %}

{% include 'horizon/common/_page_header.html' with title=_('All

Hypervisors') %}

{% endblock page_header %}

{% block main %}

View: index.html

<div ng-controller="TableController">

<table class="...">

<thead>

<tr class="...">

<th class="...">

<h3 class="...">{$ title $}</h3>

</th>

</tr>

<tr class="...">

<th class="..." ng-repeat='header in headers'>

<div class="...">{$ header.name $}</div>

</th>

</tr>

</thead>

View: index.html<tr ng-repeat="instance in instances">

<td ng-repeat="datum in instance.data">{$ datum $}</td>

<td class="...">

<div ng-controller="ActionDropdownController">

<div class="..." dropdown>

<button class="..."

ng-click="action(instance.actions[0], instance.name)">

{$ instance.actions[0].verbose_name $}

</button>

...

<div class="...">

<li class="..." ng-repeat="action in instance.actions">

<a href="#" class="..."

ng-click="$parent.action(action,parent.instance.name)">

{$ action.verbose_name $}

</a>

</li>

</ul>

</div>

</td>

</tr>

</table>

Client-side Rendering

“Full” dataset search

Cache up to 1K records client-side“Full” pagination

Why?

Advantages

● Clean split between server and client side

● Significantly cleaner, terser, easier to

understand client-side code

● Significant easier to improve UX

● Client- and server-side code can be

developed and tested independently

● Faster feature velocity

Better UX

Faster!

Thank You

David Lapsley

@devlaps, david.lapsley@metacloud.com

If this sounds interesting…

http://jobs.metacloud.com

We are hiring!

Recommended