Skip to content

Commit

Permalink
Merge pull request #69 from riot-appstore/pr/reactive_forms
Browse files Browse the repository at this point in the history
Add Reactive Forms support
  • Loading branch information
HendrikVE authored Jul 12, 2018
2 parents c66da86 + 5336fa3 commit 920be9b
Show file tree
Hide file tree
Showing 12 changed files with 208 additions and 97 deletions.
59 changes: 17 additions & 42 deletions rapstore-frontend/src/app/app-uploader/app-uploader.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,23 @@
<h3> Upload your RIOT app:</h3>
<div class="row">
<div class="col-sm-6">
<form (ngSubmit)="fileUpload()" #appUploadForm="ngForm">
<div [hidden]="!message" class="alert alert-success">
{{message}}
</div>
<div [hidden]="errors.length == 0" class="alert alert-danger">
<ul>
<li *ngFor="let error of errors">{{error}}</li>
</ul>
</div>
<div class="form-group">
<label for="name">App name</label>
<input type="text" class="form-control" name="name" [(ngModel)]="model.name" #name="ngModel" required/>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea ref-textarea name="description" #description="ngModel" [(ngModel)]="model.description"
class="form-control" rows=12></textarea>
</div>
<div class="form-group">
<label for="licenses">Licenses</label>
<input type="text" class="form-control" name="licenses" [(ngModel)]="model.licenses" #licenses="ngModel"
required/>
</div>
<div class="form-group">
<label for="project_page">Project page</label>
<input type="text" class="form-control" name="project_page" [(ngModel)]="model.project_page"
#project_page="ngModel" required/>
</div>
<!--<div class="form-group">
<label for="version_name">Version name</label>
<input type="text" class="form-control" name="version_name" [(ngModel)]="model.initial_instance.version_name" #version_name="ngModel" required />
</div>
<div class="form-group">
<label for="version_code">Version code</label>
<input type="text" class="form-control" name="version_code" [(ngModel)]="model.initial_instance.version_code" #version_code="ngModel" required />
</div>-->
<div class="form-group">
<label for="file">App in tar.gz format. Ensure the Makefile is in the root (e.g
my_app.tar.gz/Makefile)</label>
<input type="file" (change)="fileChanged($event)" name="file" accept=".tar.gz">
</div>
<button class="btn btn-primary" type="submit">Submit</button>
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div [hidden]="!message" class="alert alert-success">
{{message}}
</div>
<div [hidden]="errors.length == 0" class="alert alert-danger">
<ul>
<li *ngFor="let error of errors">{{error}}</li>
</ul>
</div>

<ng-container *ngFor="let el of elements">
<app-form-element [element]="el" [form]="form"></app-form-element>
</ng-container>

<app-form-element [element]="file_element" [form]="form" (notify_file)="fileChanged($event)"></app-form-element>

<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
Expand Down
43 changes: 29 additions & 14 deletions rapstore-frontend/src/app/app-uploader/app-uploader.component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {Component, OnInit} from '@angular/core';
import {Http, RequestOptions, Headers} from '@angular/http';
import {AuthService} from '../auth.service';
import {DynFormService} from '../dyn-form.service';
import {environment} from '../../environments/environment';
import {Application} from '../models';
import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms';
import { FormElementBase, TextboxElement, TextareaElement } from '../models';

@Component({
selector: 'app-app-uploader',
Expand All @@ -15,40 +18,52 @@ export class AppUploaderComponent implements OnInit {
message: string = '';
errors: string[] = [];
private baseurl = environment.apiUrl;
form: FormGroup;
elements: FormElementBase<any>[];
file_element: FormElementBase<any>;

constructor(private http: Http, private AuthService: AuthService, protected model: Application) {
this.model.initial_instance = {id: 0, version_name: '', version_code: 0};
constructor(private http: Http, private AuthService: AuthService, private fb: FormBuilder, private df: DynFormService) {
this.file_element = new FormElementBase({key: "file", label: "File", controlType: "file"});
this.elements = [
new TextboxElement({key: "name", label: "App name", required: true}),
new TextareaElement({key: "description", label: "Description"}),
new TextboxElement({key: "licenses", label: "Licenses"}),
new TextboxElement({key: "project_page", label: "Project page"}),
];

this.form = df.toFormGroup(this.elements.concat([this.file_element]));
}

ngOnInit() {
}

fileUpload() {
if (this.file && this.model.name) {
onSubmit() {
if(this.form.valid) {
let values = this.form.value;
this.errors = [];
this.message = '';
let formData: FormData = new FormData();
formData.append('name', this.model.name);
formData.append('description', this.model.description);
formData.append('licenses', this.model.licenses);
console.log(this.form.controls)
formData.append('name', values.name);
formData.append('description', values.description);
formData.append('licenses', values.licenses);

let project_page = this.model.project_page;
if (project_page) {
formData.append('project_page', project_page);
if(values.project_page) {
formData.append('project_page', values.project_page);
}

formData.append('app_tarball', this.file, this.file.name);
formData.append('initial_instance.version_name', 'VERSION');
formData.append('initial_instance.version_code', '0');
let headers = new Headers();

let headers = new Headers();
headers.append('Authorization', 'Token ' + this.AuthService.get_token());
headers.append('Accept', 'application/json');
let options = new RequestOptions({headers: headers});
this.http.post(`${this.baseurl}/api/app/`, formData, options)
.map(res => res.json())
.subscribe(
data => this.message = `Successfully uploaded your app "${this.model.name}" ! The app will be under a review process in order to make it public.`,
data => this.message = `Successfully uploaded your app "${this.form.value.name}" ! The app will be under a review process in order to make it public.`,
err => {
let errors = JSON.parse(err.text());
for (let k in errors) {
Expand All @@ -59,8 +74,8 @@ export class AppUploaderComponent implements OnInit {
}
}

fileChanged(event) {
this.file = event.target.files.length > 0 && event.target.files[0];
fileChanged(file: File) {
this.file = file;
}

}
7 changes: 6 additions & 1 deletion rapstore-frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';
import {NguUtilityModule} from 'ngu-utility/dist';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule } from '@angular/forms';


import {AppComponent} from './app.component';
Expand All @@ -15,6 +16,7 @@ import {AppService} from './appservice.service';
import {AppBuildComponent} from './app-build/app-build.component';
import {BoardService} from './board.service';
import {AuthService} from './auth.service';
import {DynFormService} from './dyn-form.service';
import {AuthGuard} from './auth-guard';
import {LoginComponent} from './login/login.component';
import {NavbarComponent} from './navbar/navbar.component';
Expand All @@ -34,6 +36,7 @@ import { AboutComponent } from './about/about.component';
import { LogoutComponent } from './logout/logout.component';
import { FeedbackComponent } from './feedback/feedback.component';
import { SimpleNotificationsModule } from 'angular2-notifications';
import { FormElementComponent } from './form-element/form-element.component';


@NgModule({
Expand All @@ -55,19 +58,21 @@ import { SimpleNotificationsModule } from 'angular2-notifications';
ImprintComponent,
AboutComponent,
LogoutComponent,
FormElementComponent,
FeedbackComponent,
],
imports: [
CommonModule,
BrowserModule,
FormsModule,
ReactiveFormsModule,
NguUtilityModule,
AppRoutingModule,
HttpModule,
BrowserAnimationsModule,
SimpleNotificationsModule.forRoot()
],
providers: [AppService, BoardService, AuthService, AuthGuard, BrowserIntegrationService, UserService, FeedbackService],
providers: [AppService, BoardService, AuthService, AuthGuard, BrowserIntegrationService, UserService, FeedbackService, DynFormService],
bootstrap: [AppComponent]
})
export class AppModule {
Expand Down
15 changes: 15 additions & 0 deletions rapstore-frontend/src/app/dyn-form.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';

import { DynFormService } from './dyn-form.service';

describe('DynFormService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [DynFormService]
});
});

it('should be created', inject([DynFormService], (service: DynFormService) => {
expect(service).toBeTruthy();
}));
});
19 changes: 19 additions & 0 deletions rapstore-frontend/src/app/dyn-form.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Injectable } from '@angular/core';
import { FormElementBase } from './models';
import { FormControl, FormGroup, Validators } from '@angular/forms';

@Injectable()
export class DynFormService {

constructor() { }

toFormGroup(elements: FormElementBase<any>[] ) {
let group: any = {};

elements.forEach(el => {
group[el.key] = el.required ? new FormControl(el.value || '', Validators.required)
: new FormControl(el.value || '');
});
return new FormGroup(group);
}
}
Empty file.
11 changes: 11 additions & 0 deletions rapstore-frontend/src/app/form-element/form-element.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div [formGroup]="form" class="form-group">
<label class="center-block">{{element.label}}</label>

<div [ngSwitch]="element.controlType">
<input *ngSwitchCase="'textbox'" [formControlName]="element.key"
[id]="element.key" [type]="element.type" class="form-control">
<textarea *ngSwitchCase="'textarea'" ref-textarea [formControlName]="element.key" class="form-control" rows=12></textarea>
<input *ngSwitchCase="'file'" type="file" accept=".tar.gz" [formControlName]="element.key" (change)="onChange($event)">
</div>
<!--<div class="errorMessage" *ngIf="!isValid">{{question.label}} is required</div>-->
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { FormElementComponent } from './form-element.component';

describe('FormElementComponent', () => {
let component: FormElementComponent;
let fixture: ComponentFixture<FormElementComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FormElementComponent ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(FormElementComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
21 changes: 21 additions & 0 deletions rapstore-frontend/src/app/form-element/form-element.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { FormElementBase } from '../models';
import { FormGroup} from '@angular/forms';

@Component({
selector: 'app-form-element',
templateUrl: './form-element.component.html',
styleUrls: ['./form-element.component.css']
})

export class FormElementComponent {
@Input() element: FormElementBase<any>;
@Input() form: FormGroup;
@Output() notify_file: EventEmitter<File> = new EventEmitter<File>();
// get isValid() { return this.form.controls[this.element.key].valid; }
onChange(event) {
let file: File = event.target.files.length > 0 && event.target.files[0];
this.notify_file.emit(file);
}

}
40 changes: 40 additions & 0 deletions rapstore-frontend/src/app/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,43 @@ export class Signup {
export class Feedback {
description: string;
}

export class FormElementBase<T> {
value: T;
key: string;
label: string;
required: boolean;
order: number;
controlType: string;

constructor(options: {
value?: T,
key?: string,
label?: string,
required?: boolean,
order?: number,
controlType?: string
} = {}) {
this.value = options.value;
this.key = options.key || '';
this.label = options.label || '';
this.required = !!options.required;
this.order = options.order === undefined ? 1 : options.order;
this.controlType = options.controlType || '';
}
}

export class TextboxElement extends FormElementBase<string> {
controlType = 'textbox';
type: string;

constructor(options: {} = {}) {
super(options);
this.type = options['type'] || '';
}
}

export class TextareaElement extends FormElementBase<string> {
controlType = 'textarea';
type: string;
}
41 changes: 6 additions & 35 deletions rapstore-frontend/src/app/userprofile/userprofile.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,12 @@ <h3>User Profile:</h3>
<p><strong>Gender: </strong>{{user?.gender}}</p>
<p><strong>Phone number: </strong>{{user?.phone_number}}</p>
</div>
<form [hidden]="!edit" (ngSubmit)="update()" #updateUserForm="ngForm">
<div class="form-group">
<label for="first_name">First name</label>
<input type="text" class="form-control" name="first_name" [(ngModel)]="model.first_name" #first_name="ngModel"
required/>
</div>
<div class="form-group">
<label for="last_name">Last name</label>
<input type="text" class="form-control" name="last_name" [(ngModel)]="model.last_name" #last_name="ngModel"
required/>
</div>
<div class="form-group">
<label for="location">Location</label>
<input type="text" class="form-control" name="location" [(ngModel)]="model.location" #location="ngModel"
required/>
</div>
<div class="form-group">
<label for="company">Company</label>
<input type="text" class="form-control" name="company" [(ngModel)]="model.company" #company="ngModel" required/>
</div>
<div class="form-group">
<label for="gender">Gender</label>
<select required class="form-control" [ngModel]="model.gender" (change)="model.gender=gender.value"
name="gender" #gender>
<option value="M">Male</option>
<option value="F">Female</option>
</select>
</div>
<div class="form-group">
<label for="phone_number">Phone number</label>
<input type="text" class="form-control" name="phone_number" [(ngModel)]="model.phone_number"
#phone_number="ngModel" required/>
</div>
<input type="button" (click)="set_edit(false)" value="Cancel"/> <!-- add cancel logic -->
<input type="submit" value="OK"/>
<form [hidden]="!edit" (ngSubmit)="update()" [formGroup]="form">
<ng-container *ngFor="let el of elements">
<app-form-element [element]="el" [form]="form"></app-form-element>
</ng-container>
<input type="button" (click)="set_edit(false)" value="Cancel"/> <!-- add cancel logic -->
<input type="submit" value="OK"/>
</form>
</div>
<div class="col-md-8" [hidden]="edit">
Expand Down
Loading

0 comments on commit 920be9b

Please sign in to comment.