opencast admin ui - introduction to developing using angularjs
TRANSCRIPT
Admin UIIntroduction 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 conceptsbehind AngularJS
– for the open minded
Model-View-Whatever works for youTemplate
<!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 scopescope 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 navigationangular.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 = [];
– 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 navigationangular.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;…sh
ared
/con
trol
lers
/na
viga
tionC
ontr
olle
r.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 navigationangular.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); };
/sha
red/
cont
rolle
rs/
navi
gatio
nCon
trol
ler.j
sin
dex.
htm
lDefinition
Usage
– for the open minded
Services
Create custom object shareable through the App
Lazily instantiated Only instantiated when a component requires it
SingletonEach 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(); }]);
/sha
red/
serv
ices
/aut
hSer
vice
.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}) }); } } }); }]);
/sha
red/
reso
urce
s/ac
lRes
ourc
e.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>
/sha
red/
dire
ctiv
es/w
ithR
oleD
irec
tive.
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>
/sha
red/
filte
rs/t
rust
edR
esou
rceU
rlFi
lter
Filter definition
Usage
– 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 {}; },
– 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