implementing categories

This commit is contained in:
Jochen Saalfeld 2018-09-19 15:18:57 +02:00
parent 849da27745
commit 48526d6c19
No known key found for this signature in database
GPG Key ID: 8ACD4E8264B67DF4
23 changed files with 571 additions and 108 deletions

View File

@ -4779,13 +4779,15 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": false, "resolved": false,
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": false, "resolved": false,
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -4808,7 +4810,8 @@
"version": "0.0.1", "version": "0.0.1",
"resolved": false, "resolved": false,
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true "dev": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
@ -4975,6 +4978,7 @@
"resolved": false, "resolved": false,
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }

View File

@ -1,5 +1,6 @@
<mat-toolbar color='primary'> <mat-toolbar color='primary'>
<button *ngIf="plusButton" class='generic-plus-button on-transition-fade' (click)=clickPlusButton() mat-fab> <button *ngIf="plusButton" class='head-button on-transition-fade' (click)=clickPlusButton()
mat-fab>
<fa-icon icon='plus'></fa-icon> <fa-icon icon='plus'></fa-icon>
</button> </button>

View File

@ -0,0 +1,4 @@
.head-button {
bottom: -30px;
z-index: 100;
}

View File

@ -16,10 +16,10 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
* ```html * ```html
* <os-head-bar * <os-head-bar
* appName="Files" * appName="Files"
* PlusButton=true * plusButton=true
* [menuList]=myMenu * [menuList]=myMenu
* (plusButtonClicked)=onPlusButton() * (plusButtonClicked)=onPlusButton()
* (ellipsisMenuItem)=onEllipsisItem($event)> * (ellipsisMenuItem)=onEllipsisItem($event)
* </os-head-bar> * </os-head-bar>
* ``` * ```
* *
@ -54,29 +54,34 @@ export class HeadBarComponent implements OnInit {
/** /**
* Input declaration for the app name * Input declaration for the app name
*/ */
@Input() public appName: string; @Input()
public appName: string;
/** /**
* Determine if there should be a plus button. * Determine if there should be a plus button.
*/ */
@Input() public plusButton: false; @Input()
public plusButton: false;
/** /**
* If not empty shows a ellipsis menu on the right side * If not empty shows a ellipsis menu on the right side
* *
* The parent needs to provide a menu, i.e `[menuList]=myMenu`. * The parent needs to provide a menu, i.e `[menuList]=myMenu`.
*/ */
@Input() public menuList: any[]; @Input()
public menuList: any[];
/** /**
* Emit a signal to the parent component if the plus button was clicked * Emit a signal to the parent component if the plus button was clicked
*/ */
@Output() public plusButtonClicked = new EventEmitter<boolean>(); @Output()
public plusButtonClicked = new EventEmitter<boolean>();
/** /**
* Emit a signal to the parent of an item in the menuList was selected. * Emit a signal to the parent of an item in the menuList was selected.
*/ */
@Output() public ellipsisMenuItem = new EventEmitter<any>(); @Output()
public ellipsisMenuItem = new EventEmitter<any>();
/** /**
* Empty constructor * Empty constructor

View File

@ -1,6 +1,10 @@
<mat-form-field [formGroup]="form"> <mat-form-field [formGroup]="form">
<mat-select [formControl]="formControl" placeholder="{{listname}}" multiple="{{multiple}}" #thisSelector> <mat-select [formControl]="formControl" [placeholder]="listname" [multiple]="multiple" #thisSelector>
<ngx-mat-select-search [formControl]="filterControl"></ngx-mat-select-search> <ngx-mat-select-search [formControl]="filterControl"></ngx-mat-select-search>
<div *ngIf="!multiple">
<mat-option><span translate>None</span></mat-option>
<mat-divider></mat-divider>
</div>
<mat-option *ngFor="let selectedItem of filteredItems | async" [value]="selectedItem"> <mat-option *ngFor="let selectedItem of filteredItems | async" [value]="selectedItem">
{{selectedItem.getTitle(translate)}} {{selectedItem.getTitle(translate)}}
</mat-option> </mat-option>

View File

@ -1,6 +1,6 @@
import { Component, OnInit, Input, ViewChild } from '@angular/core'; import { Component, OnInit, Input, ViewChild } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { ReplaySubject, Subject } from 'rxjs'; import { Subject, BehaviorSubject } from 'rxjs';
import { MatSelect } from '@angular/material'; import { MatSelect } from '@angular/material';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { Displayable } from '../../models/base/displayable'; import { Displayable } from '../../models/base/displayable';
@ -20,7 +20,7 @@ import { TranslateService } from '@ngx-translate/core';
* ```html * ```html
* <os-search-value-selector * <os-search-value-selector
* ngDefaultControl * ngDefaultControl
* multiple="true" * [multiple]="true"
* placeholder="Placeholder" * placeholder="Placeholder"
* [InputListValues]="myListValues", * [InputListValues]="myListValues",
* [form]="myform_name", * [form]="myform_name",
@ -50,7 +50,7 @@ export class SearchValueSelectorComponent implements OnInit {
/** /**
* List of the filtered content, when entering somithing in the search bar * List of the filtered content, when entering somithing in the search bar
*/ */
public filteredItems: ReplaySubject<Displayable[]> = new ReplaySubject<Displayable[]>(1); public filteredItems: BehaviorSubject<Displayable[]>;
/** /**
* Decide if this should be a single or multi-select-field * Decide if this should be a single or multi-select-field
@ -62,7 +62,7 @@ export class SearchValueSelectorComponent implements OnInit {
* The Input List Values * The Input List Values
*/ */
@Input() @Input()
public InputListValues: Displayable[]; public InputListValues: BehaviorSubject<Displayable[]>;
/** /**
* Placeholder of the List * Placeholder of the List
@ -111,13 +111,11 @@ export class SearchValueSelectorComponent implements OnInit {
* onInit with filter ans subscription on filter * onInit with filter ans subscription on filter
*/ */
public ngOnInit(): void { public ngOnInit(): void {
// load the initial item list this.filteredItems = this.InputListValues;
this.filteredItems.next(this.InputListValues.slice());
// listen to value changes // listen to value changes
this.filterControl.valueChanges.pipe(takeUntil(this._onDestroy)).subscribe(() => { this.filterControl.valueChanges.pipe(takeUntil(this._onDestroy)).subscribe(() => {
this.filterItems(); this.filterItems();
}); });
// this.multiSelect.stateChanges.subscribe(fn => console.log('ive changed'));
} }
/** /**
@ -130,14 +128,14 @@ export class SearchValueSelectorComponent implements OnInit {
// get the search keyword // get the search keyword
let search = this.filterControl.value; let search = this.filterControl.value;
if (!search) { if (!search) {
this.filteredItems.next(this.InputListValues.slice()); this.filteredItems.next(this.InputListValues.getValue());
return; return;
} else { } else {
search = search.toLowerCase(); search = search.toLowerCase();
} }
// filter the values // filter the values
this.filteredItems.next( this.filteredItems.next(
this.InputListValues.filter( this.InputListValues.getValue().filter(
selectedItem => selectedItem =>
selectedItem selectedItem
.toString() .toString()

View File

@ -16,6 +16,13 @@ export class Category extends BaseModel {
public getTitle(): string { public getTitle(): string {
return this.prefix + ' - ' + this.name; return this.prefix + ' - ' + this.name;
} }
/**
* update the values of the motion with new values
*/
public patchValues(update: object): void {
Object.assign(this, update);
}
} }
BaseModel.registerCollectionElement('motions/category', Category); BaseModel.registerCollectionElement('motions/category', Category);

View File

@ -49,7 +49,7 @@ export class AgendaRepositoryService extends BaseRepository<ViewItem, Item> {
* *
* TODO: used over not-yet-existing detail view * TODO: used over not-yet-existing detail view
*/ */
public save(item: Item, viewUser: ViewItem): Observable<Item> { public update(item: Item, viewUser: ViewItem): Observable<Item> {
return null; return null;
} }

View File

@ -25,7 +25,7 @@ export class AssignmentRepositoryService extends BaseRepository<ViewAssignment,
super(DS, Assignment, [User, Item, Tag]); super(DS, Assignment, [User, Item, Tag]);
} }
public save(assignment: Assignment, viewAssignment: ViewAssignment): Observable<Assignment> { public update(assignment: Assignment, viewAssignment: ViewAssignment): Observable<Assignment> {
return null; return null;
} }

View File

@ -75,7 +75,7 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
* @param update the update that should be created * @param update the update that should be created
* @param viewModel the view model that the update is based on * @param viewModel the view model that the update is based on
*/ */
public abstract save(update: M, viewModel: V): Observable<M>; public abstract update(update: M, viewModel: V): Observable<M>;
/** /**
* Deletes a given Model * Deletes a given Model

View File

@ -27,7 +27,7 @@ export class MediafileRepositoryService extends BaseRepository<ViewMediafile, Me
* *
* TODO: used over not-yet-existing detail view * TODO: used over not-yet-existing detail view
*/ */
public save(file: Mediafile, viewFile: ViewMediafile): Observable<Mediafile> { public update(file: Mediafile, viewFile: ViewMediafile): Observable<Mediafile> {
return null; return null;
} }

View File

@ -1,19 +1,58 @@
<os-head-bar appName="Category" plusButton=true (plusButtonClicked)=onPlusButton()> <os-head-bar appName="Categories" [plusButton]=true (plusButtonClicked)=onPlusButton()>
</os-head-bar> </os-head-bar>
<div class='custom-table-header on-transition-fade'>
<mat-table class='on-transition-fade' [dataSource]="dataSource" matSort> <button mat-button>
<!-- name column --> <fa-icon icon="search"></fa-icon>
<ng-container matColumnDef="name"> </button>
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell> </div>
<mat-cell *matCellDef="let category"> {{category.name}} </mat-cell> <mat-accordion class="os-card">
</ng-container> <mat-expansion-panel [ngClass]="{new: category.id === undefined}" *ngFor="let category of this.dataSource" (opened)="panelOpening('true', category)" (closed)="panelOpening('false', category)"
multiple="false">
<!-- prefix column --> <mat-expansion-panel-header>
<ng-container matColumnDef="prefix"> <mat-panel-title *ngIf="!category.edit">
<mat-header-cell *matHeaderCellDef mat-sort-header> Prefix </mat-header-cell> {{category.name}}
<mat-cell *matCellDef="let category"> {{category.prefix}} </mat-cell> </mat-panel-title>
</ng-container> <mat-panel-title *ngIf="category.edit">
{{this.formGroup.get('name').value}}
<mat-header-row *matHeaderRowDef="['name', 'prefix']"></mat-header-row> </mat-panel-title>
<mat-row *matRowDef="let row; columns: ['name', 'prefix']"></mat-row> <mat-panel-description *ngIf="!category.edit">
</mat-table> {{category.prefix}}
</mat-panel-description>
<mat-panel-description *ngIf="category.edit">
{{this.formGroup.get('prefix').value}}
</mat-panel-description>
</mat-expansion-panel-header>
<form [formGroup]='this.formGroup' *ngIf="category.edit" (keydown)="keyDownFunction($event, category)">
<span translate>Edit category details:</span><br>
<mat-form-field>
<input formControlName="name" matInput placeholder="{{'Name' | translate}}">
<small *ngIf="!this.formGroup.controls.name.valid">
<span translate>Required</span>
</small>
</mat-form-field>
<mat-form-field>
<input formControlName="prefix" matInput placeholder="{{'Prefix' | translate}}">
<small *ngIf="!this.formGroup.controls.prefix.valid">
<span translate>Required</span>
</small>
</mat-form-field>
</form>
<mat-action-row>
<button *ngIf="!category.edit" mat-button class='on-transition-fade' (click)=onEditButton(category)
mat-icon-button>
<fa-icon icon='pen'></fa-icon>
</button>
<button *ngIf="category.edit" mat-button class='on-transition-fade' (click)=onCancelButton(category)
mat-icon-button>
<fa-icon icon='times'></fa-icon>
</button>
<button *ngIf="category.edit" mat-button class='on-transition-fade' (click)=onSaveButton(category)
mat-icon-button>
<fa-icon icon='save'></fa-icon>
</button>
<button mat-button class='on-transition-fade' (click)=onDeleteButton(category) mat-icon-button>
<fa-icon icon='trash'></fa-icon>
</button>
</mat-action-row>
</mat-expansion-panel>
</mat-accordion>

View File

@ -0,0 +1,44 @@
.button-side {
right: 0;
top: 0px;
float: right;
}
.text-side {
size: 50%;
}
.content-row {
size: 100%;
}
.new {
// put in theme later
background-color: lightblue;
}
.mini-button {
top: 0px;
width: 20px;
height: 20px;
min-height: 20px;
font-size: 10px;
box-shadow: none;
vertical-align: top;
padding: 0 0;
margin: 0;
}
.onethird {
width: 33%;
}
.custom-table-header {
// display: none;
width: 100%;
height: 60px;
line-height: 60px;
text-align: right;
background: white;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}

View File

@ -1,59 +1,98 @@
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { MatSort, MatTable, MatTableDataSource } from '@angular/material';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from '../../../../base.component'; import { BaseComponent } from '../../../../base.component';
import { Category } from '../../../../shared/models/motions/category'; import { Category } from '../../../../shared/models/motions/category';
import { DataStoreService } from '../../../../core/services/data-store.service'; import { CategoryRepositoryService } from '../../services/category-repository.service';
import { ViewCategory } from '../../models/view-category';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
/** /**
* List view for the categories. * List view for the categories.
*
* TODO: Creation of new Categories
*/ */
@Component({ @Component({
selector: 'os-category-list', selector: 'os-category-list',
templateUrl: './category-list.component.html', templateUrl: './category-list.component.html',
styleUrls: ['./category-list.component.scss'] styleUrls: ['./category-list.component.scss']
}) })
export class CategoryListComponent extends BaseComponent implements OnInit { export class CategoryListComponent extends BaseComponent implements OnInit, OnDestroy {
/** /**
* Store the categories * States the edit mode
*/ */
public categoryArray: Array<Category>; public editMode = false;
/** /**
* Will be processed by the mat-table * Source of the Data
*/ */
public dataSource: MatTableDataSource<Category>; public dataSource: Array<ViewCategory>;
/** /**
* The table itself. * The current focussed formgroup
*/ */
@ViewChild(MatTable) public formGroup: FormGroup;
public table: MatTable<Category>;
/**
* Sort the Table
*/
@ViewChild(MatSort)
public sort: MatSort;
/** /**
* The usual component constructor * The usual component constructor
* @param titleService * @param titleService
* @param translate * @param translate
* @param repo
* @param formBuilder
*/ */
public constructor( public constructor(
protected titleService: Title, protected titleService: Title,
protected translate: TranslateService, protected translate: TranslateService,
protected DS: DataStoreService private repo: CategoryRepositoryService,
private formBuilder: FormBuilder
) { ) {
super(titleService, translate); super(titleService, translate);
this.formGroup = this.formBuilder.group({
name: ['', Validators.required],
prefix: ['', Validators.required]
});
} }
/**
* On Destroy Function
*
* Saves the edits
*/
public ngOnDestroy(): void {
this.dataSource.forEach(viewCategory => {
if (viewCategory.edit && viewCategory.opened) {
const nameControl = this.formGroup.get('name');
const prefixControl = this.formGroup.get('prefix');
const nameValue = nameControl.value;
const prefixValue = prefixControl.value;
viewCategory.name = nameValue;
viewCategory.prefix = prefixValue;
this.saveCategory(viewCategory);
}
});
}
/**
* Event on Key Down in form
*/
public keyDownFunction(event: KeyboardEvent, viewCategory: ViewCategory): void {
if (event.keyCode === 13) {
this.onSaveButton(viewCategory);
}
}
/**
* Stores the Datamodel in the repo
* @param viewCategory
*/
private saveCategory(viewCategory: ViewCategory): void {
if (this.repo.osInDataStore(viewCategory)) {
this.repo.create(viewCategory).subscribe();
} else {
this.repo.update(viewCategory).subscribe();
}
viewCategory.edit = false;
}
/** /**
* Init function. * Init function.
* *
@ -61,26 +100,127 @@ export class CategoryListComponent extends BaseComponent implements OnInit {
*/ */
public ngOnInit(): void { public ngOnInit(): void {
super.setTitle('Category'); super.setTitle('Category');
this.categoryArray = this.DS.getAll(Category); this.repo.getViewModelListObservable().subscribe(newViewCategories => {
this.dataSource = new MatTableDataSource(this.categoryArray); this.dataSource = newViewCategories;
this.dataSource.sort = this.sort; });
this.sortDataSource();
}
// Observe DataStore for motions. Initially, executes once for every motion. /**
// The alternative approach is to put the observable as DataSource to the table * Add a new Category.
this.DS.changeObservable.subscribe(newModel => { */
if (newModel instanceof Category) { public onPlusButton(): void {
this.categoryArray = this.DS.getAll(Category); let noNewOnes = true;
this.dataSource.data = this.categoryArray; this.dataSource.forEach(viewCategory => {
if (viewCategory.id === undefined) {
noNewOnes = false;
}
});
if (noNewOnes) {
const newCategory = new Category();
newCategory.id = undefined;
newCategory.name = this.translate.instant('Name');
newCategory.prefix = this.translate.instant('Prefix');
const newViewCategory = new ViewCategory(newCategory);
newViewCategory.opened = true;
this.dataSource.reverse();
this.dataSource.push(newViewCategory);
this.dataSource.reverse();
this.editMode = true;
}
}
/**
* Executed on edit button
* @param viewCategory
*/
public onEditButton(viewCategory: ViewCategory): void {
viewCategory.edit = true;
viewCategory.synced = false;
this.editMode = true;
const nameControl = this.formGroup.get('name');
const prefixControl = this.formGroup.get('prefix');
nameControl.setValue(viewCategory.name);
prefixControl.setValue(viewCategory.prefix);
}
/**
* Saves the categories
*/
public onSaveButton(viewCategory: ViewCategory): void {
if (this.formGroup.controls.name.valid && this.formGroup.controls.prefix.valid) {
this.editMode = false;
const nameControl = this.formGroup.get('name');
const prefixControl = this.formGroup.get('prefix');
const nameValue = nameControl.value;
const prefixValue = prefixControl.value;
if (
viewCategory.id === undefined ||
nameValue !== viewCategory.name ||
prefixValue !== viewCategory.prefix
) {
viewCategory.prefix = prefixValue;
viewCategory.name = nameValue;
this.saveCategory(viewCategory);
}
}
this.sortDataSource();
}
/**
* sorts the datasource by prefix alphabetically
*/
protected sortDataSource(): void {
this.dataSource.sort((viewCategory1, viewCategory2) => {
if (viewCategory1.prefix > viewCategory2.prefix) {
return 1;
}
if (viewCategory1.prefix < viewCategory2.prefix) {
return -1;
} }
}); });
} }
/** /**
* Add a new Category. * executed on cancel button
* * @param viewCategory
* TODO: Not yet implemented
*/ */
public onPlusButton(): void { public onCancelButton(viewCategory: ViewCategory): void {
console.log('Add New Category'); viewCategory.edit = false;
this.editMode = false;
}
/**
* is executed, when the delete button is pressed
*/
public onDeleteButton(viewCategory: ViewCategory): void {
if (this.repo.osInDataStore(viewCategory) && viewCategory.id !== undefined) {
this.repo.delete(viewCategory).subscribe();
}
const index = this.dataSource.indexOf(viewCategory, 0);
if (index > -1) {
this.dataSource.splice(index, 1);
}
// if no category is there, we setill have to be able to create one
if (this.dataSource.length < 1) {
this.editMode = false;
}
}
/**
* Is executed when a mat-extension-panel is opened or closed
* @param open true if opened, false if being closed
* @param category the category in the panel
*/
public panelOpening(open: boolean, category: ViewCategory): void {
category.opened = open as boolean;
if (category.edit === true) {
this.onSaveButton(category);
this.onCancelButton(category);
}
if (!open) {
category.edit = false;
this.editMode = false;
}
} }
} }

View File

@ -15,7 +15,7 @@
<span *ngIf="motion && !editMotion"> {{motion.title}}</span> <span *ngIf="motion && !editMotion"> {{motion.title}}</span>
<span *ngIf="editMotion"> {{contentForm.get('title').value}}</span> <span *ngIf="editMotion"> {{contentForm.get('title').value}}</span>
<br> <br>
<div *ngIf="motion" class='motion-submitter'> <div *ngIf="motion && !newMotion" class='motion-submitter'>
<span translate>by</span> {{motion.submitters}} <span translate>by</span> {{motion.submitters}}
</div> </div>
</div> </div>
@ -148,7 +148,7 @@
<div *ngIf="motion && motion.submitters || editMotion"> <div *ngIf="motion && motion.submitters || editMotion">
<div *ngIf="editMotion && newMotion"> <div *ngIf="editMotion && newMotion">
<div *ngIf="motion && editMotion"> <div *ngIf="motion && editMotion">
<os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="this.metaInfoForm.get('submitters')" multiple="true" listname="Submitter" [InputListValues]="getAllUsers()"></os-search-value-selector> <os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="this.metaInfoForm.get('submitters')" [multiple]="true" listname="Submitter" [InputListValues]="this.submitterObserver"></os-search-value-selector>
</div> </div>
</div> </div>
<div *ngIf="!editMotion || !newMotion"> <div *ngIf="!editMotion || !newMotion">
@ -164,7 +164,7 @@
<!-- print all motion supporters --> <!-- print all motion supporters -->
<div *ngIf="editMotion"> <div *ngIf="editMotion">
<div *ngIf="motion && editMotion"> <div *ngIf="motion && editMotion">
<os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="this.metaInfoForm.get('supporters_id')" multiple="true" listname="Supporter" [InputListValues]="getAllUsers()"></os-search-value-selector> <os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="this.metaInfoForm.get('supporters_id')" [multiple]="true" listname="Supporter" [InputListValues]="this.supporterObserver"></os-search-value-selector>
</div> </div>
</div> </div>
<div *ngIf="!editMotion && motion.hasSupporters()"> <div *ngIf="!editMotion && motion.hasSupporters()">
@ -218,11 +218,11 @@
<!-- Category --> <!-- Category -->
<div *ngIf="motion && motion.categoryId || editMotion"> <div *ngIf="motion && motion.categoryId || editMotion">
<div *ngIf='!editMotion'> <div *ngIf='!editMotion'>
<h3 translate> Category</h3> <h3 translate>Category</h3>
{{motion.category}} {{motion.category}}
</div> </div>
<div *ngIf="editMotion"> <div *ngIf="editMotion">
<os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="this.metaInfoForm.get('category_id')" multiple="false" listname="Category" [InputListValues]="getMotionCategories()"></os-search-value-selector> <os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="this.metaInfoForm.get('category_id')" [multiple]="false" listname="Category" [InputListValues]="this.categoryObserver"></os-search-value-selector>
</div> </div>
</div> </div>

View File

@ -10,6 +10,8 @@ import { MotionRepositoryService } from '../../services/motion-repository.servic
import { ViewMotion } from '../../models/view-motion'; import { ViewMotion } from '../../models/view-motion';
import { User } from '../../../../shared/models/users/user'; import { User } from '../../../../shared/models/users/user';
import { DataStoreService } from '../../../../core/services/data-store.service'; import { DataStoreService } from '../../../../core/services/data-store.service';
import { BehaviorSubject } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
/** /**
* Component for the motion detail view * Component for the motion detail view
@ -64,6 +66,21 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
*/ */
public motionCopy: ViewMotion; public motionCopy: ViewMotion;
/**
* Subject for the Categories
*/
public categoryObserver: BehaviorSubject<Array<Category>>;
/**
* Subject for the Submitters
*/
public submitterObserver: BehaviorSubject<Array<User>>;
/**
* Subject for the Supporters
*/
public supporterObserver: BehaviorSubject<Array<User>>;
/** /**
* Constuct the detail view. * Constuct the detail view.
* *
@ -72,6 +89,7 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
* @param route determine if this is a new or an existing motion * @param route determine if this is a new or an existing motion
* @param formBuilder For reactive forms. Form Group and Form Control * @param formBuilder For reactive forms. Form Group and Form Control
* @param repo: Motion Repository * @param repo: Motion Repository
* @param translate: Translation Service
*/ */
public constructor( public constructor(
public vp: ViewportService, public vp: ViewportService,
@ -79,7 +97,8 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
private route: ActivatedRoute, private route: ActivatedRoute,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private repo: MotionRepositoryService, private repo: MotionRepositoryService,
private DS: DataStoreService private DS: DataStoreService,
protected translate: TranslateService
) { ) {
super(); super();
this.createForm(); this.createForm();
@ -100,6 +119,21 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
}); });
}); });
} }
// Initial Filling of the Subjects
this.submitterObserver = new BehaviorSubject(DS.getAll(User));
this.supporterObserver = new BehaviorSubject(DS.getAll(User));
this.categoryObserver = new BehaviorSubject(this.DS.getAll(Category));
// Make sure the subjects are updated, when a new Model for the type arrives
this.DS.changeObservable.subscribe(newModel => {
if (newModel instanceof User) {
this.submitterObserver.next(DS.getAll(User));
this.supporterObserver.next(DS.getAll(User));
}
if (newModel instanceof Category) {
this.categoryObserver.next(DS.getAll(Category));
}
});
} }
/** /**
@ -107,7 +141,7 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
*/ */
public patchForm(formMotion: ViewMotion): void { public patchForm(formMotion: ViewMotion): void {
this.metaInfoForm.patchValue({ this.metaInfoForm.patchValue({
category_id: formMotion.categoryId, category_id: formMotion.category,
supporters_id: formMotion.supporters, supporters_id: formMotion.supporters,
submitters: formMotion.submitters, submitters: formMotion.submitters,
state_id: formMotion.stateId, state_id: formMotion.stateId,
@ -162,17 +196,10 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
this.router.navigate(['./motions/' + response.id]); this.router.navigate(['./motions/' + response.id]);
}); });
} else { } else {
this.repo.save(newMotionValues, this.motionCopy).subscribe(); this.repo.update(newMotionValues, this.motionCopy).subscribe();
} }
} }
/**
* return all Categories
*/
public getMotionCategories(): Category[] {
return this.DS.getAll(Category);
}
/** /**
* Click on the edit button (pen-symbol) * Click on the edit button (pen-symbol)
*/ */
@ -215,13 +242,6 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
}); });
} }
/**
* returns all Possible supporters
*/
public getAllUsers(): User[] {
return this.DS.getAll(User);
}
/** /**
* Init. Does nothing here. * Init. Does nothing here.
*/ */

View File

@ -0,0 +1,98 @@
import { Category } from '../../../shared/models/motions/category';
import { TranslateService } from '@ngx-translate/core';
import { BaseViewModel } from '../../base/base-view-model';
/**
* Category class for the View
*
* Stores a Category including all (implicit) references
* Provides "safe" access to variables and functions in {@link Category}
* @ignore
*/
export class ViewCategory extends BaseViewModel {
private _category: Category;
private _edit: boolean;
private _synced: boolean;
private _opened: boolean;
public get category(): Category {
return this._category;
}
public get id(): number {
return this.category ? this.category.id : null;
}
public get name(): string {
return this.category ? this.category.name : null;
}
public get prefix(): string {
return this.category ? this.category.prefix : null;
}
public set synced(bol: boolean) {
this._synced = bol;
}
public set edit(bol: boolean) {
this._edit = bol;
}
public set opened(bol: boolean) {
this._opened = bol;
}
public set prefix(pref: string) {
this._category.prefix = pref;
}
public set name(nam: string) {
this._category.name = nam;
}
public get opened(): boolean {
return this._opened;
}
public get synced(): boolean {
return this._synced;
}
public get edit(): boolean {
return this._edit;
}
public constructor(category?: Category, id?: number, prefix?: string, name?: string) {
super();
if (!category) {
category = new Category();
category.id = id;
category.name = name;
category.prefix = prefix;
}
this._category = category;
this._edit = false;
this._synced = true;
this._opened = false;
}
public getTitle(translate?: TranslateService): string {
return this.name;
}
/**
* Updates the local objects if required
* @param update
*/
public updateValues(update: Category): void {
this._category = update;
}
/**
* Duplicate this motion into a copy of itself
*/
public copy(): ViewCategory {
return new ViewCategory(this._category);
}
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { CategoryRepositoryService } from './category-repository.service';
describe('CategoryRepositoryService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [CategoryRepositoryService]
});
});
it('should be created', inject([CategoryRepositoryService], (service: CategoryRepositoryService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,83 @@
import { Injectable } from '@angular/core';
import { Category } from '../../../shared/models/motions/category';
import { ViewCategory } from '../models/view-category';
import { DataSendService } from '../../../core/services/data-send.service';
import { Observable } from 'rxjs';
import { DataStoreService } from '../../../core/services/data-store.service';
import { BaseRepository } from '../../base/base-repository';
/**
* Repository Services for Categories
*
* The repository is meant to process domain objects (those found under
* shared/models), so components can display them and interact with them.
*
* Rather than manipulating models directly, the repository is meant to
* inform the {@link DataSendService} about changes which will send
* them to the Server.
*/
@Injectable({
providedIn: 'root'
})
export class CategoryRepositoryService extends BaseRepository<ViewCategory, Category> {
/**
* Creates a CategoryRepository
* Converts existing and incoming category to ViewCategories
* Handles CRUD using an observer to the DataStore
* @param DataSend
*/
public constructor(protected DS: DataStoreService, private dataSend: DataSendService) {
super(DS, Category);
}
protected createViewModel(category: Category): ViewCategory {
return new ViewCategory(category);
}
public create(update: object, viewCategory?: ViewCategory): Observable<any> {
const categories = this.DS.getAll(Category);
const categoryIds: number[] = [];
if (update instanceof Category) {
viewCategory = new ViewCategory(update);
}
if (update instanceof ViewCategory) {
viewCategory = update;
}
categories.forEach(category => {
categoryIds.push(category.id);
});
if (viewCategory.id in categoryIds) {
return this.update(update, viewCategory);
} else {
return this.dataSend.saveModel(viewCategory.category);
}
}
public update(update: object, viewCategory?: ViewCategory): Observable<any> {
let updateCategory: Category;
if (viewCategory) {
updateCategory = viewCategory.category;
} else {
updateCategory = new Category();
}
updateCategory.patchValues(update);
return this.dataSend.saveModel(updateCategory);
}
public delete(viewCategory: ViewCategory): Observable<any> {
const category = viewCategory.category;
return this.dataSend.delete(category);
}
/**
* Checks if a Catagory is on the server already
* @param viewCategory the category to check if it is already on the server
*/
public osInDataStore(viewCategory: ViewCategory): boolean {
const serverCategoryArray = this.DS.getAll(Category);
if (serverCategoryArray.find(cat => cat.id === viewCategory.id)) {
return true;
}
return false;
}
}

View File

@ -66,7 +66,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
* TODO: Remove the viewMotion and make it actually distignuishable from save() * TODO: Remove the viewMotion and make it actually distignuishable from save()
*/ */
public create(update: any, viewMotion?: ViewMotion): Observable<any> { public create(update: any, viewMotion?: ViewMotion): Observable<any> {
return this.save(update, viewMotion); return this.update(update, viewMotion);
} }
/** /**
@ -78,7 +78,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
* @param update the form data containing the update values * @param update the form data containing the update values
* @param viewMotion The View Motion. If not present, a new motion will be created * @param viewMotion The View Motion. If not present, a new motion will be created
*/ */
public save(update: any, viewMotion?: ViewMotion): Observable<any> { public update(update: any, viewMotion?: ViewMotion): Observable<any> {
let updateMotion: Motion; let updateMotion: Motion;
if (viewMotion) { if (viewMotion) {
// implies that an existing motion was updated // implies that an existing motion was updated
@ -109,6 +109,13 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
}); });
} }
update.supporters_id = supporterIds; update.supporters_id = supporterIds;
// category_id: Category -> category_id: number;
const category = update.category_id as Category;
update.category_id = undefined;
if (category) {
update.category_id = category.id;
}
// Update the Motion
updateMotion.patchValues(update); updateMotion.patchValues(update);
return this.dataSend.saveModel(updateMotion); return this.dataSend.saveModel(updateMotion);
} }

View File

@ -27,7 +27,7 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config>
* *
* TODO: used over not-yet-existing detail view * TODO: used over not-yet-existing detail view
*/ */
public save(config: Config, viewConfig: ViewConfig): Observable<Config> { public update(config: Config, viewConfig: ViewConfig): Observable<Config> {
return null; return null;
} }

View File

@ -28,7 +28,7 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
* *
* TODO: used over not-yet-existing detail view * TODO: used over not-yet-existing detail view
*/ */
public save(user: User, viewUser: ViewUser): Observable<User> { public update(user: User, viewUser: ViewUser): Observable<User> {
return null; return null;
} }

View File

@ -21,12 +21,6 @@ body {
padding: 0; padding: 0;
} }
/**the plus button in Motion, Agenda, etc*/
.generic-plus-button {
bottom: -30px;
z-index: 100;
}
.generic-mini-button { .generic-mini-button {
bottom: -28px; bottom: -28px;
z-index: 100; z-index: 100;