From: Chocobozzz Date: Fri, 9 Sep 2016 20:16:51 +0000 (+0200) Subject: Client: reactive forms X-Git-Tag: v0.0.1-alpha~746 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=4b2f33f3c6d109365090b08244d7f99ad4e69025;p=oweals%2Fpeertube.git Client: reactive forms --- 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 @@
{{ information }}
{{ error }}
-
+
-
- The password should have more than 5 characters +
+ {{ formErrors['new-password'] }}
- + 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 @@ import { } from '@angular/common'; import { Component, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { Router } from '@angular/router'; import { AccountService } from './account.service'; +import { FormReactive, USER_PASSWORD } from '../shared'; @Component({ selector: 'my-account', template: require('./account.component.html') }) -export class AccountComponent implements OnInit { - newPassword = ''; - newConfirmedPassword = ''; - changePasswordForm: FormGroup; +export class AccountComponent extends FormReactive implements OnInit { information: string = null; error: string = null; + form: FormGroup; + formErrors = { + 'new-password': '', + 'new-confirmed-password': '' + }; + validationMessages = { + 'new-password': USER_PASSWORD.MESSAGES, + 'new-confirmed-password': USER_PASSWORD.MESSAGES + }; + constructor( private accountService: AccountService, + private formBuilder: FormBuilder, private router: Router - ) {} + ) { + super(); + } - ngOnInit() { - this.changePasswordForm = new FormGroup({ - 'new-password': new FormControl('', [ Validators.required, Validators.minLength(6) ]), - 'new-confirmed-password': new FormControl('', [ Validators.required, Validators.minLength(6) ]), + buildForm() { + this.form = this.formBuilder.group({ + 'new-password': [ '', USER_PASSWORD.VALIDATORS ], + 'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ], }); + + this.form.valueChanges.subscribe(data => this.onValueChanged(data)); + } + + ngOnInit() { + this.buildForm(); } changePassword() { + const newPassword = this.form.value['new-password']; + const newConfirmedPassword = this.form.value['new-confirmed-password']; + this.information = null; this.error = null; - if (this.newPassword !== this.newConfirmedPassword) { + if (newPassword !== newConfirmedPassword) { this.error = 'The new password and the confirmed password do not correspond.'; return; } - this.accountService.changePassword(this.newPassword).subscribe( + this.accountService.changePassword(newPassword).subscribe( ok => this.information = 'Password updated.', 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 @@
{{ error }}
-
+
@@ -17,7 +17,7 @@
-
+
It should be a valid url.
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'; styles: [ require('./friend-add.component.scss') ] }) export class FriendAddComponent implements OnInit { - friendAddForm: FormGroup; + form: FormGroup; urls = [ ]; error: string = null; constructor(private router: Router, private friendService: FriendService) {} ngOnInit() { - this.friendAddForm = new FormGroup({}); + this.form = new FormGroup({}); this.addField(); } addField() { - this.friendAddForm.addControl(`url-${this.urls.length}`, new FormControl('', [ validateUrl ])); + this.form.addControl(`url-${this.urls.length}`, new FormControl('', [ validateUrl ])); this.urls.push(''); } @@ -42,7 +42,7 @@ export class FriendAddComponent implements OnInit { isFormValid() { // Do not check the last input for (let i = 0; i < this.urls.length - 1; i++) { - if (!this.friendAddForm.controls[`url-${i}`].valid) return false; + if (!this.form.controls[`url-${i}`].valid) return false; } const lastIndex = this.urls.length - 1; @@ -50,13 +50,13 @@ export class FriendAddComponent implements OnInit { if (this.urls[lastIndex] === '' && lastIndex !== 0) { return true; } else { - return this.friendAddForm.controls[`url-${lastIndex}`].valid; + return this.form.controls[`url-${lastIndex}`].valid; } } removeField(index: number) { // Remove the last control - this.friendAddForm.removeControl(`url-${this.urls.length - 1}`); + this.form.removeControl(`url-${this.urls.length - 1}`); this.urls.splice(index, 1); } @@ -94,7 +94,8 @@ export class FriendAddComponent implements OnInit { private getNotEmptyUrls() { const notEmptyUrls = []; - this.urls.forEach((url) => { + Object.keys(this.form.value).forEach((urlKey) => { + const url = this.form.value[urlKey]; if (url !== '') notEmptyUrls.push(url); }); 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 @@
{{ error }}
- +
-
- Username is required with a length >= 3 and <= 20 +
+ {{ formErrors.username }}
-
- Password is required with a length >= 6 +
+ {{ formErrors.password }}
- + 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 @@ import { Component, OnInit } from '@angular/core'; -import { FormGroup, FormControl, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { Router } from '@angular/router'; import { UserService } from '../shared'; +import { FormReactive, USER_USERNAME, USER_PASSWORD } from '../../../shared'; @Component({ selector: 'my-user-add', template: require('./user-add.component.html') }) -export class UserAddComponent implements OnInit { - userAddForm: FormGroup; +export class UserAddComponent extends FormReactive implements OnInit { error: string = null; - username = ''; - password = ''; - constructor(private router: Router, private userService: UserService) {} + form: FormGroup; + formErrors = { + 'username': '', + 'password': '' + }; + validationMessages = { + 'username': USER_USERNAME.MESSAGES, + 'password': USER_PASSWORD.MESSAGES, + }; - ngOnInit() { - this.userAddForm = new FormGroup({ - username: new FormControl('', [ Validators.required, Validators.minLength(3), Validators.maxLength(20) ]), - password: new FormControl('', [ Validators.required, Validators.minLength(6) ]), + constructor( + private formBuilder: FormBuilder, + private router: Router, + private userService: UserService + ) { + super(); + } + + buildForm() { + this.form = this.formBuilder.group({ + username: [ '', USER_USERNAME.VALIDATORS ], + password: [ '', USER_PASSWORD.VALIDATORS ], }); + + this.form.valueChanges.subscribe(data => this.onValueChanged(data)); + } + + ngOnInit() { + this.buildForm(); } addUser() { this.error = null; - this.userService.addUser(this.username, this.password).subscribe( + const { username, password } = this.form.value; + + this.userService.addUser(username, password).subscribe( ok => this.router.navigate([ '/admin/users/list' ]), 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 { VideoMiniatureComponent, VideoSortComponent, VideoWatchComponent, - VideoService + VideoService, + WebTorrentService } from './videos'; import { FriendsComponent, @@ -59,7 +60,7 @@ const APP_PROVIDERS = [ AuthService, RestExtractor, - RestExtractor, RestService, VideoService, SearchService, FriendService, UserService, AccountService + RestExtractor, RestService, VideoService, SearchService, FriendService, UserService, AccountService, WebTorrentService ]; /** * `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 @@
{{ error }}
-
+
-
- Username is required +
+ {{ formErrors.username }}
-
- Password is required +
+ {{ formErrors.password }}
- + 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 @@ import { Component, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; -import { AuthService } from '../shared'; +import { AuthService, FormReactive } from '../shared'; @Component({ selector: 'my-login', template: require('./login.component.html') }) -export class LoginComponent implements OnInit { +export class LoginComponent extends FormReactive implements OnInit { error: string = null; - username = ''; - password: ''; - loginForm: FormGroup; + + form: FormGroup; + formErrors = { + 'username': '', + 'password': '' + }; + validationMessages = { + 'username': { + 'required': 'Username is required.', + }, + 'password': { + 'required': 'Password is required.' + } + }; constructor( private authService: AuthService, + private formBuilder: FormBuilder, private router: Router - ) {} + ) { + super(); + } - ngOnInit() { - this.loginForm = new FormGroup({ - username: new FormControl('', [ Validators.required ]), - password: new FormControl('', [ Validators.required ]), + buildForm() { + this.form = this.formBuilder.group({ + username: [ '', Validators.required ], + password: [ '', Validators.required ], }); + + this.form.valueChanges.subscribe(data => this.onValueChanged(data)); + } + + ngOnInit() { + this.buildForm(); } login() { - this.authService.login(this.username, this.password).subscribe( - result => { - this.error = null; + this.error = null; + + const { username, password } = this.form.value; + + this.authService.login(username, password).subscribe( + result => this.router.navigate(['/videos/list']), - this.router.navigate(['/videos/list']); - }, error => { console.error(error.json); 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 @@ -export * from './url.validator'; diff --git a/client/src/app/shared/form-validators/url.validator.ts b/client/src/app/shared/form-validators/url.validator.ts deleted file mode 100644 index 67163b4e9..000000000 --- a/client/src/app/shared/form-validators/url.validator.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { FormControl } from '@angular/forms'; - -export function validateUrl(c: FormControl) { - let URL_REGEXP = new RegExp('^https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$'); - - return URL_REGEXP.test(c.value) ? null : { - validateUrl: { - valid: false - } - }; -} 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 @@ +import { FormGroup } from '@angular/forms'; + +export abstract class FormReactive { + abstract form: FormGroup; + abstract formErrors: Object; + abstract validationMessages: Object; + + abstract buildForm(): void; + + protected onValueChanged(data?: any) { + for (const field in this.formErrors) { + // clear previous error message (if any) + this.formErrors[field] = ''; + const control = this.form.get(field); + + if (control && control.dirty && !control.valid) { + const messages = this.validationMessages[field]; + for (const key in control.errors) { + this.formErrors[field] += messages[key] + ' '; + } + } + } + } +} 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 @@ +export * from './url.validator'; +export * from './user'; +export * from './video'; diff --git a/client/src/app/shared/forms/form-validators/url.validator.ts b/client/src/app/shared/forms/form-validators/url.validator.ts new file mode 100644 index 000000000..67163b4e9 --- /dev/null +++ b/client/src/app/shared/forms/form-validators/url.validator.ts @@ -0,0 +1,11 @@ +import { FormControl } from '@angular/forms'; + +export function validateUrl(c: FormControl) { + let URL_REGEXP = new RegExp('^https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$'); + + return URL_REGEXP.test(c.value) ? null : { + validateUrl: { + valid: false + } + }; +} 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 @@ +import { Validators } from '@angular/forms'; + +export const USER_USERNAME = { + VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(20) ], + MESSAGES: { + 'required': 'Username is required.', + 'minlength': 'Username must be at least 3 characters long.', + 'maxlength': 'Username cannot be more than 20 characters long.' + } +}; +export const USER_PASSWORD = { + VALIDATORS: [ Validators.required, Validators.minLength(6) ], + MESSAGES: { + 'required': 'Password is required.', + 'minlength': 'Password must be at least 6 characters long.', + } +}; 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 @@ +import { Validators } from '@angular/forms'; + +export const VIDEO_NAME = { + VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(50) ], + MESSAGES: { + 'required': 'Video name is required.', + 'minlength': 'Video name must be at least 3 characters long.', + 'maxlength': 'Video name cannot be more than 50 characters long.' + } +}; +export const VIDEO_DESCRIPTION = { + VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(250) ], + MESSAGES: { + 'required': 'Video description is required.', + 'minlength': 'Video description must be at least 3 characters long.', + 'maxlength': 'Video description cannot be more than 250 characters long.' + } +}; + +export const VIDEO_TAGS = { + VALIDATORS: [ Validators.pattern('^[a-zA-Z0-9]{2,10}$') ], + MESSAGES: { + 'pattern': 'A tag should be between 2 and 10 alphanumeric characters long.' + } +}; 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 @@ +export * from './form-validators'; +export * 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 @@ export * from './auth'; -export * from './form-validators'; +export * from './forms'; export * from './rest'; export * from './search'; export * 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 @@
{{ error }}
-
+
-
- A name is required and should be between 3 and 50 characters long +
+ {{ formErrors.name }}
-
- A tag should be between 2 and 10 characters (alphanumeric) long +
+ {{ formErrors.currentTag }}
-
+
{{ tag }} x
@@ -53,12 +53,12 @@
-
- A description is required and should be between 3 and 250 characters long +
+ {{ formErrors.description }}
@@ -69,7 +69,7 @@
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 @@ import { Component, ElementRef, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { Router } from '@angular/router'; import { FileUploader } from 'ng2-file-upload/ng2-file-upload'; -import { AuthService } from '../../shared'; +import { AuthService, FormReactive, VIDEO_NAME, VIDEO_DESCRIPTION, VIDEO_TAGS } from '../../shared'; @Component({ selector: 'my-videos-add', @@ -12,22 +12,31 @@ import { AuthService } from '../../shared'; template: require('./video-add.component.html') }) -export class VideoAddComponent implements OnInit { - currentTag: string; // Tag the user is writing in the input - error: string = null; - videoForm: FormGroup; +export class VideoAddComponent extends FormReactive implements OnInit { + tags: string[] = []; uploader: FileUploader; - video = { + + error: string = null; + form: FormGroup; + formErrors = { name: '', - tags: [], - description: '' + description: '', + currentTag: '' + }; + validationMessages = { + name: VIDEO_NAME.MESSAGES, + description: VIDEO_DESCRIPTION.MESSAGES, + currentTag: VIDEO_TAGS.MESSAGES }; constructor( private authService: AuthService, private elementRef: ElementRef, + private formBuilder: FormBuilder, private router: Router - ) {} + ) { + super(); + } get filename() { if (this.uploader.queue.length === 0) { @@ -37,20 +46,26 @@ export class VideoAddComponent implements OnInit { return this.uploader.queue[0].file.name; } - get isTagsInputDisabled () { - return this.video.tags.length >= 3; + buildForm() { + this.form = this.formBuilder.group({ + name: [ '', VIDEO_NAME.VALIDATORS ], + description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], + currentTag: [ '', VIDEO_TAGS.VALIDATORS ] + }); + + this.form.valueChanges.subscribe(data => this.onValueChanged(data)); } getInvalidFieldsTitle() { let title = ''; - const nameControl = this.videoForm.controls['name']; - const descriptionControl = this.videoForm.controls['description']; + const nameControl = this.form.controls['name']; + const descriptionControl = this.form.controls['description']; if (!nameControl.valid) { title += 'A name is required\n'; } - if (this.video.tags.length === 0) { + if (this.tags.length === 0) { title += 'At least one tag is required\n'; } @@ -66,13 +81,6 @@ export class VideoAddComponent implements OnInit { } ngOnInit() { - this.videoForm = new FormGroup({ - name: new FormControl('', [ Validators.required, Validators.minLength(3), Validators.maxLength(50) ]), - description: new FormControl('', [ Validators.required, Validators.minLength(3), Validators.maxLength(250) ]), - tags: new FormControl('', Validators.pattern('^[a-zA-Z0-9]{2,10}$')) - }); - - this.uploader = new FileUploader({ authToken: this.authService.getRequestHeaderValue(), queueLimit: 1, @@ -81,26 +89,37 @@ export class VideoAddComponent implements OnInit { }); this.uploader.onBuildItemForm = (item, form) => { - form.append('name', this.video.name); - form.append('description', this.video.description); + const name = this.form.value['name']; + const description = this.form.value['description']; + + form.append('name', name); + form.append('description', description); - for (let i = 0; i < this.video.tags.length; i++) { - form.append(`tags[${i}]`, this.video.tags[i]); + for (let i = 0; i < this.tags.length; i++) { + form.append(`tags[${i}]`, this.tags[i]); } }; + + this.buildForm(); } onTagKeyPress(event: KeyboardEvent) { + const currentTag = this.form.value['currentTag']; + // Enter press if (event.keyCode === 13) { // Check if the tag is valid and does not already exist if ( - this.currentTag !== '' && - this.videoForm.controls['tags'].valid && - this.video.tags.indexOf(this.currentTag) === -1 + currentTag !== '' && + this.form.controls['currentTag'].valid && + this.tags.indexOf(currentTag) === -1 ) { - this.video.tags.push(this.currentTag); - this.currentTag = ''; + this.tags.push(currentTag); + this.form.patchValue({ currentTag: '' }); + + if (this.tags.length >= 3) { + this.form.get('currentTag').disable(); + } } } } @@ -110,7 +129,7 @@ export class VideoAddComponent implements OnInit { } removeTag(tag: string) { - this.video.tags.splice(this.video.tags.indexOf(tag), 1); + this.tags.splice(this.tags.indexOf(tag), 1); } upload() {