cosmetic tuning

merge-requests/1/head
Florian Hartwich 2017-04-07 02:55:42 +02:00
parent 7ed157625b
commit 27d92ed2dd
56 changed files with 511 additions and 439 deletions

View File

@ -17,14 +17,14 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/common": "^4.0.0", "@angular/common": "4.0.0",
"@angular/compiler": "^4.0.0", "@angular/compiler": "4.0.0",
"@angular/core": "^4.0.0", "@angular/core": "4.0.0",
"@angular/forms": "^4.0.0", "@angular/forms": "4.0.0",
"@angular/http": "^4.0.0", "@angular/http": "4.0.0",
"@angular/platform-browser": "^4.0.0", "@angular/platform-browser": "4.0.0",
"@angular/platform-browser-dynamic": "^4.0.0", "@angular/platform-browser-dynamic": "4.0.0",
"@angular/router": "^4.0.0", "@angular/router": "4.0.0",
"bootstrap": "^3.3.7", "bootstrap": "^3.3.7",
"core-js": "^2.4.1", "core-js": "^2.4.1",
"jquery": "^3.1.0", "jquery": "^3.1.0",
@ -38,7 +38,7 @@
}, },
"devDependencies": { "devDependencies": {
"@angular/cli": "1.0.0", "@angular/cli": "1.0.0",
"@angular/compiler-cli": "^4.0.0", "@angular/compiler-cli": "4.0.0",
"@types/jasmine": "2.5.38", "@types/jasmine": "2.5.38",
"@types/node": "~6.0.60", "@types/node": "~6.0.60",
"@types/socket.io-client": "^1.4.26", "@types/socket.io-client": "^1.4.26",

View File

@ -8,6 +8,7 @@ import {Component} from '@angular/core';
}) })
export class AboutComponent { export class AboutComponent {
constructor() {} constructor() {
}
} }

View File

@ -13,11 +13,11 @@ li {
padding-right: 15px; padding-right: 15px;
} }
.right { .right {
float: right; float: right;
width: 300px; width: 300px;
} }
.left { .left {
float: left; float: left;
width: calc(100% - 300px); width: calc(100% - 300px);

View File

@ -50,24 +50,25 @@
</li> </li>
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">
Schnellzugriff Schnellzugriff
<span class="caret"></span> <span class="caret"></span>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li> <li>
<a [routerLink]="['/tasks']" [queryParams]="{query: 'BACKLOG'}">Backlog</a> <a [routerLink]="['/tasks']" [queryParams]="{query: 'BACKLOG'}">Backlog</a>
</li> </li>
<li> <li>
<a [routerLink]="['/tasks']" [queryParams]="{query: 'IN_PROGRESS'}">In Bearbeitung</a> <a [routerLink]="['/tasks']" [queryParams]="{query: 'IN_PROGRESS'}">In Bearbeitung</a>
</li> </li>
<li> <li>
<a [routerLink]="['/tasks']" [queryParams]="{query: 'TEST'}">Im Test</a> <a [routerLink]="['/tasks']" [queryParams]="{query: 'TEST'}">Im Test</a>
</li> </li>
<li> <li>
<a [routerLink]="['/tasks']" [queryParams]="{query: 'COMPLETED'}">Abgeschlossen</a> <a [routerLink]="['/tasks']" [queryParams]="{query: 'COMPLETED'}">Abgeschlossen</a>
</li> </li>
</ul> </ul>
</li> </li>
<li *ngIf="authEnabled"> <li *ngIf="authEnabled">
@ -81,10 +82,10 @@
</div> </div>
<div class="content"> <div class="content">
<div id="left"> <div id="left">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>
<div id="right"> <div id="right">
<router-outlet name="right"></router-outlet> <router-outlet name="right"></router-outlet>
</div> </div>
</div> </div>

View File

@ -33,7 +33,7 @@ export class AppComponent {
ngOnInit() { ngOnInit() {
this.defaultTitle = this.titleService.getTitle(); this.defaultTitle = this.titleService.getTitle();
this.router.events this.router.events
//.do(event => console.log(event)) //.do(event => console.log(event))
.filter(event => event instanceof NavigationEnd) .filter(event => event instanceof NavigationEnd)
.subscribe(event => { .subscribe(event => {
this.setBrowserTitle(); this.setBrowserTitle();
@ -41,7 +41,7 @@ export class AppComponent {
this.taskService.tasks$.subscribe((tasks) => { this.taskService.tasks$.subscribe((tasks) => {
this.numberInProgress = tasks.filter( this.numberInProgress = tasks.filter(
(task:Task)=> task.state === 'IN_PROGRESS').length; (task: Task) => task.state === 'IN_PROGRESS').length;
}); });
} }
@ -51,7 +51,7 @@ export class AppComponent {
let title = this.defaultTitle; let title = this.defaultTitle;
let route = this.activatedRoute; let route = this.activatedRoute;
// firstChild gibt die Haupt-Kindroute der übergebenen Route zurück // firstChild gibt die Haupt-Kindroute der übergebenen Route zurück
while(route.firstChild) { while (route.firstChild) {
console.log() console.log()
route = route.firstChild; route = route.firstChild;
title = route.snapshot.data['title'] || title; title = route.snapshot.data['title'] || title;

View File

@ -15,11 +15,11 @@ import {ModelDrivenFormComponent} from "./model-driven-form/model-driven-form.co
export const appRoutes: Routes = [ export const appRoutes: Routes = [
{path: 'dashboard', component: DashboardComponent, data: {title: 'Startseite'}}, {path: 'dashboard', component: DashboardComponent, data: {title: 'Startseite'}},
{path: '', redirectTo: '/dashboard', pathMatch: 'full'}, {path: '', redirectTo: '/dashboard', pathMatch: 'full'},
{path: 'settings', component: SettingsComponent, data: { title: 'Einstellungen' }}, {path: 'settings', component: SettingsComponent, data: {title: 'Einstellungen'}},
{path: 'about', component: AboutComponent, data: {title: 'Über uns'}}, {path: 'about', component: AboutComponent, data: {title: 'Über uns'}},
{path: 'rxdemo', component: RxDemoComponent, data: {title: 'RxJS Demo'}}, {path: 'rxdemo', component: RxDemoComponent, data: {title: 'RxJS Demo'}},
{path: 'blog', component: BlogComponent, data: {title: 'Blog'}}, {path: 'blog', component: BlogComponent, data: {title: 'Blog'}},
{path: 'model-form', component: ModelDrivenFormComponent, data: { title: 'Model Driven Task Form' }}, {path: 'model-form', component: ModelDrivenFormComponent, data: {title: 'Model Driven Task Form'}},
{path: 'login', component: LoginComponent}, {path: 'login', component: LoginComponent},

View File

@ -1,14 +1,14 @@
<div class="blog-entry"> <div class="blog-entry">
<div class="blog-image"> <div class="blog-image">
<img [src]="entry.image" [alt]="entry.title"/> <img [src]="entry.image" [alt]="entry.title"/>
</div> </div>
<div class="blog-summary"> <div class="blog-summary">
<span class="title">{{entry.title}}</span> <span class="title">{{entry.title}}</span>
<p>{{entry.text}}</p> <p>{{entry.text}}</p>
</div> </div>
<div class="blog-delete"> <div class="blog-delete">
<button (click)="deleteBlogEntry(entry.id)">Entfernen</button> <button (click)="deleteBlogEntry(entry.id)">Entfernen</button>
</div> </div>
<div class="blog-timestamp"> <div class="blog-timestamp">
<hr> <hr>
<span class="ts-title">created at:</span> <span class="ts-title">created at:</span>

View File

@ -12,9 +12,9 @@ describe('Blog Entry Isolated Test', () => {
declarations: [BlogEntryComponent] declarations: [BlogEntryComponent]
}); });
const fixture = TestBed.createComponent(BlogEntryComponent); const fixture = TestBed.createComponent(BlogEntryComponent);
const blogEntryComponent : BlogEntryComponent = fixture.componentInstance; const blogEntryComponent: BlogEntryComponent = fixture.componentInstance;
const element = fixture.nativeElement; const element = fixture.nativeElement;
const blogEntry : BlogEntry = new BlogEntry; const blogEntry: BlogEntry = new BlogEntry;
// Testdaten // Testdaten
const testId = 101; const testId = 101;
@ -60,19 +60,19 @@ describe('Blog Entry Isolated Test', () => {
describe('BlogEntryComp -> BlogComp Relational Test', () => { describe('BlogEntryComp -> BlogComp Relational Test', () => {
it ('should remove entry from BlogComponent on "delete" button click', () => { it('should remove entry from BlogComponent on "delete" button click', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes([])], imports: [RouterTestingModule.withRoutes([])],
declarations: [BlogEntryComponent, BlogComponent] declarations: [BlogEntryComponent, BlogComponent]
}); });
const entryFixture = TestBed.createComponent(BlogEntryComponent); const entryFixture = TestBed.createComponent(BlogEntryComponent);
const entryInstance : BlogEntryComponent = entryFixture.componentInstance; const entryInstance: BlogEntryComponent = entryFixture.componentInstance;
const blogFixture = TestBed.createComponent(BlogComponent); const blogFixture = TestBed.createComponent(BlogComponent);
const blogInstance : BlogComponent = blogFixture.componentInstance; const blogInstance: BlogComponent = blogFixture.componentInstance;
const blogEntry : BlogEntry = blogInstance.entries[0]; const blogEntry: BlogEntry = blogInstance.entries[0];
entryInstance.entry = blogEntry; entryInstance.entry = blogEntry;
entryInstance.blogComponent = blogInstance; entryInstance.blogComponent = blogInstance;

View File

@ -11,7 +11,7 @@ export class BlogEntryComponent {
@Input() blogComponent: BlogComponent; @Input() blogComponent: BlogComponent;
@Input() entry: BlogEntry; @Input() entry: BlogEntry;
deleteBlogEntry(id:number) { deleteBlogEntry(id: number) {
this.blogComponent.deleteBlogEntry(id); this.blogComponent.deleteBlogEntry(id);
} }

View File

@ -1,5 +1,5 @@
export class BlogEntry { export class BlogEntry {
id : number; id: number;
title: string; title: string;
text: string; text: string;
image: string; image: string;

View File

@ -6,11 +6,11 @@
<div class="control"> <div class="control">
<label for="title">Titel:</label> <label for="title">Titel:</label>
<input type="text" id="title" #title /> <input type="text" id="title" #title/>
</div> </div>
<div class="control"> <div class="control">
<label for="title">Bild-URL:</label> <label for="title">Bild-URL:</label>
<input type="text" id="image" #image /> <input type="text" id="image" #image/>
</div> </div>
<div class="control"> <div class="control">
<label for="text">Text:</label> <label for="text">Text:</label>

View File

@ -5,7 +5,7 @@ describe('Blog Component Isolated Test', () => {
let blogComponent: BlogComponent; let blogComponent: BlogComponent;
beforeEach(() => { beforeEach(() => {
blogComponent = new BlogComponent(null, null, null); blogComponent = new BlogComponent(null, null, null);
}); });
it('should have initial entries', () => { it('should have initial entries', () => {

View File

@ -29,11 +29,11 @@ export class BlogComponent {
title: "Neue Vorschläge für die kommende Module-Loader-Syntax", title: "Neue Vorschläge für die kommende Module-Loader-Syntax",
image: "https://cdn-images-1.medium.com/max/1000/1*RQFLsbQumE-iNrXzs_Oz8g.jpeg", image: "https://cdn-images-1.medium.com/max/1000/1*RQFLsbQumE-iNrXzs_Oz8g.jpeg",
text: "Nachdem im ES2015-Standard bereits die Module-API verabschiedet wurde, hat das zuständige Konsortium nun neue Vorschläge für die Module-Loader-Syntax bekannt gegeben...", text: "Nachdem im ES2015-Standard bereits die Module-API verabschiedet wurde, hat das zuständige Konsortium nun neue Vorschläge für die Module-Loader-Syntax bekannt gegeben...",
createdAt : null createdAt: null
} }
]; ];
this.entries = initialEntries; this.entries = initialEntries;
this.entries.forEach((entry)=> { this.entries.forEach((entry) => {
entry.createdAt = new Date(); entry.createdAt = new Date();
if (this.id < entry.id) { if (this.id < entry.id) {
this.id = entry.id; this.id = entry.id;
@ -41,7 +41,7 @@ export class BlogComponent {
}); });
} }
createBlogEntry(title:string, image:string, text:string) { createBlogEntry(title: string, image: string, text: string) {
this.id++; this.id++;
let entry = new BlogEntry(); let entry = new BlogEntry();
entry.id = this.id; entry.id = this.id;
@ -53,7 +53,7 @@ export class BlogComponent {
this.entries.push(entry); this.entries.push(entry);
} }
deleteBlogEntry(id:number) { deleteBlogEntry(id: number) {
let entryIndex = this.entries.findIndex(entry => entry.id === id); let entryIndex = this.entries.findIndex(entry => entry.id === id);
if (entryIndex >= 0) { if (entryIndex >= 0) {
this.entries.splice(entryIndex, 1); this.entries.splice(entryIndex, 1);

View File

@ -11,6 +11,7 @@ import {Title} from '@angular/platform-browser';
export class DashboardComponent { export class DashboardComponent {
title: string; title: string;
constructor(r: ActivatedRoute, private router: Router, private titleService: Title) { constructor(r: ActivatedRoute, private router: Router, private titleService: Title) {
} }
@ -22,6 +23,7 @@ export class DashboardComponent {
this.titleService.setTitle(this.title); this.titleService.setTitle(this.title);
} }
} }
ngOnDestroy() { ngOnDestroy() {
this.titleService.setTitle(this.originalTitle); this.titleService.setTitle(this.originalTitle);
} }

View File

@ -1,19 +1,18 @@
.form-signin .form-signin {
{
max-width: 330px; max-width: 330px;
padding: 15px; padding: 15px;
margin: 0 auto; margin: 0 auto;
} }
.form-signin .form-signin-heading, .form-signin .checkbox
{ .form-signin .form-signin-heading, .form-signin .checkbox {
margin-bottom: 10px; margin-bottom: 10px;
} }
.form-signin .checkbox
{ .form-signin .checkbox {
font-weight: normal; font-weight: normal;
} }
.form-signin .form-control
{ .form-signin .form-control {
position: relative; position: relative;
font-size: 16px; font-size: 16px;
height: auto; height: auto;
@ -22,24 +21,24 @@
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
} }
.form-signin .form-control:focus
{ .form-signin .form-control:focus {
z-index: 2; z-index: 2;
} }
.form-signin input[type="text"]
{ .form-signin input[type="text"] {
margin-bottom: -1px; margin-bottom: -1px;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
.form-signin input[type="password"]
{ .form-signin input[type="password"] {
margin-bottom: 10px; margin-bottom: 10px;
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
} }
.account-wall
{ .account-wall {
margin-top: 20px; margin-top: 20px;
padding: 40px 0px 20px 0px; padding: 40px 0px 20px 0px;
background-color: #f7f7f7; background-color: #f7f7f7;
@ -47,15 +46,15 @@
-webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
} }
.login-title
{ .login-title {
color: #555; color: #555;
font-size: 18px; font-size: 18px;
font-weight: 400; font-weight: 400;
display: block; display: block;
} }
.profile-img
{ .profile-img {
width: 96px; width: 96px;
height: 96px; height: 96px;
margin: 0 auto 10px; margin: 0 auto 10px;
@ -64,12 +63,12 @@
-webkit-border-radius: 50%; -webkit-border-radius: 50%;
border-radius: 50%; border-radius: 50%;
} }
.need-help
{ .need-help {
margin-top: 10px; margin-top: 10px;
} }
.new-account
{ .new-account {
display: block; display: block;
margin-top: 10px; margin-top: 10px;
} }

View File

@ -1,14 +1,14 @@
<form class="form-signin" (ngSubmit)="login(userName.value, password.value)"> <form class="form-signin" (ngSubmit)="login(userName.value, password.value)">
<div class="row"> <div class="row">
<h2 style="text-align: center;" class="form-signin-heading">Anmelden</h2> <h2 style="text-align: center;" class="form-signin-heading">Anmelden</h2>
<label for="inputEmail" class="sr-only">Benutzername</label> <label for="inputEmail" class="sr-only">Benutzername</label>
<input #userName id="inputEmail" class="form-control" placeholder="Benutzername" required="" autofocus=""> <input #userName id="inputEmail" class="form-control" placeholder="Benutzername" required="" autofocus="">
<label for="inputPassword" class="sr-only">Passwort</label> <label for="inputPassword" class="sr-only">Passwort</label>
<input #password type="password" id="inputPassword" class="form-control" placeholder="Passwort" required=""> <input #password type="password" id="inputPassword" class="form-control" placeholder="Passwort" required="">
<button class="btn btn-lg btn-primary btn-block" type="submit">Anmelden</button> <button class="btn btn-lg btn-primary btn-block" type="submit">Anmelden</button>
</div> </div>
</form> </form>

View File

@ -9,7 +9,7 @@ import {NgForm} from "@angular/forms";
styleUrls: ['./login.component.css'] styleUrls: ['./login.component.css']
}) })
export class LoginComponent { export class LoginComponent {
@ViewChild(NgForm) form : NgForm; @ViewChild(NgForm) form: NgForm;
constructor(private loginService: LoginService, constructor(private loginService: LoginService,

View File

@ -1,10 +1,12 @@
import { Injectable } from '@angular/core'; import {Injectable} from '@angular/core';
import {CanActivate, import {
Router, CanActivate,
ActivatedRouteSnapshot, Router,
RouterStateSnapshot } from '@angular/router'; ActivatedRouteSnapshot,
import { Observable } from 'rxjs/Observable'; RouterStateSnapshot
import { LoginService } from "../services/login-service/login-service"; } from '@angular/router';
import {Observable} from 'rxjs/Observable';
import {LoginService} from "../services/login-service/login-service";
@Injectable() @Injectable()
export class LoginGuard implements CanActivate { export class LoginGuard implements CanActivate {

View File

@ -1,14 +1,16 @@
export class MockSocket { export class MockSocket {
emit(action, data) { emit(action, data) {
} }
addEventListener(l) { addEventListener(l) {
} }
removeEventListener(l) { removeEventListener(l) {
} }
} }
export function mockIO(): any { export function mockIO(): any {
return new MockSocket(); return new MockSocket();
} }

View File

@ -28,14 +28,14 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<button class="btn btn-success" (click)="addTag()"> + </button> <button class="btn btn-success" (click)="addTag()"> +</button>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Status</label> <label>Status</label>
<select formControlName="state" class="form-control"> <select formControlName="state" class="form-control">
<option *ngFor="let state of model.states" [value]="state"> <option *ngFor="let state of model.states" [value]="state">
{{model.stateTexts[state]}} {{model.stateTexts[state]}}
</option> </option>
</select> </select>
</div> </div>

View File

@ -4,12 +4,15 @@ import {ModelDrivenFormComponent} from './model-driven-form.component';
import {UserService} from '../services/user-service/user.service'; import {UserService} from '../services/user-service/user.service';
import {TaskServiceModelForm} from "../services/task-service/task-model-form.service"; import {TaskServiceModelForm} from "../services/task-service/task-model-form.service";
import {ShowErrorComponentModelDriven} from "../show-error/show-error-model-driven.component"; import {ShowErrorComponentModelDriven} from "../show-error/show-error-model-driven.component";
import {generateRandomString} from "../../test/test.helper"; import {generateRandomString} from "../test/test.helper";
describe('Model Driven Form', () => { describe('Model Driven Form', () => {
let fixture ; // ModelDrivenFormComponent Fixture
let form ; let fixture;
// main FormGroup
let form;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@ -30,16 +33,20 @@ describe('Model Driven Form', () => {
it('should show error on tag name provided with gt 0 and lt 3', () => { it('should show error on tag name provided with gt 0 and lt 3', () => {
// "add tag" Aufruf starten -> neue FormGroup wird erstellt // "add tag" Aufruf starten -> neue FormGroup wird erstellt
const tagControlGroup : FormGroup = fixture.componentInstance.createTagControl() const tagControlGroup: FormGroup = fixture.componentInstance.createTagControl()
const tagLabel = tagControlGroup.get('label'); const tagLabel = tagControlGroup.get('label');
expectErrorOnInput(tagLabel, ''); expectErrorOnInput(tagLabel, '');
expectErrorOnInput(tagLabel, 'xyz');
expectErrorOnInput(tagLabel, 'xy', {requiredLength: 3, actualLength: 2}, 'minlength'); expectErrorOnInput(tagLabel, 'xy', {requiredLength: 3, actualLength: 2}, 'minlength');
}); });
it('should show error in description gt 2000 signs', () => { it('should show error in description gt 2000 signs', () => {
const descriptionControl = form.get('description'); const descriptionControl = form.get('description');
expectErrorOnInput(descriptionControl, generateRandomString(2000)); expectErrorOnInput(descriptionControl, generateRandomString(2000));
expectErrorOnInput(descriptionControl, generateRandomString(2001), {requiredLength: 2000, actualLength: 2001}, 'maxlength'); expectErrorOnInput(descriptionControl, generateRandomString(2001), {
requiredLength: 2000,
actualLength: 2001
}, 'maxlength');
}); });
it('should show error on email not matching email-pattern', () => { it('should show error on email not matching email-pattern', () => {
@ -52,16 +59,17 @@ describe('Model Driven Form', () => {
expectErrorOnInput(assigneeEMail, 'test@web.de'); expectErrorOnInput(assigneeEMail, 'test@web.de');
}); });
function expectErrorOnInput(formControl, inputString, expectedErrorObject?, expectedErrorAttribute?){ // helper method
// check for error object according to given input value
function expectErrorOnInput(formControl, inputString, expectedErrorObject?, expectedErrorAttribute?) {
formControl.setValue(inputString); formControl.setValue(inputString);
if (expectedErrorObject && expectedErrorAttribute) { if (expectedErrorObject && expectedErrorAttribute) {
expect(formControl.errors[expectedErrorAttribute]).toEqual(expectedErrorObject); expect(formControl.errors[expectedErrorAttribute]).toEqual(expectedErrorObject);
} else if (expectedErrorObject) { } else if (expectedErrorObject) {
expect(formControl.errors).toEqual(expectedErrorObject); expect(formControl.errors).toEqual(expectedErrorObject);
} else { } else {
expect(formControl.errors).toBeNull(); expect(formControl.errors).toBeNull();
} }
} }
}); });

View File

@ -2,7 +2,7 @@ import {Component} from '@angular/core';
import {FormGroup, FormArray, FormControl, FormBuilder, Validators} from '@angular/forms'; import {FormGroup, FormArray, FormControl, FormBuilder, Validators} from '@angular/forms';
import {Task, createInitialTask} from '../models/model-interfaces'; import {Task, createInitialTask} from '../models/model-interfaces';
import * as model from '../models/model-interfaces'; import * as model from '../models/model-interfaces';
import {ifNotBacklogThanAssignee, emailValidator, UserExistsValidatorDirective} from '../models/app-validators'; import {ifNotBacklogThanAssignee, emailValidator, UserExistsValidatorDirective} from '../models/app-validators';
import {UserService} from '../services/user-service/user.service'; import {UserService} from '../services/user-service/user.service';
import {TaskServiceModelForm} from "../services/task-service/task-model-form.service"; import {TaskServiceModelForm} from "../services/task-service/task-model-form.service";
@ -42,21 +42,21 @@ export class ModelDrivenFormComponent {
this.tagsArray = <FormArray>this.taskForm.controls['tags']; this.tagsArray = <FormArray>this.taskForm.controls['tags'];
/* /*
this.taskForm = new FormGroup({ this.taskForm = new FormGroup({
title: new FormControl(''), title: new FormControl(''),
description: new FormControl(''), description: new FormControl(''),
favorite: new FormControl(false), favorite: new FormControl(false),
state: new FormControl('BACKLOG'), state: new FormControl('BACKLOG'),
tags: new FormArray([ tags: new FormArray([
new FormGroup({ new FormGroup({
label: new FormControl('') label: new FormControl('')
}) })
]), ]),
assignee: new FormGroup({ assignee: new FormGroup({
name: new FormControl(''), name: new FormControl(''),
email: new FormControl('') email: new FormControl('')
}), }),
});*/ });*/
} }
private createTagControl(): FormGroup { private createTagControl(): FormGroup {
@ -82,7 +82,7 @@ export class ModelDrivenFormComponent {
} }
loadTask(id: number) { loadTask(id: number) {
const task : Task = this.taskService.getTask(id); const task: Task = this.taskService.getTask(id);
this.adjustTagsArray(task.tags); this.adjustTagsArray(task.tags);
this.taskForm.patchValue(task); this.taskForm.patchValue(task);
this.task = task; this.task = task;
@ -94,7 +94,7 @@ export class ModelDrivenFormComponent {
while (tagCount > this.tagsArray.controls.length) { while (tagCount > this.tagsArray.controls.length) {
this.addTag(); this.addTag();
} }
while (tagCount < this.tagsArray.controls.length) { while (tagCount < this.tagsArray.controls.length) {
this.removeTag(0); this.removeTag(0);
} }
} }

View File

@ -16,7 +16,7 @@ export function asyncIfNotBacklogThenAssignee(control): Promise<any> {
return promise; return promise;
} }
export function ifNotBacklogThanAssignee(formGroup: FormControl): {[key: string]: any} { export function ifNotBacklogThanAssignee(formGroup: FormControl): { [key: string]: any } {
const nameControl = formGroup.get('assignee.name'); const nameControl = formGroup.get('assignee.name');
const stateControl = formGroup.get('state'); const stateControl = formGroup.get('state');
if (!nameControl || !stateControl) { if (!nameControl || !stateControl) {
@ -39,7 +39,7 @@ export function ifNotBacklogThanAssignee(formGroup: FormControl): {[key: string]
}) })
export class IfNotBacklogThanAssigneeValidatorDirective { export class IfNotBacklogThanAssigneeValidatorDirective {
public validate(formGroup: AbstractControl): {[key: string]: any} { public validate(formGroup: AbstractControl): { [key: string]: any } {
const nameControl = formGroup.get('assignee.name'); const nameControl = formGroup.get('assignee.name');
const stateControl = formGroup.get('state'); const stateControl = formGroup.get('state');
if (!nameControl || !stateControl) { if (!nameControl || !stateControl) {
@ -61,7 +61,7 @@ export class IfNotBacklogThanAssigneeValidatorDirective {
}] }]
}) })
export class EmailValidatorDirective { export class EmailValidatorDirective {
validate(control: AbstractControl): {[key: string]: any} { validate(control: AbstractControl): { [key: string]: any } {
const re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i; const re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
if (!control.value || control.value === '' || re.test(control.value)) { if (!control.value || control.value === '' || re.test(control.value)) {
return null; return null;
@ -71,11 +71,11 @@ export class EmailValidatorDirective {
} }
} }
export function emailValidator(control): {[key: string]: any} { export function emailValidator(control): { [key: string]: any } {
return new EmailValidatorDirective().validate(control); return new EmailValidatorDirective().validate(control);
} }
export function emailValidator2(control): {[key: string]: any} { export function emailValidator2(control): { [key: string]: any } {
const re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i; const re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
if (!control.value || control.value === '' || re.test(control.value)) { if (!control.value || control.value === '' || re.test(control.value)) {
return null; return null;
@ -100,7 +100,7 @@ export class UserExistsValidatorDirective {
validate(control: AbstractControl): Observable<any> { validate(control: AbstractControl): Observable<any> {
return this.userService.checkUserExists(control.value) return this.userService.checkUserExists(control.value)
.map(userExists => { .map(userExists => {
return (userExists === false) ? {userNotFound: true} :null; return (userExists === false) ? {userNotFound: true} : null;
}); });
} }
} }

View File

@ -55,9 +55,9 @@ export const stateTexts = {
'COMPLETED': 'Abgeschlossen' 'COMPLETED': 'Abgeschlossen'
}; };
export const statesAsObjects = [{ name: 'BACKLOG', text: 'Backlog'}, export const statesAsObjects = [{name: 'BACKLOG', text: 'Backlog'},
{ name: 'IN_PROGRESS', text: 'In Bearbeitung'}, {name: 'IN_PROGRESS', text: 'In Bearbeitung'},
{ name: 'TEST', text: 'Test'}, {name: 'TEST', text: 'Test'},
{ name: 'COMPLETED', text: 'Abgeschlossen'}]; {name: 'COMPLETED', text: 'Abgeschlossen'}];

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import {Component, OnInit} from '@angular/core';
@Component({ @Component({
selector: 'app-not-found', selector: 'app-not-found',
@ -7,7 +7,8 @@ import { Component, OnInit } from '@angular/core';
}) })
export class NotFoundComponent implements OnInit { export class NotFoundComponent implements OnInit {
constructor() {} constructor() {
}
ngOnInit() { ngOnInit() {
} }

View File

@ -1,20 +1,20 @@
ul { ul {
list-style-type: none; list-style-type: none;
padding-left: 10px; padding-left: 10px;
} }
li { li {
font-family: "Courier New"; font-family: "Courier New";
font-size: small; font-size: small;
} }
button { button {
margin-bottom: 10px; margin-bottom: 10px;
} }
#square { #square {
background-color: red; background-color: red;
height: 100px; height: 100px;
width: 100px; width: 100px;
display: block; display: block;
} }

View File

@ -86,7 +86,7 @@
<h3>Behavior Subject</h3> <h3>Behavior Subject</h3>
<button (click)="registerNewSubscriber('subscriber1')"> <button (click)="registerNewSubscriber('subscriber1')">
Subscriber 1 anmelden Subscriber 1 anmelden
</button> </button>
<button (click)="registerNewSubscriber('subscriber2')"> <button (click)="registerNewSubscriber('subscriber2')">
Subscriber 2 anmelden Subscriber 2 anmelden

View File

@ -219,7 +219,7 @@ export class RxDemoComponent {
this.dateSubscription = Observable.timer(0, 1000) this.dateSubscription = Observable.timer(0, 1000)
.map(() => new Date()) .map(() => new Date())
.subscribe(value => { .subscribe(value => {
// console.log(value) // console.log(value)
this.currentDate = value; this.currentDate = value;
}); });
@ -231,20 +231,20 @@ export class RxDemoComponent {
console.log(`Subscription 2: ${value}`); console.log(`Subscription 2: ${value}`);
}); });
/* /*
this.randomValuesSubject = new Subject(); this.randomValuesSubject = new Subject();
const interval = setInterval(() => { const interval = setInterval(() => {
this.randomValuesSubject.next(Math.random()); this.randomValuesSubject.next(Math.random());
}, 1000); }, 1000);
this.sub1 = this.randomValuesSubject.subscribe((value) => { this.sub1 = this.randomValuesSubject.subscribe((value) => {
console.log(`Subscription 1: ${value}`); console.log(`Subscription 1: ${value}`);
}); });
this.sub2 = this.randomValuesSubject.subscribe((value) => { this.sub2 = this.randomValuesSubject.subscribe((value) => {
console.log(`Subscription 2: ${value}`); console.log(`Subscription 2: ${value}`);
}); });
*/ */
const square = document.getElementById('square'); const square = document.getElementById('square');
Observable.fromEvent(square, '') Observable.fromEvent(square, '')

View File

@ -27,19 +27,19 @@ export class LoginService {
this.socket = socketIO(WEB_SOCKET_URL); this.socket = socketIO(WEB_SOCKET_URL);
} }
getUser(name: string) : User[]{ getUser(name: string): User[] {
this.http.get(BASE_URL + "?name=" + name).subscribe(result => this.results$ = result.json()); this.http.get(BASE_URL + "?name=" + name).subscribe(result => this.results$ = result.json());
return this.results$; return this.results$;
} }
login(name, password) { login(name, password) {
if (this.getUser(name)) { if (this.getUser(name)) {
let user = this.results$[0]; let user = this.results$[0];
let passMd5 = Md5.hashStr(password); let passMd5 = Md5.hashStr(password);
if (user && user.password === passMd5) { if (user && user.password === passMd5) {
localStorage.setItem(CURRENT_USER, JSON.stringify(user)); localStorage.setItem(CURRENT_USER, JSON.stringify(user));
return true; return true;
} }
} }
} }

View File

@ -29,7 +29,7 @@ export class Store<T extends Identifiable> {
case EDIT: case EDIT:
return items.map(task => { return items.map(task => {
var editedTask = action.data; var editedTask = action.data;
if (task.id !== editedTask.id){ if (task.id !== editedTask.id) {
return task; return task;
} }
return editedTask; return editedTask;

View File

@ -25,7 +25,7 @@ export class TaskStore {
case EDIT: case EDIT:
return tasks.map(task => { return tasks.map(task => {
const editedTask = action.data; const editedTask = action.data;
if (task.id !== editedTask.id){ if (task.id !== editedTask.id) {
return task; return task;
} }
return editedTask; return editedTask;

View File

@ -39,7 +39,8 @@ export class TaskService {
.map(res => res.json()) .map(res => res.json())
.do((tasks) => { .do((tasks) => {
this.taskStore.dispatch({type: LOAD, data: tasks}); this.taskStore.dispatch({type: LOAD, data: tasks});
}).subscribe(_ => {}); }).subscribe(_ => {
});
return this.tasks$; return this.tasks$;
} }
@ -62,11 +63,12 @@ export class TaskService {
.do(savedTask => { .do(savedTask => {
this.tasksChanged.next(savedTask); this.tasksChanged.next(savedTask);
const actionType = task.id ? EDIT : ADD; const actionType = task.id ? EDIT : ADD;
const action = {type : actionType, data: savedTask}; const action = {type: actionType, data: savedTask};
this.taskStore.dispatch(action); this.taskStore.dispatch(action);
this.socket.emit('broadcast_task', action); this.socket.emit('broadcast_task', action);
}); });
} }
deleteTask(task: Task) { deleteTask(task: Task) {
return this.http.delete(BASE_URL + task.id) return this.http.delete(BASE_URL + task.id)
.do(response => { .do(response => {

View File

@ -1,4 +1,3 @@
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
export class UserService { export class UserService {
checkUserExists(name: string): Observable<boolean> { checkUserExists(name: string): Observable<boolean> {

View File

@ -27,6 +27,7 @@ export class SettingsComponent {
this.titleService.setTitle(title); this.titleService.setTitle(title);
} }
} }
ngOnDestroy() { ngOnDestroy() {
this.titleService.setTitle(this.originalTitle); this.titleService.setTitle(this.originalTitle);
} }

View File

@ -5,10 +5,11 @@ import {NgForm, FormGroup, FormGroupDirective} from '@angular/forms';
selector: 'pjm-show-error', selector: 'pjm-show-error',
template: ` template: `
<div *ngIf="errorMessages" class="alert alert-danger"> <div *ngIf="errorMessages" class="alert alert-danger">
<div *ngFor="let errorMessage of errorMessages"> <div *ngFor="let errorMessage of errorMessages">
{{errorMessage}} {{errorMessage}}
</div> </div>
</div>` }) </div>`
})
export class ShowErrorComponentModelDriven { export class ShowErrorComponentModelDriven {
@Input('path') path; @Input('path') path;
@ -22,7 +23,7 @@ export class ShowErrorComponentModelDriven {
let form: FormGroup; let form: FormGroup;
if (this.ngForm) { if (this.ngForm) {
form = this.ngForm.form; form = this.ngForm.form;
} else { } else {
form = this.formGroup.form; form = this.formGroup.form;
} }
const control = form.get(this.path); const control = form.get(this.path);

View File

@ -5,10 +5,11 @@ import {NgForm, FormGroup} from '@angular/forms';
selector: 'show-error', selector: 'show-error',
template: ` template: `
<div *ngIf="errorMessages" class="alert alert-danger"> <div *ngIf="errorMessages" class="alert alert-danger">
<div *ngFor="let errorMessage of errorMessages"> <div *ngFor="let errorMessage of errorMessages">
{{errorMessage}} {{errorMessage}}
</div> </div>
</div>` }) </div>`
})
export class ShowErrorComponent { export class ShowErrorComponent {
@Input('path') controlPath; @Input('path') controlPath;

View File

@ -1,7 +1,7 @@
label { label {
display: block; display: block;
} }
.ng-touched.ng-invalid { .ng-touched.ng-invalid {
border-color: red; border-color: red;
} }

View File

@ -1,96 +1,96 @@
<h2 *ngIf="!task.id">Neue Aufgabe anlegen</h2> <h2 *ngIf="!task.id">Neue Aufgabe anlegen</h2>
<h2 *ngIf="task.id">Aufgabe bearbeiten</h2> <h2 *ngIf="task.id">Aufgabe bearbeiten</h2>
<form (submit)="saveTask()" #form="ngForm" > <form (submit)="saveTask()" #form="ngForm">
<div class="form-group" > <div class="form-group">
<label for="title">Titel</label> <label for="title">Titel</label>
<input type="text" class="form-control" <input type="text" class="form-control"
[(ngModel)]="task.title" [(ngModel)]="task.title"
name="title" name="title"
id="title" id="title"
required minlength="5" maxlength="100"/> required minlength="5" maxlength="100"/>
<show-error text="Titel" path="title"></show-error> <show-error text="Titel" path="title"></show-error>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="description">Beschreibung</label> <label for="description">Beschreibung</label>
<textarea id="description" class="form-control" name="description" [(ngModel)]="task.description"> <textarea id="description" class="form-control" name="description" [(ngModel)]="task.description">
</textarea> </textarea>
<show-error text="Beschreibung" path="description"></show-error> <show-error text="Beschreibung" path="description"></show-error>
</div> </div>
<div > <div>
<div *ngFor="let tag of task.tags; let i = index" > <div *ngFor="let tag of task.tags; let i = index">
<div class="form-inline form-group"> <div class="form-inline form-group">
<input class="form-control" <input class="form-control"
name="tag{{i}}" name="tag{{i}}"
[(ngModel)]="tag.label" [(ngModel)]="tag.label"
minlength="3"> minlength="3">
<button class="btn btn-danger" <button class="btn btn-danger"
(click)="removeTag(i)"> (click)="removeTag(i)">
Tag entfernen Tag entfernen
</button>
<show-error text="Tag" path="tag{{i}}"></show-error>
</div>
</div>
</div>
<div class="form-group">
<button class="btn btn-success"
(click)="addTag()" >
+
</button> </button>
<show-error text="Tag" path="tag{{i}}"></show-error>
</div>
</div> </div>
</div>
<div class="form-group"> <div class="form-group">
<label>Status</label> <button class="btn btn-success"
<select id="state" name="state" [(ngModel)]="task.state" class="form-control"> (click)="addTag()">
<optgroup *ngFor="let group of model.stateGroups" [label]="group.label"> +
<option *ngFor="let state of group.states" [value]="state">
{{model.stateTexts[state]}}
</option>
</optgroup>
</select>
</div>
<h4>Zuständiger</h4>
<div >
<div class="form-group">
<label for="assignee_name">Name</label>
<input type="text"
id="assignee_name"
name="assignee_name"
class="form-control"
[(ngModel)]="task.assignee.name"/>
</div>
<div class="form-group">
<label for="assignee_email">E-Mail</label>
<input type="text"
id="assignee_email"
name="assignee_email"
class="form-control"
[(ngModel)]="task.assignee.email"
emailValidator/>
<show-error path="assignee_email"></show-error>
</div>
</div>
<button id="cancel"
(click)="cancel()"
class="btn btn-default">
Abbrechen
</button> </button>
</div>
<div class="form-group">
<label>Status</label>
<select id="state" name="state" [(ngModel)]="task.state" class="form-control">
<optgroup *ngFor="let group of model.stateGroups" [label]="group.label">
<option *ngFor="let state of group.states" [value]="state">
{{model.stateTexts[state]}}
</option>
</optgroup>
</select>
</div>
<h4>Zuständiger</h4>
<div>
<div class="form-group">
<label for="assignee_name">Name</label>
<input type="text"
id="assignee_name"
name="assignee_name"
class="form-control"
[(ngModel)]="task.assignee.name"/>
</div>
<div class="form-group">
<label for="assignee_email">E-Mail</label>
<input type="text"
id="assignee_email"
name="assignee_email"
class="form-control"
[(ngModel)]="task.assignee.email"
emailValidator/>
<show-error path="assignee_email"></show-error>
</div>
</div>
<button id="cancel"
(click)="cancel()"
class="btn btn-default">
Abbrechen
</button>
<!-- <!--
<a class="btn btn-default" <a class="btn btn-default"
[routerLink]="task.id ? '../..' : '../'"> [routerLink]="task.id ? '../..' : '../'">
Abbrechen Abbrechen
</a> </a>
--> -->
<button id="save" <button id="save"
type="submit" type="submit"
class="btn btn-default" class="btn btn-default"
[disabled]="!form.valid"> [disabled]="!form.valid">
Aufgabe speichern Aufgabe speichern
</button> </button>
</form> </form>

View File

@ -1,80 +1,89 @@
import { ViewChild, Component } from '@angular/core'; import {ViewChild, Component} from '@angular/core';
import { Title } from '@angular/platform-browser'; import {Title} from '@angular/platform-browser';
import { Router, ActivatedRoute } from '@angular/router'; import {Router, ActivatedRoute} from '@angular/router';
import { Location } from '@angular/common'; import {Location} from '@angular/common';
import { NgForm } from '@angular/forms'; import {NgForm} from '@angular/forms';
import { createInitialTask } from '../../models/model-interfaces'; import {createInitialTask} from '../../models/model-interfaces';
import { TaskService } from '../../services/task-service/task.service'; import {TaskService} from '../../services/task-service/task.service';
import * as model from '../../models/model-interfaces'; import * as model from '../../models/model-interfaces';
export var EditTaskComponent = (function () { export var EditTaskComponent = (function () {
function EditTaskComponent(route, taskService, router, titleService, location) { function EditTaskComponent(route, taskService, router, titleService, location) {
this.route = route; this.route = route;
this.taskService = taskService; this.taskService = taskService;
this.router = router; this.router = router;
this.titleService = titleService; this.titleService = titleService;
this.location = location; this.location = location;
this.model = model; this.model = model;
this.task = createInitialTask(); this.task = createInitialTask();
this.saved = false; this.saved = false;
}
EditTaskComponent.prototype.ngOnInit = function () {
var _this = this;
this.subscription = this.route.params
.map(function (params) {
return params['id'];
})
.filter(function (id) {
return id != undefined;
})
.flatMap(function (id) {
return _this.taskService.getTask(id);
})
.subscribe(function (task) {
_this.task = task;
});
};
EditTaskComponent.prototype.ngOnDestroy = function () {
this.subscription.unsubscribe();
};
EditTaskComponent.prototype.addTag = function () {
this.task.tags.push({label: ''});
return false;
};
EditTaskComponent.prototype.removeTag = function (i) {
this.task.tags.splice(i, 1);
return false;
};
EditTaskComponent.prototype.saveTask = function () {
var _this = this;
this.taskService.saveTask(this.task).subscribe(function (task) {
_this.saved = true;
var relativeUrl = _this.router.url.includes('edit') ? '../..' : '..';
_this.router.navigate([relativeUrl], {relativeTo: _this.route});
});
};
EditTaskComponent.prototype.cancel = function () {
//this.location.back();
var relativeUrl = this.router.url.includes('edit') ? '../..' : '..';
this.router.navigate([relativeUrl], {relativeTo: this.route});
return false;
};
EditTaskComponent.prototype.canDeactivate = function () {
if (this.saved || !this.form.dirty) {
return true;
} }
EditTaskComponent.prototype.ngOnInit = function () { return window.confirm("Ihr Formular besitzt ungespeicherte \u00C4nderungen, m\u00F6chten Sie die Seite wirklich verlassen?");
var _this = this; };
this.subscription = this.route.params EditTaskComponent.decorators = [
.map(function (params) { return params['id']; }) {
.filter(function (id) { return id != undefined; }) type: Component, args: [{
.flatMap(function (id) { return _this.taskService.getTask(id); }) templateUrl: './edit-task.component.html',
.subscribe(function (task) { styleUrls: ['./edit-task.component.css']
_this.task = task; },]
}); },
}; ];
EditTaskComponent.prototype.ngOnDestroy = function () { /** @nocollapse */
this.subscription.unsubscribe(); EditTaskComponent.ctorParameters = [
}; {type: ActivatedRoute,},
EditTaskComponent.prototype.addTag = function () { {type: TaskService,},
this.task.tags.push({ label: '' }); {type: Router,},
return false; {type: Title,},
}; {type: Location,},
EditTaskComponent.prototype.removeTag = function (i) { ];
this.task.tags.splice(i, 1); EditTaskComponent.propDecorators = {
return false; 'form': [{type: ViewChild, args: [NgForm,]},],
}; };
EditTaskComponent.prototype.saveTask = function () { return EditTaskComponent;
var _this = this;
this.taskService.saveTask(this.task).subscribe(function (task) {
_this.saved = true;
var relativeUrl = _this.router.url.includes('edit') ? '../..' : '..';
_this.router.navigate([relativeUrl], { relativeTo: _this.route });
});
};
EditTaskComponent.prototype.cancel = function () {
//this.location.back();
var relativeUrl = this.router.url.includes('edit') ? '../..' : '..';
this.router.navigate([relativeUrl], { relativeTo: this.route });
return false;
};
EditTaskComponent.prototype.canDeactivate = function () {
if (this.saved || !this.form.dirty) {
return true;
}
return window.confirm("Ihr Formular besitzt ungespeicherte \u00C4nderungen, m\u00F6chten Sie die Seite wirklich verlassen?");
};
EditTaskComponent.decorators = [
{ type: Component, args: [{
templateUrl: './edit-task.component.html',
styleUrls: ['./edit-task.component.css']
},] },
];
/** @nocollapse */
EditTaskComponent.ctorParameters = [
{ type: ActivatedRoute, },
{ type: TaskService, },
{ type: Router, },
{ type: Title, },
{ type: Location, },
];
EditTaskComponent.propDecorators = {
'form': [{ type: ViewChild, args: [NgForm,] },],
};
return EditTaskComponent;
}()); }());
//# sourceMappingURL=edit-task.component.js.map //# sourceMappingURL=edit-task.component.js.map

View File

@ -1 +1,10 @@
{"version":3,"file":"edit-task.component.js","sourceRoot":"","sources":["edit-task.component.ts"],"names":[],"mappings":"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"} {
"version": 3,
"file": "edit-task.component.js",
"sourceRoot": "",
"sources": [
"edit-task.component.ts"
],
"names": [],
"mappings": "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"
}

View File

@ -26,8 +26,7 @@ export class EditTaskComponent {
subscription: Subscription; subscription: Subscription;
constructor( constructor(private route: ActivatedRoute,
private route: ActivatedRoute,
private taskService: TaskService, private taskService: TaskService,
private router: Router, private router: Router,
private titleService: Title, private titleService: Title,
@ -53,7 +52,7 @@ export class EditTaskComponent {
return false; return false;
} }
removeTag(i:number) { removeTag(i: number) {
this.task.tags.splice(i, 1); this.task.tags.splice(i, 1);
return false; return false;
} }
@ -75,7 +74,7 @@ export class EditTaskComponent {
return false; return false;
} }
canDeactivate() : boolean { canDeactivate(): boolean {
if (this.saved || !this.form.dirty) { if (this.saved || !this.form.dirty) {
return true; return true;
} }

View File

@ -1,15 +1,16 @@
import { Injectable } from '@angular/core'; import {Injectable} from '@angular/core';
export var EditTaskGuard = (function () { export var EditTaskGuard = (function () {
function EditTaskGuard() { function EditTaskGuard() {
} }
EditTaskGuard.prototype.canDeactivate = function (component, route, router) {
return component.canDeactivate(); EditTaskGuard.prototype.canDeactivate = function (component, route, router) {
}; return component.canDeactivate();
EditTaskGuard.decorators = [ };
{ type: Injectable }, EditTaskGuard.decorators = [
]; {type: Injectable},
/** @nocollapse */ ];
EditTaskGuard.ctorParameters = []; /** @nocollapse */
return EditTaskGuard; EditTaskGuard.ctorParameters = [];
return EditTaskGuard;
}()); }());
//# sourceMappingURL=edit-task.guard.js.map //# sourceMappingURL=edit-task.guard.js.map

View File

@ -1 +1,10 @@
{"version":3,"file":"edit-task.guard.js","sourceRoot":"","sources":["edit-task.guard.ts"],"names":[],"mappings":"OAAO,EAAE,UAAU,EAAE,MAAS,eAAe;AAM7C;IAAA;IAcA,CAAC;IAZC,qCAAa,GAAb,UAAc,SAA4B,EAC5B,KAA6B,EAC7B,MAA2B;QAEvC,MAAM,CAAC,SAAS,CAAC,aAAa,EAAE,CAAC;IACnC,CAAC;IACI,wBAAU,GAA0B;QAC3C,EAAE,IAAI,EAAE,UAAU,EAAE;KACnB,CAAC;IACF,kBAAkB;IACX,4BAAc,GAA6D,EACjF,CAAC;IACF,oBAAC;AAAD,CAAC,AAdD,IAcC"} {
"version": 3,
"file": "edit-task.guard.js",
"sourceRoot": "",
"sources": [
"edit-task.guard.ts"
],
"names": [],
"mappings": "OAAO,EAAE,UAAU,EAAE,MAAS,eAAe;AAM7C;IAAA;IAcA,CAAC;IAZC,qCAAa,GAAb,UAAc,SAA4B,EAC5B,KAA6B,EAC7B,MAA2B;QAEvC,MAAM,CAAC,SAAS,CAAC,aAAa,EAAE,CAAC;IACnC,CAAC;IACI,wBAAU,GAA0B;QAC3C,EAAE,IAAI,EAAE,UAAU,EAAE;KACnB,CAAC;IACF,kBAAkB;IACX,4BAAc,GAA6D,EACjF,CAAC;IACF,oBAAC;AAAD,CAAC,AAdD,IAcC"
}

View File

@ -1,15 +1,14 @@
import { Injectable } from '@angular/core'; import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, RouterStateSnapshot, CanDeactivate} from '@angular/router'; import {ActivatedRouteSnapshot, RouterStateSnapshot, CanDeactivate} from '@angular/router';
import { Observable } from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {EditTaskComponent} from "./edit-task.component"; import {EditTaskComponent} from "./edit-task.component";
@Injectable() @Injectable()
export class EditTaskGuard implements CanDeactivate<EditTaskComponent>{ export class EditTaskGuard implements CanDeactivate<EditTaskComponent> {
canDeactivate(component: EditTaskComponent, canDeactivate(component: EditTaskComponent,
route: ActivatedRouteSnapshot, route: ActivatedRouteSnapshot,
router: RouterStateSnapshot) router: RouterStateSnapshot): Observable<boolean> | boolean {
: Observable<boolean> | boolean {
return component.canDeactivate(); return component.canDeactivate();
} }
} }

View File

@ -1,52 +1,77 @@
div.task-list-entry, a.task-list-entry { div.task-list-entry, a.task-list-entry {
padding: 8px; padding: 8px;
width: 525px; width: 525px;
border-radius: 2px; border-radius: 2px;
border: lightgrey solid 1px; border: lightgrey solid 1px;
cursor: pointer; cursor: pointer;
margin-bottom: -1px; margin-bottom: -1px;
} }
.marked { .marked {
background: lightgrey; background: lightgrey;
} }
span { span {
cursor: pointer; cursor: pointer;
} }
a { a {
font-size: x-large; font-size: x-large;
font-weight: 700; font-weight: 700;
} }
small { small {
color: grey; color: grey;
} }
.trash { .trash {
padding-top: 18px; padding-top: 18px;
font-size: 17px; font-size: 17px;
margin-left: -10px; margin-left: -10px;
} }
.selected { .selected {
background-color: aliceblue; background-color: aliceblue;
} }
@-webkit-keyframes fadeIn { from { opacity:0; } to { opacity:1; } } @-webkit-keyframes fadeIn {
@-moz-keyframes fadeIn { from { opacity:0; } to { opacity:1; } } from {
@keyframes fadeIn { from { opacity:0; } to { opacity:1; } } opacity: 0;
}
to {
opacity: 1;
}
}
@-moz-keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fade-in { .fade-in {
opacity:0; /* make things invisible upon start */ opacity: 0; /* make things invisible upon start */
-webkit-animation:fadeIn ease-in 1; /* call our keyframe named fadeIn, use animattion ease-in and repeat it only 1 time */ -webkit-animation: fadeIn ease-in 1; /* call our keyframe named fadeIn, use animattion ease-in and repeat it only 1 time */
-moz-animation:fadeIn ease-in 1; -moz-animation: fadeIn ease-in 1;
animation:fadeIn ease-in 1; animation: fadeIn ease-in 1;
-webkit-animation-fill-mode:forwards; /* this makes sure that after animation is done we remain at the last keyframe value (opacity: 1)*/ -webkit-animation-fill-mode: forwards; /* this makes sure that after animation is done we remain at the last keyframe value (opacity: 1)*/
-moz-animation-fill-mode:forwards; -moz-animation-fill-mode: forwards;
animation-fill-mode:forwards; animation-fill-mode: forwards;
-webkit-animation-duration:0.5s; -webkit-animation-duration: 0.5s;
-moz-animation-duration:0.5s; -moz-animation-duration: 0.5s;
animation-duration:0.5s; animation-duration: 0.5s;
} }

View File

@ -8,7 +8,7 @@ import {Task} from '../../models/model-interfaces';
styleUrls: ['./task-item.component.css'], styleUrls: ['./task-item.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
inputs: ['task', 'selected'], inputs: ['task', 'selected'],
outputs: ['taskSelected' , 'taskDelete'], outputs: ['taskSelected', 'taskDelete'],
}) })
export class TaskItemComponent { export class TaskItemComponent {
@ -17,12 +17,13 @@ export class TaskItemComponent {
checkCounter = 0; checkCounter = 0;
taskSelected = new EventEmitter(); taskSelected = new EventEmitter();
taskDelete = new EventEmitter(); taskDelete = new EventEmitter();
constructor(private router: Router) { constructor(private router: Router) {
} }
select() { select() {
this.taskSelected.emit(this.task.id) this.taskSelected.emit(this.task.id)
} }
@ -33,7 +34,7 @@ export class TaskItemComponent {
ngAfterViewChecked() { ngAfterViewChecked() {
//var taskId = (this.task ? this.task.id : ''); //var taskId = (this.task ? this.task.id : '');
// console.log(`Task ${taskId} checked ${++this.checkCounter} times`) // console.log(`Task ${taskId} checked ${++this.checkCounter} times`)
} }
} }

View File

@ -1,8 +1,8 @@
.search-bar { .search-bar {
padding-top: 20px; padding-top: 20px;
padding-bottom: 20px; padding-bottom: 20px;
} }
.task-list { .task-list {
width: 525px; width: 525px;
} }

View File

@ -40,15 +40,15 @@ export class TaskListComponent implements OnInit {
Observable.merge(paramsStream, searchTermStream) Observable.merge(paramsStream, searchTermStream)
.distinctUntilChanged() .distinctUntilChanged()
.switchMap(query => this.taskService.findTasks(query)) .switchMap(query => this.taskService.findTasks(query))
.subscribe(); .subscribe();
/* /*
this.tasks$ = Observable.merge(paramsStream, searchTermStream) this.tasks$ = Observable.merge(paramsStream, searchTermStream)
.distinctUntilChanged() .distinctUntilChanged()
.switchMap(query => this.taskService.findTasks(query)); .switchMap(query => this.taskService.findTasks(query));
*/ */
} }
@ -58,13 +58,13 @@ export class TaskListComponent implements OnInit {
selectTask(taskId: string | number) { selectTask(taskId: string | number) {
this.selectedTaskId = taskId; this.selectedTaskId = taskId;
this.router.navigate([ {outlets: {'right': [ 'overview' , taskId]}}], {relativeTo: this.route}); this.router.navigate([{outlets: {'right': ['overview', taskId]}}], {relativeTo: this.route});
} }
findTasks(queryString: string) { findTasks(queryString: string) {
// jetzt über type-ahead gelöst // jetzt über type-ahead gelöst
// this.tasks$ = this.taskService.findTasks(queryString); // this.tasks$ = this.taskService.findTasks(queryString);
// this.adjustBrowserUrl(queryString); // this.adjustBrowserUrl(queryString);
} }
adjustBrowserUrl(queryString = '') { adjustBrowserUrl(queryString = '') {

View File

@ -1,12 +1,12 @@
.label-small { .label-small {
font-size: 50%; font-size: 50%;
z-index: 10; z-index: 10;
} }
.overview { .overview {
border-left: thin solid lightgrey; border-left: thin solid lightgrey;
padding-left: 10px; padding-left: 10px;
padding-top: 20px; padding-top: 20px;
margin-left: 10px; margin-left: 10px;
height: 100vh; height: 100vh;
} }

View File

@ -24,7 +24,8 @@
</div> </div>
<div class="col-xs-12"> <div class="col-xs-12">
<h4>Status</h4> <h4>Status</h4>
<select class="form-control" name="state" [ngModel]="task.state" (ngModelChange)="task.state = $event; saveTask()" > <select class="form-control" name="state" [ngModel]="task.state"
(ngModelChange)="task.state = $event; saveTask()">
<optgroup *ngFor="let group of model.stateGroups" [label]="group.label"> <optgroup *ngFor="let group of model.stateGroups" [label]="group.label">
<option *ngFor="let state of group.states" [value]="state"> <option *ngFor="let state of group.states" [value]="state">
{{model.stateTexts[state]}} {{model.stateTexts[state]}}

View File

@ -24,9 +24,9 @@ export class TaskOverviewComponent {
ngOnInit() { ngOnInit() {
this.route.params.subscribe((params) => { this.route.params.subscribe((params) => {
this.taskService.getTask(params['id']).subscribe(task => { this.taskService.getTask(params['id']).subscribe(task => {
this.task = task; this.task = task;
}); });
}); });
} }

View File

@ -1,9 +1,9 @@
#left { #left {
width:535px; width: 535px;
float: left; float: left;
padding-right: 10px; padding-right: 10px;
} }
#right { #right {
overflow: hidden overflow: hidden
} }

View File

@ -1 +1 @@
<router-outlet></router-outlet> <router-outlet></router-outlet>

View File

@ -11,7 +11,7 @@ describe('Basic Test', () => {
// Testfall 1 // Testfall 1
it('should succeed on most commonly used matchers', () => { it('should succeed on most commonly used matchers', () => {
expect(1+1).toBe(2) expect(1 + 1).toBe(2)
expect(null).toEqual(null); expect(null).toEqual(null);
expect('Hello world').toContain('Hello'); expect('Hello world').toContain('Hello');
expect(true).toBeTruthy(); expect(true).toBeTruthy();

View File

@ -1,10 +1,9 @@
// return random string of given length, from:
// method to create random string of given length, from:
// http://stackoverflow.com/questions/16106701/how-to-generate-a-random-string-of-letters-and-numbers-in-javascript // http://stackoverflow.com/questions/16106701/how-to-generate-a-random-string-of-letters-and-numbers-in-javascript
export function generateRandomString(len) { export function generateRandomString(len) {
var text = ""; var text = "";
var charset = "abcdefghijklmnopqrstuvwxyz0123456789"; var charset = "abcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < len; i++ ) for (var i = 0; i < len; i++)
text += charset.charAt(Math.floor(Math.random() * charset.length)); text += charset.charAt(Math.floor(Math.random() * charset.length));
return text; return text;
} }