Inhoud

  • Wat is AngularJS
  • Features
  • Ontwikkelomgeving

Superheroic Javascript MVW framework

Angularjs is what HTML would have been, had it been designed for building web-apps

AngularJS is

framework, geen library

Single page applications

  • Begonnen als open source project van Miško Hevery
  • Opgepakt door Google
  • Honderden contributors versie 1.2
  • Weddenschap: project herbouwen in 2 weken (3)
  • 17000 regels code -> 1000 regels

Text

En meer dan 200 anderen: https://builtwith.angularjs.org/

Gebruikt door

Filosofie

  • Declaratief in html
  • DOM manipulatie != application logic
  • Databinding
  • Modulair
  • Dependency injection
  • Testbaar
  • Plain javascript

Wat kan je ermee

User input

  • inlezen
  • valideren (valid/pristine/dirty)
  • structureren van input data (model)

Views renderen

  • samenstellen van sub-views
  • data injectie
  • aanpassen wanneer data verandert

Code structuur

  • separation of concerns
  • testability

Deep linking

  • URL's versturen naar anderen
  • Pagina's herlaadbaar

Server communicatie

  • Ajax/REST (promises)            

sep 2013

nu

JQuery

Forget everything you know about JQuery

Features

Data binding

<div ng-app>
      <h3>AngularJS data binding</h3>
      <input type="text" ng-model="inputTekst"/><br>
      <span>{{inputTekst}}</span>
</div>

( Bidirectioneel )

function resetInput(){
    $scope.inputText = '';
}

Applicatie structuur

Dependency Injection

angular.module('wiseSelectietool',['wiseMessages', ngRoute])

.controller('ListCtrl', 
    function ($scope, SelectionService, $location) {
		
        $scope.previewReady = true;

        $scope.selections = SelectionService.getSelections();

    }
);
.controller('ListCtrl', ['$scope', 'SelectionService', '$location', 
    function ($scope, SelectionService, $location) {
		
        $scope.previewReady = true;

        $scope.selections = SelectionService.getSelections();

    }
]);

Routing

Single Page Application

<body>
    ...
    <div ng-view></div>
    ...
</body>
angular.module('myAppl', ['ngRoute'])

	
.config(function ($routeProvider) {
	$routeProvider
		.when('/', {
			controller : 'view0Ctrl',
			templateUrl: 'view0.html'
		})
		.when('/view1', {
			controller : 'view1Ctrl',
			templateUrl: 'view1.html'
		})
		.when('/view2', {
			controller : 'view2Ctrl',
			templateUrl: 'view2.html'
		})
		.otherwise({
			redirectTo: '/'
		});
});

Directives

  • Marker op een DOM element om gedrag te koppelen
  • <wise-messages />
  • <div wise-messages>...</div>
  • <section class="wise-messages">...
angular.module('myApp')
    .directive('wise-messages', function () {
    return {
        restrict: 'EA', //E = element, A = attribute, C = class, M = comment         
        scope: {
            //@ reads the attribute value, = provides two-way binding, & works with functions
            title: '@'         },
        template: '<div>{{ myVal }}</div>',
        templateUrl: 'mytemplate.html',
        controller: controllerFunction, //Embed a custom controller in the directive
        link: function ($scope, element, attrs) { } //DOM manipulation
    }
});

Directive voorbeeld

<ul>
    <to-do-item item="item" ng-repeat="item in todos" /> 
</ul>
.directive('toDoItem',function(){
    var toDoItem={
        restrict: 'AE',
        replace : true,
        template : '<li><input type="checkbox" ng-model="item.done"/>' +
        '<span ng-class="{done: item.done}">{{item.tekst}}</span></li>'    
    }
    return toDoItem;
})

DOM manipulation voorbeeld

app.directive('klikker', function () {
    return {
        link: function ($scope, element, attrs) {
            element.bind('click', function () {
                element.html('Je klikte me!');
            });
            element.bind('mouseenter', function () {
                element.css('background-color', 'yellow');
            });
            element.bind('mouseleave', function () {
                element.css('background-color', 'white');
            });
        }
    };
});
<div klikker>Klik mij!</div>

Filter

myApp.filter("labelAttrFilter", function propertyTypeFilter() {

    return function (items, searchText) {
               var arrayToReturn = [];
               if (items instanceof Array) {
                   if (searchText || '') {
                       searchText = searchText.toLowerCase();
                       for (var i = 0; i < items.length; i++) {
                           if (items[i].label.toLowerCase().indexOf(searchText) != -1) {
                               arrayToReturn.push(items[i]);
                           }
                       }
                   }
               }
        return arrayToReturn;
    };
});

Default filtering op alle attributen van object

<ul>
    <li ng-repeat="item in mozaictypes | orderBy: 'label' | filter:labelAttrFilter">{{item.label}}</li>
</ul>
[
    {value: "A01", label: "Studentenvrijheid"},
    {value: "A02", label: "Online Starters"},
    {value: "A03", label: "Digitale Singles"}
]

Let nu op:

Ontwikkel omgeving

  • npm (opzetten ontwikkel omgeving)
  • Bower (package/dependency manager)
  • Grunt (+ plugins, task runner) 
  • Karma / Jasmine (unit tests)
  • Protractor (end-to-end tests (todo)) 

Node Package Manager

> npm install

Ontwikkeltools installeren

{
  "name": "wise-selectie-tool",
  "version": "0.0.1",
  "scripts": {
    "postinstall": "node ./node_modules/bower/bin/bower install"
  },
  "devDependencies": {
    "matchdep": "~0.1.2",
    "grunt": "~0.4.1",
    "grunt-contrib-connect": "~0.5.0",
    "grunt-contrib-jshint": "~0.6.0",
    "grunt-contrib-clean": "^0.6.0",
    "grunt-contrib-watch": "~0.5.3",
    "grunt-contrib-concat": "~0.3.0",
    "grunt-contrib-uglify": "^0.6.0",
    "grunt-contrib-copy": "^0.6.0",
    "grunt-contrib-imagemin": "^0.8.1",
    "grunt-contrib-cssmin": "^0.10.0",
    "grunt-contrib-htmlmin": "^0.3.0",
    "grunt-autoprefixer": "^1.0.1",
    "grunt-svgmin": "^1.0.0",
    "grunt-wiredep": "^1.9.0",
    "grunt-usemin": "^2.4.0",
    "grunt-concurrent": "^1.0.0",
    "grunt-peg": "^1.5.0",
    "grunt-shell": "~0.4.0",
    "grunt-karma": "~0.6.2",
    "grunt-open": "~0.2.2",
    "grunt-protractor-runner": "~0.1.6",
    "grunt-shell-spawn": "~0.3.0",
    "bower": "^1.2.8",
    "karma-script-launcher": "~0.1.0",
    "karma-firefox-launcher": "~0.1.0",
    "karma-chrome-launcher": "~0.1.0",
    "karma-html2js-preprocessor": "~0.1.0",
    "karma-jasmine": "~0.1.3",
    "karma-requirejs": "~0.2.0",
    "karma-coffee-preprocessor": "~0.1.0",
    "karma-phantomjs-launcher": "~0.1.0",
    "karma-coverage": "^0.2.6",
    "karma": "~0.10.2",
    "protractor": "~0.14.0",
    "time-grunt": "^1.0.0"
  }
}

package.json

{
  "name": "wise-selectie-tool",
  "version": "0.0.1",
  "dependencies": {
    "jquery": "~2.1.1",
    "jquery-ui": "~1.11.1",
    "angular": "~1.2.16",
    "angular-route": "~1.2.16",
    "angular-animate": "~1.2.16",
    "angular-mocks": "~1.2.16",
    "angular-resource": "~1.2.16",
    "angular-dragdrop": "~1.0.8",
    "components-font-awesome": "~4.2.0",
    "angular-sanitize": "~1.2.16",
    "angular-cookies": "~1.2.16",
    "angular-ui-slider": "0.0.2"
  },
  "resolutions": {
    "angular": "~1.2.16",
    "jquery-ui": "~1.11.1"
  }
}

bower.json

<!-- bower:js -->
    <script src="../bower_components/jquery/dist/jquery.js"></script>
    <script src="../bower_components/jquery-ui/jquery-ui.js"></script>
    <script src="../bower_components/angular/angular.js"></script>
    <script src="../bower_components/angular-route/angular-route.js"></script>
    <script src="../bower_components/angular-animate/angular-animate.js"></script>
    <script src="../bower_components/angular-mocks/angular-mocks.js"></script>
    <script src="../bower_components/angular-resource/angular-resource.js"></script>
    <script src="../bower_components/angular-dragdrop/src/angular-dragdrop.js"></script>
    <script src="../bower_components/angular-sanitize/angular-sanitize.js"></script>
    <script src="../bower_components/angular-cookies/angular-cookies.js"></script>
    <script src="../bower_components/angular-ui-slider/src/slider.js"></script>
<!-- endbower -->

i.c.m. grunt-wiredep:

inject components in HTML 

> bower install

Gruntfile.js

Plugins

module.exports = function(grunt) {
	"use strict";
  require('time-grunt')(grunt);

 ...

  //autotest and watch tests
  grunt.registerTask('autotest', ['karma:unit_auto']);
  grunt.registerTask('autotest:unit', ['karma:unit_auto']);
  grunt.registerTask('autotest:e2e', 
    ['connect:testserver',
     'shell:selenium',
     'watch:protractor']);

  //coverage testing
  grunt.registerTask('test:coverage', ['karma:unit_coverage']);
  grunt.registerTask('coverage', 
    ['karma:unit_coverage',
     'open:coverage',
     'connect:coverage']);

  //installation-related
  grunt.registerTask('install', ['update','shell:protractor_install']);
  grunt.registerTask('update', ['shell:npm_install', 'concat']);

  //server daemon
  grunt.registerTask('serve', ['connect:webserver']);

  grunt.registerTask('build', [
		'test:unit',
		'clean:dist',
		'wiredep',          
		'useminPrepare',
		'concurrent:dist',
		'autoprefixer',
		'concat',
		'cssmin',
		'uglify',
		'copy:dist',
                'rev',
		'usemin',
		'htmlmin'
	]);
};
  • clean
  • concat
  • cssmin
  • htmlmin
  • uglify
  • wiredep
  • peg
  • karma
  • watch
  • ...
module.exports = function(config) {
  config.set({
    files : [
	  // AngularJS
          'bower_components/angular/angular.js',
          'bower_components/angular-route/angular-route.js',
          'bower_components/angular-resource/angular-resource.js',
          'bower_components/angular-sanitize/angular-sanitize.js',
          'bower_components/angular-ui-slider/src/slider.js',
          'bower_components/angular-mocks/angular-mocks.js',
          'bower_components/angular-dragdrop/src/angular-dragdrop.js',

	  // wellicht later als bower component?
	  './../AngularJS/target/wise-auth/wise-auth.js',

	  // de applicatie
	  'app/scripts/app.js',
	  'app/scripts/util/map.js',
	  'app/scripts/util/MD5.js',
	  'app/scripts/cql/normalize.js',
	  'app/scripts/cql/cql-parser.js',
	  'app/scripts/wise-messages/wise-messages-module.js',
	  'app/scripts/wise-messages/wise-messages-service.js',
	  'app/scripts/wise-messages/wise-messages-directive.js',
	  // TODO: de wise modules t.b.v. tests aanbieden op een stabiel systeem
	  'http://hkadev01.bicat.com/rsrc/AngularJS/components/wise-modal/wise-modal-module.js',
	  'http://hkadev01.bicat.com/rsrc/AngularJS/components/wise-modal/wise-modal-service.js',
	  'http://hkadev01.bicat.com/rsrc/AngularJS/components/wise-modal/wise-modal-directive.js',
	  'app/scripts/app.js',
	  'app/scripts/selection/selectionResource.js',
	  'app/scripts/selection/selectionService.js',
	  'app/scripts/rubrieken/rubriekenService.js',
	  'app/scripts/results/resultService.js',
	  'app/scripts/nodes/queryBuilder.js',
	  'app/scripts/rubrieken/rubriekEditor.js',
	  'app/scripts/rubrieken/abstractRubriekCtrl.js',
	  'app/scripts/rubrieken/rubriekSexeCtrl.js',
	  'app/scripts/rubrieken/rubriekLeeftijdCtrl.js',
	  'app/scripts/nodes/leaf.js',
	  'app/scripts/nodes/node.js',
	  'app/scripts/rest.js',
	  'app/scripts/auth/loginCtrl.js',
          
          // de tests
	  './test/unit/listCtrlSpec.js',
	  './test/unit/buildCtrlSpec.js'
	  './test/unit/selectionServiceSpec.js'
    ],
    basePath: '../',
    frameworks: ['jasmine'],
    reporters: ['progress'],
    browsers: ['PhantomJS'],
	  plugins: [
		  'karma-jasmine',
		  'karma-coverage',
		  'karma-phantomjs-launcher',
		  'karma-chrome-launcher',
	  ],
    autoWatch: false,
    singleRun: false,
    colors: true
  });
};

karma-unit.conf.js

"use strict";

describe('SelectionService', function() {
    beforeEach(angular.mock.module('wiseSelectietool'));

    var mockSelectionResource;
    var $httpBackend;

    beforeEach(function() {
	angular.mock.inject(function($injector){
            $httpBackend = $injector.get('$httpBackend');
	    mockSelectionResource = $injector.get('Selection');
	})
    });

    describe('Selection.get ', function() {
	it('should get all selections', inject(function (Selection) {
	    $httpBackend.expectGET("/restapi/service/selection").respond({
	        data: [
		    {"id": 17, "name": "Mijn selectie", "description": "", 
                     "cqlQuery": "((T.leeftijd = \"D\") and (E.plaatsing <> \"YLE|YLI|YSP|YTS|YYA\"))", "owner": 102},
		    {"id": 16, "name": "testvw3", "description": "", "cqlQuery": "((K.bieb_ab_nr = \"42\"))", "owner": 51624},
		    {"id": 15, "name": "Mijn selectie", "description": "", "cqlQuery": "((K.aantal_uitl = \"50\"))", "owner": 102},
		    {"id": 14, "name": "Mijn selectie", "description": "", "cqlQuery": "((K.aantal_uitl = \"50\"))", "owner": 102},
		    {"id": 12, "name": "jennifer 2", "description": "", "cqlQuery": "((E.materiaal = \"B2\"))", "owner": 51624},
		    {"id": 10, "name": "ex", "description": "", "cqlQuery": "((E.materiaal = \"\"))", "owner": 51624},
		    {"id": 1, "name": "Voorlopige Titels", "description": "Voorlopige Titels", 
                     "cqlQuery": "((T.boven = \"J\") and (T.titstat = \"V\"))", "owner": 0}
		]
	    });
	    var result = mockSelectionResource.get();
	    $httpBackend.flush();
            expect(result.data[0].id).toEqual(17);
	}));
    });

    describe('Selection.get ', function() {
	it('should get one selection', inject(function (Selection) {
	    $httpBackend.expectGET("/restapi/service/selection/17").respond({
		data: { "id": 17, "name": "Mijn selectie", "description": "",
                        "cqlQuery": "((T.leeftijd = \"D\") and (E.plaatsing <> \"YLE|YLI|YSP|YTS|YYA\"))", "owner": 102}
		});
		var result = mockSelectionResource.get(17);
		$httpBackend.flush();
		expect(result.data.id).toEqual(17);
	    }));
    });
});

selectionServiceSpec.js

exports.config = {

  seleniumServerJar: '../node_modules/protractor/selenium/selenium-server-standalone-2.37.0.jar',
  seleniumPort: null,
  chromeDriver: '../node_modules/protractor/selenium/chromedriver',
  seleniumArgs: [],

  // If sauceUser and sauceKey are specified, seleniumServerJar will be ignored.
  // The tests will be run remotely using SauceLabs.
  sauceUser: null,
  sauceKey: null,

  // ----- What tests to run -----
  //
  // Spec patterns are relative to the location of this config.
  specs: [
    './e2e/*.js'
  ],

  capabilities: {
    'browserName': 'chrome'
  },

  // A base URL for your application under test. Calls to protractor.get()
  // with relative paths will be prepended with this.
  baseUrl: 'http://localhost:9999',

  // Selector for the element housing the angular app - this defaults to
  // body, but is necessary if ng-app is on a descendant of <body>  
  rootElement: 'body',

  // ----- Options to be passed to minijasminenode -----
  jasmineNodeOpts: {
    // onComplete will be called just before the driver quits.
    onComplete: null,
    // If true, display spec names.
    isVerbose: false,
    // If true, print colors to the terminal.
    showColors: true,
    // If true, include stack traces in failures.
    includeStackTrace: true,
    // Default time to wait in ms before a test fails.
    defaultTimeoutInterval: 10000
  }
};

protractor.conf.js

binnenkort

describe('Home Page', function() {

  var ptor = protractor.getInstance();

  it('should have a title', function() {
    browser.get('http://hkadev01.bicat.com/selectietool/app/index.html#/');
    expect(browser.getTitle()).toEqual('Wise Marketing Workbench');
  });

});

homePageSpec.js

Werken aan frontend

  • Sources ophalen

     
  • Tools installeren







     
  • Ontwikkelen
node.js installeren (platform afhankelijk)

grunt commandline installeren (global, eenmalig):
> npm install -g grunt-cli

rest ontwikkeltools installeren: 
> npm install

dependencies voor dit project installeren:
> bower install



> cvs -d ':ext:user@cvs.bicat.com:/home/cvsroot' co bxmcgi

Wise Modules

<div data-wise-paging-summary 
     data-offset="paging().offset" 
     data-total="paging().total" 
     data-perpage="paging().perpage">
</div>


<div data-wise-paging 
     data-offset="paging().offset" 
     data-total="paging().total" 
     data-perpage="paging().perpage" 
     data-on-offset-change="setPagingOffset">
</div>

wise-paging

2. Module injecteren in app

var searchApp = angular.module('searchApp', ['wisePaging']);

1. Scripts opnemen in html

3. Directive plaatsen

4. Callback functie maken

5. Enjoy

Wise Modules

<div data-wise-modal 
     data-close-on-backdrop-click="true">
</div>

wise-modal

2. Module injecteren in app

var searchApp = angular.module('searchApp', ['wiseModal']);

1. Scripts opnemen in html

3. Directive plaatsen

4. Activeren (externe content in iFrame

5. Enjoy

WiseModalService.openModal('include:partials/resultsPreview.html', 'Resultaten preview');

Wise Modules

<span data-wise-suggest
    data-ng-model="qs"
    data-current-qs="qsCopy()"
    data-get-suggestions="getSuggestions()"
    data-select-callback="newSearch()"
    data-suggestion-list="suggestions"
    data-wait-time-ms="300"
    data-min-chars="2"
    data-placeholder="Vind..."
    class="suggestions">
</span>

wise-suggest

2. Module injecteren in app

var searchApp = angular.module('searchApp', ['wiseSuggest']);

1. Scripts opnemen in html

3. Directive plaatsen

4. Callback functies maken

5. Enjoy

Wise Modules

<div data-wise-messages="myQueue"></div>

<div data-wise-last-message="otherQueue"></div>

wise-messages

2. Module injecteren in app

var searchApp = angular.module('searchApp', ['wiseMessages']);

1. Scripts opnemen in html

3. Directive plaatsen

4. Activeren 

5. Enjoy

WiseMessageService.success('Opgeslagen',"myQueue");

Wise Modules

angular.module('wiseSelectietool')

.controller('LoginCtrl', ['$scope', '$rootScope', 
                          'AuthenticationService', 
                          function ($scope, $rootScope, AuthenticationService) {
	
    $scope.login = function(authType){
        return AuthenticationService.login($scope.username, md5($scope.password), 
                                           authType, $scope.vestigingId, $scope.instantieId)
	           .then(function(data){
			$rootScope.gebruikersnaam = data.username;
		    }
	       );
    };

    $scope.logoff = function(){
	AuthenticationService.logoff();
	$rootScope.gebruikersnaam = '';
    };
}]);

wise-auth

2. Module injecteren in app

var searchApp = angular.module('searchApp', ['wiseAuth']);

1. Scripts opnemen in html

3. Configureren

AngularJS Intro

By Jan Peter Tamminga

AngularJS Intro

  • 761