aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2016-09-09 22:16:51 +0200
committerChocobozzz <florian.bigard@gmail.com>2016-09-09 22:16:51 +0200
commit4b2f33f3c6d109365090b08244d7f99ad4e69025 (patch)
tree700d3e8e14efc4172f754d75c041ec507100e897 /client
parentab32b0fc805b92c5a1d7ac5901cb1a38e94622ca (diff)
downloadPeerTube-4b2f33f3c6d109365090b08244d7f99ad4e69025.tar.gz
PeerTube-4b2f33f3c6d109365090b08244d7f99ad4e69025.tar.zst
PeerTube-4b2f33f3c6d109365090b08244d7f99ad4e69025.zip
Client: reactive forms
Diffstat (limited to 'client')
-rw-r--r--client/src/app/account/account.component.html16
-rw-r--r--client/src/app/account/account.component.ts44
-rw-r--r--client/src/app/admin/friends/friend-add/friend-add.component.html6
-rw-r--r--client/src/app/admin/friends/friend-add/friend-add.component.ts15
-rw-r--r--client/src/app/admin/users/user-add/user-add.component.html20
-rw-r--r--client/src/app/admin/users/user-add/user-add.component.ts44
-rw-r--r--client/src/app/app.module.ts5
-rw-r--r--client/src/app/login/login.component.html20
-rw-r--r--client/src/app/login/login.component.ts53
-rw-r--r--client/src/app/shared/form-validators/index.ts1
-rw-r--r--client/src/app/shared/forms/form-reactive.ts24
-rw-r--r--client/src/app/shared/forms/form-validators/index.ts3
-rw-r--r--client/src/app/shared/forms/form-validators/url.validator.ts (renamed from client/src/app/shared/form-validators/url.validator.ts)0
-rw-r--r--client/src/app/shared/forms/form-validators/user.ts17
-rw-r--r--client/src/app/shared/forms/form-validators/video.ts25
-rw-r--r--client/src/app/shared/forms/index.ts2
-rw-r--r--client/src/app/shared/index.ts2
-rw-r--r--client/src/app/videos/video-add/video-add.component.html30
-rw-r--r--client/src/app/videos/video-add/video-add.component.ts83
19 files changed, 282 insertions, 128 deletions
diff --git a/client/src/app/account/account.component.html b/client/src/app/account/account.component.html
index 4797fa914..5a8847acd 100644
--- a/client/src/app/account/account.component.html
+++ b/client/src/app/account/account.component.html
@@ -3,25 +3,25 @@
3<div *ngIf="information" class="alert alert-success">{{ information }}</div> 3<div *ngIf="information" class="alert alert-success">{{ information }}</div>
4<div *ngIf="error" class="alert alert-danger">{{ error }}</div> 4<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
5 5
6<form role="form" (ngSubmit)="changePassword()" [formGroup]="changePasswordForm"> 6<form role="form" (ngSubmit)="changePassword()" [formGroup]="form">
7 <div class="form-group"> 7 <div class="form-group">
8 <label for="new-password">New password</label> 8 <label for="new-password">New password</label>
9 <input 9 <input
10 type="password" class="form-control" name="new-password" id="new-password" 10 type="password" class="form-control" id="new-password"
11 [(ngModel)]="newPassword" #newPasswordInput="ngModel" 11 formControlName="new-password"
12 > 12 >
13 <div [hidden]="changePasswordForm.controls['new-password'].valid || changePasswordForm.controls['new-password'].pristine" class="alert alert-warning"> 13 <div *ngIf="formErrors['new-password']" class="alert alert-danger">
14 The password should have more than 5 characters 14 {{ formErrors['new-password'] }}
15 </div> 15 </div>
16 </div> 16 </div>
17 17
18 <div class="form-group"> 18 <div class="form-group">
19 <label for="name">Confirm new password</label> 19 <label for="name">Confirm new password</label>
20 <input 20 <input
21 type="password" class="form-control" name="new-confirmed-password" id="new-confirmed-password" 21 type="password" class="form-control" id="new-confirmed-password"
22 [(ngModel)]="newConfirmedPassword" #newConfirmedPasswordInput="ngModel" 22 formControlName="new-confirmed-password"
23 > 23 >
24 </div> 24 </div>
25 25
26 <input type="submit" value="Change password" class="btn btn-default" [disabled]="!changePasswordForm.valid"> 26 <input type="submit" value="Change password" class="btn btn-default" [disabled]="!form.valid">
27</form> 27</form>
diff --git a/client/src/app/account/account.component.ts b/client/src/app/account/account.component.ts
index a22738d3f..b503406c9 100644
--- a/client/src/app/account/account.component.ts
+++ b/client/src/app/account/account.component.ts
@@ -1,44 +1,64 @@
1import { } from '@angular/common'; 1import { } from '@angular/common';
2import { Component, OnInit } from '@angular/core'; 2import { Component, OnInit } from '@angular/core';
3import { FormControl, FormGroup, Validators } from '@angular/forms'; 3import { FormBuilder, FormGroup } from '@angular/forms';
4import { Router } from '@angular/router'; 4import { Router } from '@angular/router';
5 5
6import { AccountService } from './account.service'; 6import { AccountService } from './account.service';
7import { FormReactive, USER_PASSWORD } from '../shared';
7 8
8@Component({ 9@Component({
9 selector: 'my-account', 10 selector: 'my-account',
10 template: require('./account.component.html') 11 template: require('./account.component.html')
11}) 12})
12 13
13export class AccountComponent implements OnInit { 14export class AccountComponent extends FormReactive implements OnInit {
14 newPassword = '';
15 newConfirmedPassword = '';
16 changePasswordForm: FormGroup;
17 information: string = null; 15 information: string = null;
18 error: string = null; 16 error: string = null;
19 17
18 form: FormGroup;
19 formErrors = {
20 'new-password': '',
21 'new-confirmed-password': ''
22 };
23 validationMessages = {
24 'new-password': USER_PASSWORD.MESSAGES,
25 'new-confirmed-password': USER_PASSWORD.MESSAGES
26 };
27
20 constructor( 28 constructor(
21 private accountService: AccountService, 29 private accountService: AccountService,
30 private formBuilder: FormBuilder,
22 private router: Router 31 private router: Router
23 ) {} 32 ) {
33 super();
34 }
24 35
25 ngOnInit() { 36 buildForm() {
26 this.changePasswordForm = new FormGroup({ 37 this.form = this.formBuilder.group({
27 'new-password': new FormControl('', [ <any>Validators.required, <any>Validators.minLength(6) ]), 38 'new-password': [ '', USER_PASSWORD.VALIDATORS ],
28 'new-confirmed-password': new FormControl('', [ <any>Validators.required, <any>Validators.minLength(6) ]), 39 'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ],
29 }); 40 });
41
42 this.form.valueChanges.subscribe(data => this.onValueChanged(data));
43 }
44
45 ngOnInit() {
46 this.buildForm();
30 } 47 }
31 48
32 changePassword() { 49 changePassword() {
50 const newPassword = this.form.value['new-password'];
51 const newConfirmedPassword = this.form.value['new-confirmed-password'];
52
33 this.information = null; 53 this.information = null;
34 this.error = null; 54 this.error = null;
35 55
36 if (this.newPassword !== this.newConfirmedPassword) { 56 if (newPassword !== newConfirmedPassword) {
37 this.error = 'The new password and the confirmed password do not correspond.'; 57 this.error = 'The new password and the confirmed password do not correspond.';
38 return; 58 return;
39 } 59 }
40 60
41 this.accountService.changePassword(this.newPassword).subscribe( 61 this.accountService.changePassword(newPassword).subscribe(
42 ok => this.information = 'Password updated.', 62 ok => this.information = 'Password updated.',
43 63
44 err => this.error = err 64 err => this.error = err
diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.html b/client/src/app/admin/friends/friend-add/friend-add.component.html
index 5b8dc8d87..788f3b44d 100644
--- a/client/src/app/admin/friends/friend-add/friend-add.component.html
+++ b/client/src/app/admin/friends/friend-add/friend-add.component.html
@@ -2,14 +2,14 @@
2 2
3<div *ngIf="error" class="alert alert-danger">{{ error }}</div> 3<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
4 4
5<form (ngSubmit)="makeFriends()" [formGroup]="friendAddForm"> 5<form (ngSubmit)="makeFriends()" [formGroup]="form">
6 <div class="form-group" *ngFor="let url of urls; let id = index; trackBy:customTrackBy"> 6 <div class="form-group" *ngFor="let url of urls; let id = index; trackBy:customTrackBy">
7 <label for="username">Url</label> 7 <label for="username">Url</label>
8 8
9 <div class="input-group"> 9 <div class="input-group">
10 <input 10 <input
11 type="text" class="form-control" placeholder="http://domain.com" 11 type="text" class="form-control" placeholder="http://domain.com"
12 [name]="'url-' + id" [id]="'url-' + id" [formControlName]="'url-' + id" [(ngModel)]="urls[id]" 12 [id]="'url-' + id" [formControlName]="'url-' + id"
13 /> 13 />
14 <span class="input-group-btn"> 14 <span class="input-group-btn">
15 <button *ngIf="displayAddField(id)" (click)="addField()" class="btn btn-default" type="button">+</button> 15 <button *ngIf="displayAddField(id)" (click)="addField()" class="btn btn-default" type="button">+</button>
@@ -17,7 +17,7 @@
17 </span> 17 </span>
18 </div> 18 </div>
19 19
20 <div [hidden]="friendAddForm.controls['url-' + id].valid || friendAddForm.controls['url-' + id].pristine" class="alert alert-warning"> 20 <div [hidden]="form.controls['url-' + id].valid || form.controls['url-' + id].pristine" class="alert alert-warning">
21 It should be a valid url. 21 It should be a valid url.
22 </div> 22 </div>
23 </div> 23 </div>
diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.ts b/client/src/app/admin/friends/friend-add/friend-add.component.ts
index 55aed9156..68363b482 100644
--- a/client/src/app/admin/friends/friend-add/friend-add.component.ts
+++ b/client/src/app/admin/friends/friend-add/friend-add.component.ts
@@ -11,19 +11,19 @@ import { FriendService } from '../shared';
11 styles: [ require('./friend-add.component.scss') ] 11 styles: [ require('./friend-add.component.scss') ]
12}) 12})
13export class FriendAddComponent implements OnInit { 13export class FriendAddComponent implements OnInit {
14 friendAddForm: FormGroup; 14 form: FormGroup;
15 urls = [ ]; 15 urls = [ ];
16 error: string = null; 16 error: string = null;
17 17
18 constructor(private router: Router, private friendService: FriendService) {} 18 constructor(private router: Router, private friendService: FriendService) {}
19 19
20 ngOnInit() { 20 ngOnInit() {
21 this.friendAddForm = new FormGroup({}); 21 this.form = new FormGroup({});
22 this.addField(); 22 this.addField();
23 } 23 }
24 24
25 addField() { 25 addField() {
26 this.friendAddForm.addControl(`url-${this.urls.length}`, new FormControl('', [ validateUrl ])); 26 this.form.addControl(`url-${this.urls.length}`, new FormControl('', [ validateUrl ]));
27 this.urls.push(''); 27 this.urls.push('');
28 } 28 }
29 29
@@ -42,7 +42,7 @@ export class FriendAddComponent implements OnInit {
42 isFormValid() { 42 isFormValid() {
43 // Do not check the last input 43 // Do not check the last input
44 for (let i = 0; i < this.urls.length - 1; i++) { 44 for (let i = 0; i < this.urls.length - 1; i++) {
45 if (!this.friendAddForm.controls[`url-${i}`].valid) return false; 45 if (!this.form.controls[`url-${i}`].valid) return false;
46 } 46 }
47 47
48 const lastIndex = this.urls.length - 1; 48 const lastIndex = this.urls.length - 1;
@@ -50,13 +50,13 @@ export class FriendAddComponent implements OnInit {
50 if (this.urls[lastIndex] === '' && lastIndex !== 0) { 50 if (this.urls[lastIndex] === '' && lastIndex !== 0) {
51 return true; 51 return true;
52 } else { 52 } else {
53 return this.friendAddForm.controls[`url-${lastIndex}`].valid; 53 return this.form.controls[`url-${lastIndex}`].valid;
54 } 54 }
55 } 55 }
56 56
57 removeField(index: number) { 57 removeField(index: number) {
58 // Remove the last control 58 // Remove the last control
59 this.friendAddForm.removeControl(`url-${this.urls.length - 1}`); 59 this.form.removeControl(`url-${this.urls.length - 1}`);
60 this.urls.splice(index, 1); 60 this.urls.splice(index, 1);
61 } 61 }
62 62
@@ -94,7 +94,8 @@ export class FriendAddComponent implements OnInit {
94 private getNotEmptyUrls() { 94 private getNotEmptyUrls() {
95 const notEmptyUrls = []; 95 const notEmptyUrls = [];
96 96
97 this.urls.forEach((url) => { 97 Object.keys(this.form.value).forEach((urlKey) => {
98 const url = this.form.value[urlKey];
98 if (url !== '') notEmptyUrls.push(url); 99 if (url !== '') notEmptyUrls.push(url);
99 }); 100 });
100 101
diff --git a/client/src/app/admin/users/user-add/user-add.component.html b/client/src/app/admin/users/user-add/user-add.component.html
index 09219893b..9b76c7c1b 100644
--- a/client/src/app/admin/users/user-add/user-add.component.html
+++ b/client/src/app/admin/users/user-add/user-add.component.html
@@ -2,28 +2,28 @@
2 2
3<div *ngIf="error" class="alert alert-danger">{{ error }}</div> 3<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
4 4
5<form role="form" (ngSubmit)="addUser()" [formGroup]="userAddForm"> 5<form role="form" (ngSubmit)="addUser()" [formGroup]="form">
6 <div class="form-group"> 6 <div class="form-group">
7 <label for="username">Username</label> 7 <label for="username">Username</label>
8 <input 8 <input
9 type="text" class="form-control" name="username" id="username" placeholder="Username" 9 type="text" class="form-control" id="username" placeholder="Username"
10 [(ngModel)]="username" 10 formControlName="username"
11 > 11 >
12 <div [hidden]="userAddForm.controls.username.valid || userAddForm.controls.username.pristine" class="alert alert-danger"> 12 <div *ngIf="formErrors.username" class="alert alert-danger">
13 Username is required with a length >= 3 and <= 20 13 {{ formErrors.username }}
14 </div> 14 </div>
15 </div> 15 </div>
16 16
17 <div class="form-group"> 17 <div class="form-group">
18 <label for="password">Password</label> 18 <label for="password">Password</label>
19 <input 19 <input
20 type="password" class="form-control" name="password" id="password" placeholder="Password" 20 type="password" class="form-control" id="password" placeholder="Password"
21 [(ngModel)]="password" 21 formControlName="password"
22 > 22 >
23 <div [hidden]="userAddForm.controls.password.valid || userAddForm.controls.password.pristine" class="alert alert-danger"> 23 <div *ngIf="formErrors.password" class="alert alert-danger">
24 Password is required with a length >= 6 24 {{ formErrors.password }}
25 </div> 25 </div>
26 </div> 26 </div>
27 27
28 <input type="submit" value="Add user" class="btn btn-default" [disabled]="!userAddForm.valid"> 28 <input type="submit" value="Add user" class="btn btn-default" [disabled]="!form.valid">
29</form> 29</form>
diff --git a/client/src/app/admin/users/user-add/user-add.component.ts b/client/src/app/admin/users/user-add/user-add.component.ts
index e3f4b2e1a..b79437795 100644
--- a/client/src/app/admin/users/user-add/user-add.component.ts
+++ b/client/src/app/admin/users/user-add/user-add.component.ts
@@ -1,32 +1,54 @@
1import { Component, OnInit } from '@angular/core'; 1import { Component, OnInit } from '@angular/core';
2import { FormGroup, FormControl, Validators } from '@angular/forms'; 2import { FormBuilder, FormGroup } from '@angular/forms';
3import { Router } from '@angular/router'; 3import { Router } from '@angular/router';
4 4
5import { UserService } from '../shared'; 5import { UserService } from '../shared';
6import { FormReactive, USER_USERNAME, USER_PASSWORD } from '../../../shared';
6 7
7@Component({ 8@Component({
8 selector: 'my-user-add', 9 selector: 'my-user-add',
9 template: require('./user-add.component.html') 10 template: require('./user-add.component.html')
10}) 11})
11export class UserAddComponent implements OnInit { 12export class UserAddComponent extends FormReactive implements OnInit {
12 userAddForm: FormGroup;
13 error: string = null; 13 error: string = null;
14 username = '';
15 password = '';
16 14
17 constructor(private router: Router, private userService: UserService) {} 15 form: FormGroup;
16 formErrors = {
17 'username': '',
18 'password': ''
19 };
20 validationMessages = {
21 'username': USER_USERNAME.MESSAGES,
22 'password': USER_PASSWORD.MESSAGES,
23 };
18 24
19 ngOnInit() { 25 constructor(
20 this.userAddForm = new FormGroup({ 26 private formBuilder: FormBuilder,
21 username: new FormControl('', [ <any>Validators.required, <any>Validators.minLength(3), <any>Validators.maxLength(20) ]), 27 private router: Router,
22 password: new FormControl('', [ <any>Validators.required, <any>Validators.minLength(6) ]), 28 private userService: UserService
29 ) {
30 super();
31 }
32
33 buildForm() {
34 this.form = this.formBuilder.group({
35 username: [ '', USER_USERNAME.VALIDATORS ],
36 password: [ '', USER_PASSWORD.VALIDATORS ],
23 }); 37 });
38
39 this.form.valueChanges.subscribe(data => this.onValueChanged(data));
40 }
41
42 ngOnInit() {
43 this.buildForm();
24 } 44 }
25 45
26 addUser() { 46 addUser() {
27 this.error = null; 47 this.error = null;
28 48
29 this.userService.addUser(this.username, this.password).subscribe( 49 const { username, password } = this.form.value;
50
51 this.userService.addUser(username, password).subscribe(
30 ok => this.router.navigate([ '/admin/users/list' ]), 52 ok => this.router.navigate([ '/admin/users/list' ]),
31 53
32 err => this.error = err.text 54 err => this.error = err.text
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index 950b3c48e..f071224c5 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -28,7 +28,8 @@ import {
28 VideoMiniatureComponent, 28 VideoMiniatureComponent,
29 VideoSortComponent, 29 VideoSortComponent,
30 VideoWatchComponent, 30 VideoWatchComponent,
31 VideoService 31 VideoService,
32 WebTorrentService
32} from './videos'; 33} from './videos';
33import { 34import {
34 FriendsComponent, 35 FriendsComponent,
@@ -59,7 +60,7 @@ const APP_PROVIDERS = [
59 60
60 AuthService, 61 AuthService,
61 RestExtractor, 62 RestExtractor,
62 RestExtractor, RestService, VideoService, SearchService, FriendService, UserService, AccountService 63 RestExtractor, RestService, VideoService, SearchService, FriendService, UserService, AccountService, WebTorrentService
63]; 64];
64/** 65/**
65 * `AppModule` is the main entry point into Angular2's bootstraping process 66 * `AppModule` is the main entry point into Angular2's bootstraping process
diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html
index 636872942..94a405405 100644
--- a/client/src/app/login/login.component.html
+++ b/client/src/app/login/login.component.html
@@ -2,28 +2,28 @@
2 2
3<div *ngIf="error" class="alert alert-danger">{{ error }}</div> 3<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
4 4
5<form role="form" (ngSubmit)="login()" [formGroup]="loginForm"> 5<form role="form" (ngSubmit)="login()" [formGroup]="form">
6 <div class="form-group"> 6 <div class="form-group">
7 <label for="username">Username</label> 7 <label for="username">Username</label>
8 <input 8 <input
9 type="text" class="form-control" name="username" id="username" placeholder="Username" 9 type="text" class="form-control" id="username" placeholder="Username" required
10 [(ngModel)]="username" 10 formControlName="username"
11 > 11 >
12 <div [hidden]="loginForm.controls.username.valid || loginForm.controls.username.pristine" class="alert alert-danger"> 12 <div *ngIf="formErrors.username" class="alert alert-danger">
13 Username is required 13 {{ formErrors.username }}
14 </div> 14 </div>
15 </div> 15 </div>
16 16
17 <div class="form-group"> 17 <div class="form-group">
18 <label for="password">Password</label> 18 <label for="password">Password</label>
19 <input 19 <input
20 type="password" class="form-control" name="password" id="password" placeholder="Password" 20 type="password" class="form-control" name="password" id="password" placeholder="Password" required
21 [(ngModel)]="password" 21 formControlName="password"
22 > 22 >
23 <div [hidden]="loginForm.controls.password.valid || loginForm.controls.password.pristine" class="alert alert-danger"> 23 <div *ngIf="formErrors.password" class="alert alert-danger">
24 Password is required 24 {{ formErrors.password }}
25 </div> 25 </div>
26 </div> 26 </div>
27 27
28 <input type="submit" value="Login" class="btn btn-default" [disabled]="!loginForm.valid"> 28 <input type="submit" value="Login" class="btn btn-default" [disabled]="!form.valid">
29</form> 29</form>
diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts
index 7a4e15c2c..378714ca1 100644
--- a/client/src/app/login/login.component.ts
+++ b/client/src/app/login/login.component.ts
@@ -1,39 +1,60 @@
1import { Component, OnInit } from '@angular/core'; 1import { Component, OnInit } from '@angular/core';
2import { FormControl, FormGroup, Validators } from '@angular/forms'; 2import { FormBuilder, FormGroup, Validators } from '@angular/forms';
3import { Router } from '@angular/router'; 3import { Router } from '@angular/router';
4 4
5import { AuthService } from '../shared'; 5import { AuthService, FormReactive } from '../shared';
6 6
7@Component({ 7@Component({
8 selector: 'my-login', 8 selector: 'my-login',
9 template: require('./login.component.html') 9 template: require('./login.component.html')
10}) 10})
11 11
12export class LoginComponent implements OnInit { 12export class LoginComponent extends FormReactive implements OnInit {
13 error: string = null; 13 error: string = null;
14 username = ''; 14
15 password: ''; 15 form: FormGroup;
16 loginForm: FormGroup; 16 formErrors = {
17 'username': '',
18 'password': ''
19 };
20 validationMessages = {
21 'username': {
22 'required': 'Username is required.',
23 },
24 'password': {
25 'required': 'Password is required.'
26 }
27 };
17 28
18 constructor( 29 constructor(
19 private authService: AuthService, 30 private authService: AuthService,
31 private formBuilder: FormBuilder,
20 private router: Router 32 private router: Router
21 ) {} 33 ) {
34 super();
35 }
22 36
23 ngOnInit() { 37 buildForm() {
24 this.loginForm = new FormGroup({ 38 this.form = this.formBuilder.group({
25 username: new FormControl('', [ <any>Validators.required ]), 39 username: [ '', Validators.required ],
26 password: new FormControl('', [ <any>Validators.required ]), 40 password: [ '', Validators.required ],
27 }); 41 });
42
43 this.form.valueChanges.subscribe(data => this.onValueChanged(data));
44 }
45
46 ngOnInit() {
47 this.buildForm();
28 } 48 }
29 49
30 login() { 50 login() {
31 this.authService.login(this.username, this.password).subscribe( 51 this.error = null;
32 result => { 52
33 this.error = null; 53 const { username, password } = this.form.value;
54
55 this.authService.login(username, password).subscribe(
56 result => this.router.navigate(['/videos/list']),
34 57
35 this.router.navigate(['/videos/list']);
36 },
37 error => { 58 error => {
38 console.error(error.json); 59 console.error(error.json);
39 60
diff --git a/client/src/app/shared/form-validators/index.ts b/client/src/app/shared/form-validators/index.ts
deleted file mode 100644
index f9e9a6191..000000000
--- a/client/src/app/shared/form-validators/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './url.validator';
diff --git a/client/src/app/shared/forms/form-reactive.ts b/client/src/app/shared/forms/form-reactive.ts
new file mode 100644
index 000000000..1e8a69771
--- /dev/null
+++ b/client/src/app/shared/forms/form-reactive.ts
@@ -0,0 +1,24 @@
1import { FormGroup } from '@angular/forms';
2
3export abstract class FormReactive {
4 abstract form: FormGroup;
5 abstract formErrors: Object;
6 abstract validationMessages: Object;
7
8 abstract buildForm(): void;
9
10 protected onValueChanged(data?: any) {
11 for (const field in this.formErrors) {
12 // clear previous error message (if any)
13 this.formErrors[field] = '';
14 const control = this.form.get(field);
15
16 if (control && control.dirty && !control.valid) {
17 const messages = this.validationMessages[field];
18 for (const key in control.errors) {
19 this.formErrors[field] += messages[key] + ' ';
20 }
21 }
22 }
23 }
24}
diff --git a/client/src/app/shared/forms/form-validators/index.ts b/client/src/app/shared/forms/form-validators/index.ts
new file mode 100644
index 000000000..1d2ae6f68
--- /dev/null
+++ b/client/src/app/shared/forms/form-validators/index.ts
@@ -0,0 +1,3 @@
1export * from './url.validator';
2export * from './user';
3export * from './video';
diff --git a/client/src/app/shared/form-validators/url.validator.ts b/client/src/app/shared/forms/form-validators/url.validator.ts
index 67163b4e9..67163b4e9 100644
--- a/client/src/app/shared/form-validators/url.validator.ts
+++ b/client/src/app/shared/forms/form-validators/url.validator.ts
diff --git a/client/src/app/shared/forms/form-validators/user.ts b/client/src/app/shared/forms/form-validators/user.ts
new file mode 100644
index 000000000..5b11ff265
--- /dev/null
+++ b/client/src/app/shared/forms/form-validators/user.ts
@@ -0,0 +1,17 @@
1import { Validators } from '@angular/forms';
2
3export const USER_USERNAME = {
4 VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(20) ],
5 MESSAGES: {
6 'required': 'Username is required.',
7 'minlength': 'Username must be at least 3 characters long.',
8 'maxlength': 'Username cannot be more than 20 characters long.'
9 }
10};
11export const USER_PASSWORD = {
12 VALIDATORS: [ Validators.required, Validators.minLength(6) ],
13 MESSAGES: {
14 'required': 'Password is required.',
15 'minlength': 'Password must be at least 6 characters long.',
16 }
17};
diff --git a/client/src/app/shared/forms/form-validators/video.ts b/client/src/app/shared/forms/form-validators/video.ts
new file mode 100644
index 000000000..3766d4018
--- /dev/null
+++ b/client/src/app/shared/forms/form-validators/video.ts
@@ -0,0 +1,25 @@
1import { Validators } from '@angular/forms';
2
3export const VIDEO_NAME = {
4 VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(50) ],
5 MESSAGES: {
6 'required': 'Video name is required.',
7 'minlength': 'Video name must be at least 3 characters long.',
8 'maxlength': 'Video name cannot be more than 50 characters long.'
9 }
10};
11export const VIDEO_DESCRIPTION = {
12 VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(250) ],
13 MESSAGES: {
14 'required': 'Video description is required.',
15 'minlength': 'Video description must be at least 3 characters long.',
16 'maxlength': 'Video description cannot be more than 250 characters long.'
17 }
18};
19
20export const VIDEO_TAGS = {
21 VALIDATORS: [ Validators.pattern('^[a-zA-Z0-9]{2,10}$') ],
22 MESSAGES: {
23 'pattern': 'A tag should be between 2 and 10 alphanumeric characters long.'
24 }
25};
diff --git a/client/src/app/shared/forms/index.ts b/client/src/app/shared/forms/index.ts
new file mode 100644
index 000000000..588ebb4be
--- /dev/null
+++ b/client/src/app/shared/forms/index.ts
@@ -0,0 +1,2 @@
1export * from './form-validators';
2export * from './form-reactive';
diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts
index c362a0e4a..af34b4b64 100644
--- a/client/src/app/shared/index.ts
+++ b/client/src/app/shared/index.ts
@@ -1,5 +1,5 @@
1export * from './auth'; 1export * from './auth';
2export * from './form-validators'; 2export * from './forms';
3export * from './rest'; 3export * from './rest';
4export * from './search'; 4export * from './search';
5export * from './users'; 5export * from './users';
diff --git a/client/src/app/videos/video-add/video-add.component.html b/client/src/app/videos/video-add/video-add.component.html
index 76bb61f7d..64320cae7 100644
--- a/client/src/app/videos/video-add/video-add.component.html
+++ b/client/src/app/videos/video-add/video-add.component.html
@@ -2,31 +2,31 @@
2 2
3<div *ngIf="error" class="alert alert-danger">{{ error }}</div> 3<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
4 4
5<form novalidate (ngSubmit)="upload()" [formGroup]="videoForm"> 5<form novalidate (ngSubmit)="upload()" [formGroup]="form">
6 <div class="form-group"> 6 <div class="form-group">
7 <label for="name">Name</label> 7 <label for="name">Name</label>
8 <input 8 <input
9 type="text" class="form-control" name="name" id="name" 9 type="text" class="form-control" id="name"
10 [(ngModel)]="video.name" 10 formControlName="name"
11 > 11 >
12 <div [hidden]="videoForm.controls.name.valid || videoForm.controls.name.pristine" class="alert alert-warning"> 12 <div *ngIf="formErrors.name" class="alert alert-danger">
13 A name is required and should be between 3 and 50 characters long 13 {{ formErrors.name }}
14 </div> 14 </div>
15 </div> 15 </div>
16 16
17 <div class="form-group"> 17 <div class="form-group">
18 <label for="tags">Tags</label> 18 <label for="tags">Tags</label>
19 <input 19 <input
20 type="text" class="form-control" name="tags" id="tags" 20 type="text" class="form-control" id="currentTag"
21 [disabled]="isTagsInputDisabled" (keyup)="onTagKeyPress($event)" [(ngModel)]="currentTag" 21 formControlName="currentTag" (keyup)="onTagKeyPress($event)"
22 > 22 >
23 <div [hidden]="videoForm.controls.tags.valid || videoForm.controls.tags.pristine" class="alert alert-warning"> 23 <div *ngIf="formErrors.currentTag" class="alert alert-danger">
24 A tag should be between 2 and 10 characters (alphanumeric) long 24 {{ formErrors.currentTag }}
25 </div> 25 </div>
26 </div> 26 </div>
27 27
28 <div class="tags"> 28 <div class="tags">
29 <div class="label label-primary tag" *ngFor="let tag of video.tags"> 29 <div class="label label-primary tag" *ngFor="let tag of tags">
30 {{ tag }} 30 {{ tag }}
31 <span class="remove" (click)="removeTag(tag)">x</span> 31 <span class="remove" (click)="removeTag(tag)">x</span>
32 </div> 32 </div>
@@ -53,12 +53,12 @@
53 <div class="form-group"> 53 <div class="form-group">
54 <label for="description">Description</label> 54 <label for="description">Description</label>
55 <textarea 55 <textarea
56 name="description" id="description" class="form-control" placeholder="Description..." 56 id="description" class="form-control" placeholder="Description..."
57 [(ngModel)]="video.description" 57 formControlName="description"
58 > 58 >
59 </textarea> 59 </textarea>
60 <div [hidden]="videoForm.controls.description.valid || videoForm.controls.description.pristine" class="alert alert-warning"> 60 <div *ngIf="formErrors.description" class="alert alert-danger">
61 A description is required and should be between 3 and 250 characters long 61 {{ formErrors.description }}
62 </div> 62 </div>
63 </div> 63 </div>
64 64
@@ -69,7 +69,7 @@
69 <div class="form-group"> 69 <div class="form-group">
70 <input 70 <input
71 type="submit" value="Upload" class="btn btn-default form-control" [title]="getInvalidFieldsTitle()" 71 type="submit" value="Upload" class="btn btn-default form-control" [title]="getInvalidFieldsTitle()"
72 [disabled]="!videoForm.valid || video.tags.length === 0 || filename === null" 72 [disabled]="!form.valid || tags.length === 0 || filename === null"
73 > 73 >
74 </div> 74 </div>
75</form> 75</form>
diff --git a/client/src/app/videos/video-add/video-add.component.ts b/client/src/app/videos/video-add/video-add.component.ts
index f0695d768..16a8409be 100644
--- a/client/src/app/videos/video-add/video-add.component.ts
+++ b/client/src/app/videos/video-add/video-add.component.ts
@@ -1,10 +1,10 @@
1import { Component, ElementRef, OnInit } from '@angular/core'; 1import { Component, ElementRef, OnInit } from '@angular/core';
2import { FormControl, FormGroup, Validators } from '@angular/forms'; 2import { FormBuilder, FormGroup } from '@angular/forms';
3import { Router } from '@angular/router'; 3import { Router } from '@angular/router';
4 4
5import { FileUploader } from 'ng2-file-upload/ng2-file-upload'; 5import { FileUploader } from 'ng2-file-upload/ng2-file-upload';
6 6
7import { AuthService } from '../../shared'; 7import { AuthService, FormReactive, VIDEO_NAME, VIDEO_DESCRIPTION, VIDEO_TAGS } from '../../shared';
8 8
9@Component({ 9@Component({
10 selector: 'my-videos-add', 10 selector: 'my-videos-add',
@@ -12,22 +12,31 @@ import { AuthService } from '../../shared';
12 template: require('./video-add.component.html') 12 template: require('./video-add.component.html')
13}) 13})
14 14
15export class VideoAddComponent implements OnInit { 15export class VideoAddComponent extends FormReactive implements OnInit {
16 currentTag: string; // Tag the user is writing in the input 16 tags: string[] = [];
17 error: string = null;
18 videoForm: FormGroup;
19 uploader: FileUploader; 17 uploader: FileUploader;
20 video = { 18
19 error: string = null;
20 form: FormGroup;
21 formErrors = {
21 name: '', 22 name: '',
22 tags: [], 23 description: '',
23 description: '' 24 currentTag: ''
25 };
26 validationMessages = {
27 name: VIDEO_NAME.MESSAGES,
28 description: VIDEO_DESCRIPTION.MESSAGES,
29 currentTag: VIDEO_TAGS.MESSAGES
24 }; 30 };
25 31
26 constructor( 32 constructor(
27 private authService: AuthService, 33 private authService: AuthService,
28 private elementRef: ElementRef, 34 private elementRef: ElementRef,
35 private formBuilder: FormBuilder,
29 private router: Router 36 private router: Router
30 ) {} 37 ) {
38 super();
39 }
31 40
32 get filename() { 41 get filename() {
33 if (this.uploader.queue.length === 0) { 42 if (this.uploader.queue.length === 0) {
@@ -37,20 +46,26 @@ export class VideoAddComponent implements OnInit {
37 return this.uploader.queue[0].file.name; 46 return this.uploader.queue[0].file.name;
38 } 47 }
39 48
40 get isTagsInputDisabled () { 49 buildForm() {
41 return this.video.tags.length >= 3; 50 this.form = this.formBuilder.group({
51 name: [ '', VIDEO_NAME.VALIDATORS ],
52 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ],
53 currentTag: [ '', VIDEO_TAGS.VALIDATORS ]
54 });
55
56 this.form.valueChanges.subscribe(data => this.onValueChanged(data));
42 } 57 }
43 58
44 getInvalidFieldsTitle() { 59 getInvalidFieldsTitle() {
45 let title = ''; 60 let title = '';
46 const nameControl = this.videoForm.controls['name']; 61 const nameControl = this.form.controls['name'];
47 const descriptionControl = this.videoForm.controls['description']; 62 const descriptionControl = this.form.controls['description'];
48 63
49 if (!nameControl.valid) { 64 if (!nameControl.valid) {
50 title += 'A name is required\n'; 65 title += 'A name is required\n';
51 } 66 }
52 67
53 if (this.video.tags.length === 0) { 68 if (this.tags.length === 0) {
54 title += 'At least one tag is required\n'; 69 title += 'At least one tag is required\n';
55 } 70 }
56 71
@@ -66,13 +81,6 @@ export class VideoAddComponent implements OnInit {
66 } 81 }
67 82
68 ngOnInit() { 83 ngOnInit() {
69 this.videoForm = new FormGroup({
70 name: new FormControl('', [ <any>Validators.required, <any>Validators.minLength(3), <any>Validators.maxLength(50) ]),
71 description: new FormControl('', [ <any>Validators.required, <any>Validators.minLength(3), <any>Validators.maxLength(250) ]),
72 tags: new FormControl('', <any>Validators.pattern('^[a-zA-Z0-9]{2,10}$'))
73 });
74
75
76 this.uploader = new FileUploader({ 84 this.uploader = new FileUploader({
77 authToken: this.authService.getRequestHeaderValue(), 85 authToken: this.authService.getRequestHeaderValue(),
78 queueLimit: 1, 86 queueLimit: 1,
@@ -81,26 +89,37 @@ export class VideoAddComponent implements OnInit {
81 }); 89 });
82 90
83 this.uploader.onBuildItemForm = (item, form) => { 91 this.uploader.onBuildItemForm = (item, form) => {
84 form.append('name', this.video.name); 92 const name = this.form.value['name'];
85 form.append('description', this.video.description); 93 const description = this.form.value['description'];
94
95 form.append('name', name);
96 form.append('description', description);
86 97
87 for (let i = 0; i < this.video.tags.length; i++) { 98 for (let i = 0; i < this.tags.length; i++) {
88 form.append(`tags[${i}]`, this.video.tags[i]); 99 form.append(`tags[${i}]`, this.tags[i]);
89 } 100 }
90 }; 101 };
102
103 this.buildForm();
91 } 104 }
92 105
93 onTagKeyPress(event: KeyboardEvent) { 106 onTagKeyPress(event: KeyboardEvent) {
107 const currentTag = this.form.value['currentTag'];
108
94 // Enter press 109 // Enter press
95 if (event.keyCode === 13) { 110 if (event.keyCode === 13) {
96 // Check if the tag is valid and does not already exist 111 // Check if the tag is valid and does not already exist
97 if ( 112 if (
98 this.currentTag !== '' && 113 currentTag !== '' &&
99 this.videoForm.controls['tags'].valid && 114 this.form.controls['currentTag'].valid &&
100 this.video.tags.indexOf(this.currentTag) === -1 115 this.tags.indexOf(currentTag) === -1
101 ) { 116 ) {
102 this.video.tags.push(this.currentTag); 117 this.tags.push(currentTag);
103 this.currentTag = ''; 118 this.form.patchValue({ currentTag: '' });
119
120 if (this.tags.length >= 3) {
121 this.form.get('currentTag').disable();
122 }
104 } 123 }
105 } 124 }
106 } 125 }
@@ -110,7 +129,7 @@ export class VideoAddComponent implements OnInit {
110 } 129 }
111 130
112 removeTag(tag: string) { 131 removeTag(tag: string) {
113 this.video.tags.splice(this.video.tags.indexOf(tag), 1); 132 this.tags.splice(this.tags.indexOf(tag), 1);
114 } 133 }
115 134
116 upload() { 135 upload() {