KEMBAR78
Opencast Admin UI - Introduction to developing using AngularJS | PDF
Admin UI
Introduction to developing using AngularJS
Xavier Butty
Software Engineer
for the open minded
– for the open minded
This is not an extensive AngularJS course…
.. but a short introduction to this framework using
portions of the new Admin UI
Aimed to help developer to 

start extending the Admin UI
First name, Last name
Position
for the open mindedfor the open minded
01
Base concepts
behind AngularJS
– for the open minded
Model-View-Whatever works for you
Template
<!doctype html>
<html ng-app="myApp">
<head>
<title>My little AngularJS app</
title>
</head>
<body>
Hello {{ user }}!
<script src="angular.js"></script>
</body>
</html>
Model (Scope)
{
'user': 'Georges Bregy'
}
Controller
Directive
View (DOM)
Resource
Compilation
Service
– for the open minded
Data binding
Two-way binding
Use Markup {{ myVar }} to represent data in the
template
Rendered into a live view
Updating the view update the model…
…and vice-versa
– for the open minded
Data binding
<!doctype html>
<html ng-app="myApp">
<head>
<title>My little AngularJS app</title>
</head>
<body>
<h1>Welcome {{ user }}!</h1>
<ul>
<li ng-repeat="link in links">
<a ng-href="{{link.href}}">{{link.name}}</a>
</li>
<ul>
<select
ng-model="currentLang"
name="language"
ng-options="l.name for l in languages">
<option value="">-- choose language --</option>
</select>
</span>
<script src="angular.js"></script>
</body>
</html>
{
'user': 'Georges Bregy'
'links': [
{
'href': 'http://www.google.ch',
'name': 'Search'
},
{
'href': 'http://www.opencastcommunity.com',
'name': 'Opencast community'
}
],
'languages': [
{
'name': 'English'
},
{
'name': 'German'
}
],
'currentLang': {
'name': 'English'
};
}
– for the open minded
Scope
Model attached to a portion of a view
One root scope…
…with unlimited child scopes that 

inherit from the parent scope
– for the open minded
<!doctype html>
<html lang="en" ng-app="adminNg">
<head>…</head>
<body ng-cloak ng-controller="ApplicationCtrl" ng-click="bodyClicked()">
<header ng-controller="NavCtrl" class="primary-header">
<div class=“header-branding”>…</div>
<nav id="nav-dd-container" class="nav-dd-container">
<div class="nav-dd" id="lang-dd" old-admin-ng-dropdown="">
<div class="lang" ng-class="currentLanguageCode"></div>
<ul class="dropdown-ul">
<li>
<a href="#"
ng-repeat="language in availableLanguages | orderBy:'displayLanguage'"
ng-click="changeLanguage(language.code)">
<i class="lang {{ toLanguageClass(language) }}"></
i>{{ language.displayLanguage }}
</a>
</li>
</ul>
</div>
<div ng-if="documentationUrl" class="nav-dd help" id="help-dd" ng-click="toDoc()"></div>
<div class="nav-dd" id="user-dd" old-admin-ng-dropdown=“”>…</div>
</nav>
</header>
<footer id="main-footer" ng-controller="NavCtrl">
<div class="default-footer">
<div ng-if="version.version" class="meta">
Opencast Video Capture Software (v.{{ version.version }} - build:
{{ version.buildNumber }})
</div>
<div ng-if="feedbackUrl" class="feedback-btn" id=“feedback-btn”>…</div>
</div>
</footer>
</body>
</html>
Root scope
scope ApplicationCtrl
scope NavCtrl
x child scopes (ng-repeat)
scope NavCtrl
– for the open minded
Dependancy injection
Injection of defined component
Most common way: Inline Array Annotation
Alternative: Manual injection with $inject
// A controller for global page navigation
angular.module('adminNg.controllers')
.controller('NavCtrl', ['$scope', '$rootScope', '$location', '$window', '$resource', '$routeParams',
'Language',
function ($scope, $rootScope, $location, $window, $resource, $routeParams, Language) {
$scope.category = $routeParams.category || $rootScope.category;
$scope.availableLanguages = [];
First name, Last name
Position
for the open mindedfor the open minded
02
Main components
– for the open minded
Modules
Use to organise the components of an app in package
Simplify unit tests and make them faster. Only the required modules
can be loaded.
For the admin UI, we have modules for:
Main app (adminNG), Controllers, Services, Filters, Directives
Other module are external components
// A controller for global page navigation
angular.module('adminNg.controllers')
.controller('NavCtrl', ['$scope', '$rootScope', '$location', '$window', '$resource',
'$routeParams', 'Language',
function ($scope, $rootScope, $location, $window, $resource, $routeParams, Language) {
$scope.category = $routeParams.category || $rootScope.category;
…
shared/controllers/
navigationController.js
– for the open minded
Controllers
Set the initial state of a scope
Linked to a defined portion of a view
Allow to augment the scope
Override properties/methods inherit defined in parent controllers

In the new Admin UI:
/shared/controllers/**/*
/modules/*/controllers
– for the open minded
<header ng-controller="NavCtrl" class="primary-header">
<div class="header-branding">
<div class="text-logo">Opencast</div>
<div class="logo-tag">Beta</div>
</div>
<nav id="nav-dd-container" class="nav-dd-container">
<div class="nav-dd" id="lang-dd" old-admin-ng-dropdown="">
<div class="lang" ng-class="currentLanguageCode"></div>
<ul class="dropdown-ul">
<li><a href="#" ng-repeat="language in availableLanguages
| orderBy:'displayLanguage'" ng-click="changeLanguage(language.code)">
<i class="lang {{ toLanguageClass(language) }}"></i>
// A controller for global page navigation
angular.module('adminNg.controllers')
.controller('NavCtrl', ['$scope', '$rootScope', '$location', '$window', '$resource',
'$routeParams', 'Language',
function ($scope, $rootScope, $location, $window, $resource, $routeParams,
Language) {
$scope.category = $routeParams.category || $rootScope.category;
$scope.availableLanguages = [];
$scope.changeLanguage = function (key) {
Language.changeLanguage(key);
};
$scope.toLanguageClass = function (language) {
return Language.$convertLanguageToCode(language.code);
};
/shared/controllers/
navigationController.jsindex.html Definition
Usage
– for the open minded
Services
Create custom object shareable through the App
Lazily instantiated 

Only instantiated when a component requires it
Singleton

Each component requiring a service get a reference to the
same instance instantiated by the service factory.
In the new Admin UI : /shared/services/**/*
– for the open minded
angular.module('adminNg.services')
.factory('AuthService', ['IdentityResource', function (IdentityResource) {
var AuthService = function () {
var me = this,
isAdmin = false,
isUserLoaded = false,
callbacks = [],
identity,
isAuthorizedAs = function (role) {
if (angular.isUndefined(me.user.roles)) {
return false;
}
return isAdmin ||
(angular.isArray(me.user.roles) && me.user.roles.indexOf(role) > -1) ||
me.user.roles === role;
};
this.user = {};
this.loadUser = function () {
identity = IdentityResource.get();
identity.$promise.then(function (user) {
var adminRole = user.org.adminRole;
me.user = user;
isAdmin = angular.isDefined(adminRole) && user.roles.indexOf(adminRole) > -1;
isUserLoaded = true;
angular.forEach(callbacks, function (item) {
isAuthorizedAs(item.role) ? item.success() : item.error();
});
});
};
this.getUser = function () {
return identity;
};
this.loadUser();
};
return new AuthService();
}]);
/shared/services/authService.js
– for the open minded
Resources
Synchronisation with the RESTfull data source

Natively use JSON for the data transfer

Using the $http service

In the new Admin UI : /shared/resources/**/*
– for the open minded
angular.module('adminNg.resources')
.factory('AclResource', ['$resource', function ($resource) {
return $resource('/admin-ng/acl/:id', { id: '@id' }, {
get: {
method: 'GET',
transformResponse: function (data) {
return JSON.parse(data);
}
},
save: {
method: 'PUT',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
transformRequest: function (data) {
if (angular.isUndefined(data)) {
return data;
}
return $.param({
name : data.name,
acl : JSON.stringify({acl: data.acl})
});
}
}
});
}]);
/shared/resources/aclResource.js
AclResource.save({id: $scope.resourceId}, {
acl: {
ace: ace
},
name: $scope.metadata.name
}, function () {
Notifications.add('success', 'ACL_UPDATED', 'acl-form');
}, function () {
Notifications.add('error', 'ACL_NOT_SAVED', 'acl-form');
});
Resource definition
Usage
– for the open minded
Directives
Allow to create custom view component
Can be cascaded
Each directive instance has its own scope
Take care to not overload the stack!
Non-native element wrapped in directive have to be
managed carefully!
– for the open minded
angular.module('adminNg.directives')
.directive('withRole', ['AuthService', function (AuthService) {
return {
priority: 1000,
link: function ($scope, element, attr) {
element.addClass('hide');
AuthService.userIsAuthorizedAs(attr.withRole, function
() {
element.removeClass('hide');
}, function () {
element.remove();
});
}
};
}]);
<div class="btn-group">
<button data-open-modal="user-modal" data-action="add" class="add" with-
role="ROLE_UI_USERS_CREATE">
<i class="fa fa-plus"></i>
<span translate="USERS.ACTIONS.ADD_USER"><!--Add User--></span>
</button>
</div>
/shared/directives/withRoleDirective.js
Usage
Directive definition
– for the open minded
Filters
Function to be used within the expression
{{ value | filter:argument1 }}


Use it for input formatting 

- Date presentation

- Text translation

- Array presentation
Can also be used within the controller
– for the open minded
angular.module('adminNg.filters')
.filter('trusted', ['$sce', function ($sce) {
return function(url) {
return $sce.trustAsResourceUrl(url);
};
}]);
<video id="player" ng-if="controls === 'false'">
<source ng-repeat="preview in video.previews"
ng-src="{{ preview.uri | trusted }}"
type="video/mp4" />
Your browser does not support HTML5 video.
</video>
/shared/

filters/trustedResourceUrlFilter
Filter definition
Usage
First name, Last name
Position
for the open mindedfor the open minded
Jasmine
for unit testing
03
– for the open minded
How does a Jasmine specs look
like?
describe('Navigation controller', function () {
var $scope, $httpBackend, Language;
beforeEach(module('adminNg'));
beforeEach(module(function ($provide) {
var service = {
configureFromServer: function () {},
formatDate: function (val, date) { return date; },
formatTime: function (val, date) { return date; },
changeLanguage: function () {},
getLanguageCode: function () { return 'ja_JP'; },
getLanguage: function () { return {}; },
First name, Last name
Position
for the open mindedfor the open minded
04
Let’s work on it!
– for the open minded
The summary of "create event"
show empty element
Some items show in the summary of the new event
wizard are empty, and should therefore not be
displayed.
– for the open minded
Delete series and events
Add (x) icon in the events and series tableview to
allow deletion of single Events/Series
Questions?

thanks for your attention
Xavier Butty
Software Engineer
for the open minded

Opencast Admin UI - Introduction to developing using AngularJS

  • 1.
    Admin UI Introduction todeveloping using AngularJS Xavier Butty Software Engineer for the open minded
  • 2.
    – for theopen minded This is not an extensive AngularJS course… .. but a short introduction to this framework using portions of the new Admin UI Aimed to help developer to 
 start extending the Admin UI
  • 3.
    First name, Lastname Position for the open mindedfor the open minded 01 Base concepts behind AngularJS
  • 4.
    – for theopen minded Model-View-Whatever works for you Template <!doctype html> <html ng-app="myApp"> <head> <title>My little AngularJS app</ title> </head> <body> Hello {{ user }}! <script src="angular.js"></script> </body> </html> Model (Scope) { 'user': 'Georges Bregy' } Controller Directive View (DOM) Resource Compilation Service
  • 5.
    – for theopen minded Data binding Two-way binding Use Markup {{ myVar }} to represent data in the template Rendered into a live view Updating the view update the model… …and vice-versa
  • 6.
    – for theopen minded Data binding <!doctype html> <html ng-app="myApp"> <head> <title>My little AngularJS app</title> </head> <body> <h1>Welcome {{ user }}!</h1> <ul> <li ng-repeat="link in links"> <a ng-href="{{link.href}}">{{link.name}}</a> </li> <ul> <select ng-model="currentLang" name="language" ng-options="l.name for l in languages"> <option value="">-- choose language --</option> </select> </span> <script src="angular.js"></script> </body> </html> { 'user': 'Georges Bregy' 'links': [ { 'href': 'http://www.google.ch', 'name': 'Search' }, { 'href': 'http://www.opencastcommunity.com', 'name': 'Opencast community' } ], 'languages': [ { 'name': 'English' }, { 'name': 'German' } ], 'currentLang': { 'name': 'English' }; }
  • 7.
    – for theopen minded Scope Model attached to a portion of a view One root scope… …with unlimited child scopes that 
 inherit from the parent scope
  • 8.
    – for theopen minded <!doctype html> <html lang="en" ng-app="adminNg"> <head>…</head> <body ng-cloak ng-controller="ApplicationCtrl" ng-click="bodyClicked()"> <header ng-controller="NavCtrl" class="primary-header"> <div class=“header-branding”>…</div> <nav id="nav-dd-container" class="nav-dd-container"> <div class="nav-dd" id="lang-dd" old-admin-ng-dropdown=""> <div class="lang" ng-class="currentLanguageCode"></div> <ul class="dropdown-ul"> <li> <a href="#" ng-repeat="language in availableLanguages | orderBy:'displayLanguage'" ng-click="changeLanguage(language.code)"> <i class="lang {{ toLanguageClass(language) }}"></ i>{{ language.displayLanguage }} </a> </li> </ul> </div> <div ng-if="documentationUrl" class="nav-dd help" id="help-dd" ng-click="toDoc()"></div> <div class="nav-dd" id="user-dd" old-admin-ng-dropdown=“”>…</div> </nav> </header> <footer id="main-footer" ng-controller="NavCtrl"> <div class="default-footer"> <div ng-if="version.version" class="meta"> Opencast Video Capture Software (v.{{ version.version }} - build: {{ version.buildNumber }}) </div> <div ng-if="feedbackUrl" class="feedback-btn" id=“feedback-btn”>…</div> </div> </footer> </body> </html> Root scope scope ApplicationCtrl scope NavCtrl x child scopes (ng-repeat) scope NavCtrl
  • 9.
    – for theopen minded Dependancy injection Injection of defined component Most common way: Inline Array Annotation Alternative: Manual injection with $inject // A controller for global page navigation angular.module('adminNg.controllers') .controller('NavCtrl', ['$scope', '$rootScope', '$location', '$window', '$resource', '$routeParams', 'Language', function ($scope, $rootScope, $location, $window, $resource, $routeParams, Language) { $scope.category = $routeParams.category || $rootScope.category; $scope.availableLanguages = [];
  • 10.
    First name, Lastname Position for the open mindedfor the open minded 02 Main components
  • 11.
    – for theopen minded Modules Use to organise the components of an app in package Simplify unit tests and make them faster. Only the required modules can be loaded. For the admin UI, we have modules for: Main app (adminNG), Controllers, Services, Filters, Directives Other module are external components // A controller for global page navigation angular.module('adminNg.controllers') .controller('NavCtrl', ['$scope', '$rootScope', '$location', '$window', '$resource', '$routeParams', 'Language', function ($scope, $rootScope, $location, $window, $resource, $routeParams, Language) { $scope.category = $routeParams.category || $rootScope.category; … shared/controllers/ navigationController.js
  • 12.
    – for theopen minded Controllers Set the initial state of a scope Linked to a defined portion of a view Allow to augment the scope Override properties/methods inherit defined in parent controllers
 In the new Admin UI: /shared/controllers/**/* /modules/*/controllers
  • 13.
    – for theopen minded <header ng-controller="NavCtrl" class="primary-header"> <div class="header-branding"> <div class="text-logo">Opencast</div> <div class="logo-tag">Beta</div> </div> <nav id="nav-dd-container" class="nav-dd-container"> <div class="nav-dd" id="lang-dd" old-admin-ng-dropdown=""> <div class="lang" ng-class="currentLanguageCode"></div> <ul class="dropdown-ul"> <li><a href="#" ng-repeat="language in availableLanguages | orderBy:'displayLanguage'" ng-click="changeLanguage(language.code)"> <i class="lang {{ toLanguageClass(language) }}"></i> // A controller for global page navigation angular.module('adminNg.controllers') .controller('NavCtrl', ['$scope', '$rootScope', '$location', '$window', '$resource', '$routeParams', 'Language', function ($scope, $rootScope, $location, $window, $resource, $routeParams, Language) { $scope.category = $routeParams.category || $rootScope.category; $scope.availableLanguages = []; $scope.changeLanguage = function (key) { Language.changeLanguage(key); }; $scope.toLanguageClass = function (language) { return Language.$convertLanguageToCode(language.code); }; /shared/controllers/ navigationController.jsindex.html Definition Usage
  • 14.
    – for theopen minded Services Create custom object shareable through the App Lazily instantiated 
 Only instantiated when a component requires it Singleton
 Each component requiring a service get a reference to the same instance instantiated by the service factory. In the new Admin UI : /shared/services/**/*
  • 15.
    – for theopen minded angular.module('adminNg.services') .factory('AuthService', ['IdentityResource', function (IdentityResource) { var AuthService = function () { var me = this, isAdmin = false, isUserLoaded = false, callbacks = [], identity, isAuthorizedAs = function (role) { if (angular.isUndefined(me.user.roles)) { return false; } return isAdmin || (angular.isArray(me.user.roles) && me.user.roles.indexOf(role) > -1) || me.user.roles === role; }; this.user = {}; this.loadUser = function () { identity = IdentityResource.get(); identity.$promise.then(function (user) { var adminRole = user.org.adminRole; me.user = user; isAdmin = angular.isDefined(adminRole) && user.roles.indexOf(adminRole) > -1; isUserLoaded = true; angular.forEach(callbacks, function (item) { isAuthorizedAs(item.role) ? item.success() : item.error(); }); }); }; this.getUser = function () { return identity; }; this.loadUser(); }; return new AuthService(); }]); /shared/services/authService.js
  • 16.
    – for theopen minded Resources Synchronisation with the RESTfull data source
 Natively use JSON for the data transfer
 Using the $http service
 In the new Admin UI : /shared/resources/**/*
  • 17.
    – for theopen minded angular.module('adminNg.resources') .factory('AclResource', ['$resource', function ($resource) { return $resource('/admin-ng/acl/:id', { id: '@id' }, { get: { method: 'GET', transformResponse: function (data) { return JSON.parse(data); } }, save: { method: 'PUT', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, transformRequest: function (data) { if (angular.isUndefined(data)) { return data; } return $.param({ name : data.name, acl : JSON.stringify({acl: data.acl}) }); } } }); }]); /shared/resources/aclResource.js AclResource.save({id: $scope.resourceId}, { acl: { ace: ace }, name: $scope.metadata.name }, function () { Notifications.add('success', 'ACL_UPDATED', 'acl-form'); }, function () { Notifications.add('error', 'ACL_NOT_SAVED', 'acl-form'); }); Resource definition Usage
  • 18.
    – for theopen minded Directives Allow to create custom view component Can be cascaded Each directive instance has its own scope Take care to not overload the stack! Non-native element wrapped in directive have to be managed carefully!
  • 19.
    – for theopen minded angular.module('adminNg.directives') .directive('withRole', ['AuthService', function (AuthService) { return { priority: 1000, link: function ($scope, element, attr) { element.addClass('hide'); AuthService.userIsAuthorizedAs(attr.withRole, function () { element.removeClass('hide'); }, function () { element.remove(); }); } }; }]); <div class="btn-group"> <button data-open-modal="user-modal" data-action="add" class="add" with- role="ROLE_UI_USERS_CREATE"> <i class="fa fa-plus"></i> <span translate="USERS.ACTIONS.ADD_USER"><!--Add User--></span> </button> </div> /shared/directives/withRoleDirective.js Usage Directive definition
  • 20.
    – for theopen minded Filters Function to be used within the expression {{ value | filter:argument1 }} 
 Use it for input formatting 
 - Date presentation
 - Text translation
 - Array presentation Can also be used within the controller
  • 21.
    – for theopen minded angular.module('adminNg.filters') .filter('trusted', ['$sce', function ($sce) { return function(url) { return $sce.trustAsResourceUrl(url); }; }]); <video id="player" ng-if="controls === 'false'"> <source ng-repeat="preview in video.previews" ng-src="{{ preview.uri | trusted }}" type="video/mp4" /> Your browser does not support HTML5 video. </video> /shared/
 filters/trustedResourceUrlFilter Filter definition Usage
  • 22.
    First name, Lastname Position for the open mindedfor the open minded Jasmine for unit testing 03
  • 23.
    – for theopen minded How does a Jasmine specs look like? describe('Navigation controller', function () { var $scope, $httpBackend, Language; beforeEach(module('adminNg')); beforeEach(module(function ($provide) { var service = { configureFromServer: function () {}, formatDate: function (val, date) { return date; }, formatTime: function (val, date) { return date; }, changeLanguage: function () {}, getLanguageCode: function () { return 'ja_JP'; }, getLanguage: function () { return {}; },
  • 24.
    First name, Lastname Position for the open mindedfor the open minded 04 Let’s work on it!
  • 25.
    – for theopen minded The summary of "create event" show empty element Some items show in the summary of the new event wizard are empty, and should therefore not be displayed.
  • 26.
    – for theopen minded Delete series and events Add (x) icon in the events and series tableview to allow deletion of single Events/Series
  • 27.
    Questions?
 thanks for yourattention Xavier Butty Software Engineer for the open minded