restructuring patching of values to ensure type safety

fix disappearing submitters (hopefully)

- replaced "motion from form values" function with deserialize
- fix some typos
- make id id again

null, in contrast to undefined, will be send to the server...

Better typing and changed the create/update signatures in BaseViewModel

- use object as default type for BaseModel.
- use partial objects for updating
- Displayable should not have an id. Moved this into own interface
Identifiable and make the id in the BaseViewMotion abstract.
- create only takes a BaseModel. A ViewModel should not exists so far.
- Updated the update and create method for motions.
This commit is contained in:
Jochen Saalfeld 2018-09-20 12:25:37 +02:00 committed by Sean Engelhardt
parent 48526d6c19
commit d785d77207
36 changed files with 189 additions and 156 deletions

View File

@ -182,7 +182,7 @@ export class DataStoreService {
}); });
} }
private getCollectionString<T extends BaseModel>(collectionType: ModelConstructor<T> | string): string { private getCollectionString<T extends BaseModel<T>>(collectionType: ModelConstructor<T> | string): string {
if (typeof collectionType === 'string') { if (typeof collectionType === 'string') {
return collectionType; return collectionType;
} else { } else {
@ -199,7 +199,7 @@ export class DataStoreService {
* @example: this.DS.get(User, 1) * @example: this.DS.get(User, 1)
* @example: this.DS.get<Countdown>('core/countdown', 2) * @example: this.DS.get<Countdown>('core/countdown', 2)
*/ */
public get<T extends BaseModel>(collectionType: ModelConstructor<T> | string, id: number): T { public get<T extends BaseModel<T>>(collectionType: ModelConstructor<T> | string, id: number): T {
const collectionString = this.getCollectionString<T>(collectionType); const collectionString = this.getCollectionString<T>(collectionType);
const collection: ModelCollection = this.modelStore[collectionString]; const collection: ModelCollection = this.modelStore[collectionString];
@ -219,7 +219,7 @@ export class DataStoreService {
* @example: this.DS.getMany(User, [1,2,3,4,5]) * @example: this.DS.getMany(User, [1,2,3,4,5])
* @example: this.DS.getMany<User>('users/user', [1,2,3,4,5]) * @example: this.DS.getMany<User>('users/user', [1,2,3,4,5])
*/ */
public getMany<T extends BaseModel>(collectionType: ModelConstructor<T> | string, ids: number[]): T[] { public getMany<T extends BaseModel<T>>(collectionType: ModelConstructor<T> | string, ids: number[]): T[] {
const collectionString = this.getCollectionString<T>(collectionType); const collectionString = this.getCollectionString<T>(collectionType);
const collection: ModelCollection = this.modelStore[collectionString]; const collection: ModelCollection = this.modelStore[collectionString];
@ -242,7 +242,7 @@ export class DataStoreService {
* @example: this.DS.getAll(User) * @example: this.DS.getAll(User)
* @example: this.DS.getAll<User>('users/user') * @example: this.DS.getAll<User>('users/user')
*/ */
public getAll<T extends BaseModel>(collectionType: ModelConstructor<T> | string): T[] { public getAll<T extends BaseModel<T>>(collectionType: ModelConstructor<T> | string): T[] {
const collectionString = this.getCollectionString<T>(collectionType); const collectionString = this.getCollectionString<T>(collectionType);
const collection: ModelCollection = this.modelStore[collectionString]; const collection: ModelCollection = this.modelStore[collectionString];
@ -261,7 +261,7 @@ export class DataStoreService {
* @return The BaseModel-list corresponding to the filter function * @return The BaseModel-list corresponding to the filter function
* @example this.DS.filter<User>(User, myUser => myUser.first_name === "Max") * @example this.DS.filter<User>(User, myUser => myUser.first_name === "Max")
*/ */
public filter<T extends BaseModel>( public filter<T extends BaseModel<T>>(
collectionType: ModelConstructor<T> | string, collectionType: ModelConstructor<T> | string,
callback: (model: T) => boolean callback: (model: T) => boolean
): T[] { ): T[] {

View File

@ -19,7 +19,7 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
* plusButton=true * plusButton=true
* [menuList]=myMenu * [menuList]=myMenu
* (plusButtonClicked)=onPlusButton() * (plusButtonClicked)=onPlusButton()
* (ellipsisMenuItem)=onEllipsisItem($event) * (ellipsisMenuItem)=onEllipsisItem($event)>
* </os-head-bar> * </os-head-bar>
* ``` * ```
* *

View File

@ -2,10 +2,10 @@
<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"> <div *ngIf="!multiple">
<mat-option><span translate>None</span></mat-option> <mat-option [value]="null"><span translate>None</span></mat-option>
<mat-divider></mat-divider> <mat-divider></mat-divider>
</div> </div>
<mat-option *ngFor="let selectedItem of filteredItems | async" [value]="selectedItem"> <mat-option *ngFor="let selectedItem of filteredItems | async" [value]="selectedItem.id">
{{selectedItem.getTitle(translate)}} {{selectedItem.getTitle(translate)}}
</mat-option> </mat-option>
</mat-select> </mat-select>

View File

@ -1,10 +1,13 @@
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 { Subject, BehaviorSubject } from 'rxjs'; import { Subject, ReplaySubject, 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';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Identifiable } from '../../models/base/identifiable';
type Selectable = Displayable & Identifiable;
/** /**
* Reusable Searchable Value Selector * Reusable Searchable Value Selector
@ -37,7 +40,7 @@ import { TranslateService } from '@ngx-translate/core';
}) })
export class SearchValueSelectorComponent implements OnInit { export class SearchValueSelectorComponent implements OnInit {
/** /**
* ngModel variable - Depricated with Angular 7 * ngModel variable - Deprecated with Angular 7
* DO NOT USE: READ AT remove() FUNCTION! * DO NOT USE: READ AT remove() FUNCTION!
*/ */
public myModel = []; public myModel = [];
@ -48,9 +51,9 @@ export class SearchValueSelectorComponent implements OnInit {
public filterControl = new FormControl(); public filterControl = new FormControl();
/** /**
* List of the filtered content, when entering somithing in the search bar * List of the filtered content, when entering something in the search bar
*/ */
public filteredItems: BehaviorSubject<Displayable[]>; public filteredItems: ReplaySubject<Selectable[]> = new ReplaySubject<Selectable[]>(1);
/** /**
* Decide if this should be a single or multi-select-field * Decide if this should be a single or multi-select-field
@ -62,7 +65,7 @@ export class SearchValueSelectorComponent implements OnInit {
* The Input List Values * The Input List Values
*/ */
@Input() @Input()
public InputListValues: BehaviorSubject<Displayable[]>; public InputListValues: BehaviorSubject<Selectable[]>;
/** /**
* Placeholder of the List * Placeholder of the List
@ -111,7 +114,7 @@ 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 {
this.filteredItems = this.InputListValues; this.filteredItems.next(this.InputListValues.getValue());
// 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();
@ -152,7 +155,7 @@ export class SearchValueSelectorComponent implements OnInit {
* places, but can't reflect the changes in both places. Until this can be done this will be unused code * places, but can't reflect the changes in both places. Until this can be done this will be unused code
* @param item the selected item to be removed * @param item the selected item to be removed
*/ */
public remove(item: Displayable): void { public remove(item: Selectable): void {
const myArr = this.thisSelector.value; const myArr = this.thisSelector.value;
const index = myArr.indexOf(item, 0); const index = myArr.indexOf(item, 0);
// my model was the form according to fix // my model was the form according to fix

View File

@ -2,15 +2,18 @@ import { OpenSlidesComponent } from 'app/openslides.component';
import { Deserializable } from './deserializable'; import { Deserializable } from './deserializable';
import { CollectionStringModelMapperService } from '../../../core/services/collectionStringModelMapper.service'; import { CollectionStringModelMapperService } from '../../../core/services/collectionStringModelMapper.service';
import { Displayable } from './displayable'; import { Displayable } from './displayable';
import { Identifiable } from './identifiable';
export interface ModelConstructor<T extends BaseModel> { export interface ModelConstructor<T extends BaseModel<T>> {
new (...args: any[]): T; new (...args: any[]): T;
} }
/** /**
* Abstract parent class to set rules and functions for all models. * Abstract parent class to set rules and functions for all models.
* When inherit from this class, give the subclass as the type. E.g. `class Motion extends BaseModel<Motion>`
*/ */
export abstract class BaseModel extends OpenSlidesComponent implements Deserializable, Displayable { export abstract class BaseModel<T = object> extends OpenSlidesComponent
implements Deserializable, Displayable, Identifiable {
/** /**
* Register the collection string to the type. * Register the collection string to the type.
* @param collectionString * @param collectionString
@ -57,6 +60,10 @@ export abstract class BaseModel extends OpenSlidesComponent implements Deseriali
}); });
} }
public patchValues(update: Partial<T>): void {
Object.assign(this, update);
}
public abstract getTitle(): string; public abstract getTitle(): string;
public getListTitle(): string { public getListTitle(): string {

View File

@ -13,5 +13,5 @@ export interface Deserializable {
* should be used to assign JSON values to the object itself. * should be used to assign JSON values to the object itself.
* @param input * @param input
*/ */
deserialize(input: any): void; deserialize(input: object): void;
} }

View File

@ -0,0 +1,9 @@
/**
* Every object implementing this interface has an id.
*/
export interface Identifiable {
/**
* The objects id.
*/
id: number;
}

View File

@ -1,7 +1,7 @@
import { BaseModel } from './base-model'; import { BaseModel } from './base-model';
import { Projectable } from './projectable'; import { Projectable } from './projectable';
export abstract class ProjectableBaseModel extends BaseModel implements Projectable { export abstract class ProjectableBaseModel extends BaseModel<ProjectableBaseModel> implements Projectable {
protected constructor(collectionString: string, input?: any) { protected constructor(collectionString: string, input?: any) {
super(collectionString, input); super(collectionString, input);
} }

View File

@ -4,7 +4,7 @@ import { BaseModel } from '../base/base-model';
* Representation of chat messages. * Representation of chat messages.
* @ignore * @ignore
*/ */
export class ChatMessage extends BaseModel { export class ChatMessage extends BaseModel<ChatMessage> {
public id: number; public id: number;
public message: string; public message: string;
public timestamp: string; // TODO: Type for timestamp public timestamp: string; // TODO: Type for timestamp

View File

@ -4,7 +4,7 @@ import { BaseModel } from '../base/base-model';
* Representation of a projector. Has the nested property "projectiondefaults" * Representation of a projector. Has the nested property "projectiondefaults"
* @ignore * @ignore
*/ */
export class Projector extends BaseModel { export class Projector extends BaseModel<Projector> {
public id: number; public id: number;
public elements: Object; public elements: Object;
public scale: number; public scale: number;

View File

@ -4,7 +4,7 @@ import { BaseModel } from '../base/base-model';
* Representation of a tag. * Representation of a tag.
* @ignore * @ignore
*/ */
export class Tag extends BaseModel { export class Tag extends BaseModel<Tag> {
public id: number; public id: number;
public name: string; public name: string;

View File

@ -4,7 +4,7 @@ import { BaseModel } from '../base/base-model';
* Representation of a motion category. Has the nested property "File" * Representation of a motion category. Has the nested property "File"
* @ignore * @ignore
*/ */
export class Category extends BaseModel { export class Category extends BaseModel<Category> {
public id: number; public id: number;
public name: string; public name: string;
public prefix: string; public prefix: string;
@ -16,13 +16,6 @@ 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

@ -4,7 +4,7 @@ import { BaseModel } from '../base/base-model';
* Representation of a motion change recommendation. * Representation of a motion change recommendation.
* @ignore * @ignore
*/ */
export class MotionChangeReco extends BaseModel { export class MotionChangeReco extends BaseModel<MotionChangeReco> {
public id: number; public id: number;
public motion_version_id: number; public motion_version_id: number;
public rejected: boolean; public rejected: boolean;

View File

@ -4,7 +4,7 @@ import { BaseModel } from '../base/base-model';
* Representation of a motion category. Has the nested property "File" * Representation of a motion category. Has the nested property "File"
* @ignore * @ignore
*/ */
export class MotionCommentSection extends BaseModel { export class MotionCommentSection extends BaseModel<MotionCommentSection> {
public id: number; public id: number;
public name: string; public name: string;
public read_groups_id: number[]; public read_groups_id: number[];

View File

@ -44,13 +44,6 @@ export class Motion extends AgendaBaseModel {
super('motions/motion', 'Motion', input); super('motions/motion', 'Motion', input);
} }
/**
* update the values of the motion with new values
*/
public patchValues(update: object): void {
Object.assign(this, update);
}
/** /**
* returns the motion submitters userIDs * returns the motion submitters userIDs
*/ */

View File

@ -5,7 +5,7 @@ import { WorkflowState } from './workflow-state';
* Representation of a motion workflow. Has the nested property 'states' * Representation of a motion workflow. Has the nested property 'states'
* @ignore * @ignore
*/ */
export class Workflow extends BaseModel { export class Workflow extends BaseModel<Workflow> {
public id: number; public id: number;
public name: string; public name: string;
public states: WorkflowState[]; public states: WorkflowState[];

View File

@ -4,7 +4,7 @@ import { BaseModel } from '../base/base-model';
* Representation of user group. * Representation of user group.
* @ignore * @ignore
*/ */
export class Group extends BaseModel { export class Group extends BaseModel<Group> {
public id: number; public id: number;
public name: string; public name: string;
public permissions: string[]; public permissions: string[];

View File

@ -4,7 +4,7 @@ import { BaseModel } from '../base/base-model';
* Representation of users personal note. * Representation of users personal note.
* @ignore * @ignore
*/ */
export class PersonalNote extends BaseModel { export class PersonalNote extends BaseModel<PersonalNote> {
public id: number; public id: number;
public user_id: number; public user_id: number;
public notes: Object; public notes: Object;

View File

@ -1,6 +1,5 @@
import { BaseViewModel } from '../../base/base-view-model'; import { BaseViewModel } from '../../base/base-view-model';
import { Item } from '../../../shared/models/agenda/item'; import { Item } from '../../../shared/models/agenda/item';
import { BaseModel } from '../../../shared/models/base/base-model';
import { AgendaBaseModel } from '../../../shared/models/base/agenda-base-model'; import { AgendaBaseModel } from '../../../shared/models/base/agenda-base-model';
export class ViewItem extends BaseViewModel { export class ViewItem extends BaseViewModel {
@ -46,8 +45,8 @@ export class ViewItem extends BaseViewModel {
} }
} }
public updateValues(update: BaseModel): void { public updateValues(update: Item): void {
if (update instanceof Item && this.id === update.id) { if (this.id === update.id) {
this._item = update; this._item = update;
} }
} }

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 update(item: Item, viewUser: ViewItem): Observable<Item> { public update(item: Partial<Item>, viewUser: ViewItem): Observable<Item> {
return null; return null;
} }
@ -67,7 +67,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 create(item: Item, viewItem: ViewItem): Observable<Item> { public create(item: Item): Observable<Item> {
return null; return null;
} }

View File

@ -10,6 +10,10 @@ export class ViewAssignment extends BaseViewModel {
private _agendaItem: Item; private _agendaItem: Item;
private _tags: Tag[]; private _tags: Tag[];
public get id(): number {
return this._assignment ? this._assignment.id : null;
}
public get assignment(): Assignment { public get assignment(): Assignment {
return this._assignment; return this._assignment;
} }

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 update(assignment: Assignment, viewAssignment: ViewAssignment): Observable<Assignment> { public update(assignment: Partial<Assignment>, viewAssignment: ViewAssignment): Observable<Assignment> {
return null; return null;
} }
@ -33,7 +33,7 @@ export class AssignmentRepositoryService extends BaseRepository<ViewAssignment,
return null; return null;
} }
public create(assignment: Assignment, viewAssignment: ViewAssignment): Observable<Assignment> { public create(assignment: Assignment): 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 update(update: M, viewModel: V): Observable<M>; public abstract update(update: Partial<M>, viewModel: V): Observable<M>;
/** /**
* Deletes a given Model * Deletes a given Model
@ -90,7 +90,7 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
* @param viewModel the view model that the update is based on * @param viewModel the view model that the update is based on
* TODO: remove the viewModel * TODO: remove the viewModel
*/ */
public abstract create(update: M, viewModel: V): Observable<M>; public abstract create(update: M): Observable<M>;
/** /**
* Creates a view model out of a base model. * Creates a view model out of a base model.

View File

@ -1,10 +1,16 @@
import { BaseModel } from '../../shared/models/base/base-model'; import { BaseModel } from '../../shared/models/base/base-model';
import { Displayable } from '../../shared/models/base/displayable'; import { Displayable } from '../../shared/models/base/displayable';
import { Identifiable } from '../../shared/models/base/identifiable';
/** /**
* Base class for view models. alls view models should have titles. * Base class for view models. alls view models should have titles.
*/ */
export abstract class BaseViewModel implements Displayable { export abstract class BaseViewModel implements Displayable, Identifiable {
/**
* Force children to have an id.
*/
public abstract id: number;
public abstract updateValues(update: BaseModel): void; public abstract updateValues(update: BaseModel): void;
public abstract getTitle(): string; public abstract getTitle(): string;

View File

@ -1,12 +1,15 @@
import { BaseViewModel } from '../../base/base-view-model'; import { BaseViewModel } from '../../base/base-view-model';
import { Mediafile } from '../../../shared/models/mediafiles/mediafile'; import { Mediafile } from '../../../shared/models/mediafiles/mediafile';
import { User } from '../../../shared/models/users/user'; import { User } from '../../../shared/models/users/user';
import { BaseModel } from '../../../shared/models/base/base-model';
export class ViewMediafile extends BaseViewModel { export class ViewMediafile extends BaseViewModel {
private _mediafile: Mediafile; private _mediafile: Mediafile;
private _uploader: User; private _uploader: User;
public get id(): number {
return this._mediafile ? this._mediafile.id : null;
}
public get mediafile(): Mediafile { public get mediafile(): Mediafile {
return this._mediafile; return this._mediafile;
} }
@ -49,11 +52,9 @@ export class ViewMediafile extends BaseViewModel {
return this.title; return this.title;
} }
public updateValues(update: BaseModel): void { public updateValues(update: Mediafile): void {
if (update instanceof Mediafile) { if (this.mediafile.id === update.id) {
if (this.mediafile.id === update.id) { this._mediafile = update;
this._mediafile = update;
}
} }
} }
} }

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 update(file: Mediafile, viewFile: ViewMediafile): Observable<Mediafile> { public update(file: Partial<Mediafile>, viewFile: ViewMediafile): Observable<Mediafile> {
return null; return null;
} }
@ -45,7 +45,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 create(file: Mediafile, viewFile: ViewMediafile): Observable<Mediafile> { public create(file: Mediafile): Observable<Mediafile> {
return null; return null;
} }

View File

@ -87,9 +87,9 @@ export class CategoryListComponent extends BaseComponent implements OnInit, OnDe
*/ */
private saveCategory(viewCategory: ViewCategory): void { private saveCategory(viewCategory: ViewCategory): void {
if (this.repo.osInDataStore(viewCategory)) { if (this.repo.osInDataStore(viewCategory)) {
this.repo.create(viewCategory).subscribe(); this.repo.update(viewCategory.category).subscribe();
} else { } else {
this.repo.update(viewCategory).subscribe(); this.repo.create(viewCategory.category, viewCategory).subscribe();
} }
viewCategory.edit = false; viewCategory.edit = false;
} }

View File

@ -148,7 +148,8 @@
<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]="this.submitterObserver"></os-search-value-selector> <os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="this.metaInfoForm.get('submitters_id')"
[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 +165,8 @@
<!-- 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]="this.supporterObserver"></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()">
@ -222,7 +224,8 @@
{{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]="this.categoryObserver"></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,8 +10,9 @@ 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'; import { TranslateService } from '@ngx-translate/core';
import { Motion } from '../../../../shared/models/motions/motion';
import { BehaviorSubject } from 'rxjs';
/** /**
* Component for the motion detail view * Component for the motion detail view
@ -122,7 +123,7 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
// Initial Filling of the Subjects // Initial Filling of the Subjects
this.submitterObserver = new BehaviorSubject(DS.getAll(User)); this.submitterObserver = new BehaviorSubject(DS.getAll(User));
this.supporterObserver = new BehaviorSubject(DS.getAll(User)); this.supporterObserver = new BehaviorSubject(DS.getAll(User));
this.categoryObserver = new BehaviorSubject(this.DS.getAll(Category)); this.categoryObserver = new BehaviorSubject(DS.getAll(Category));
// Make sure the subjects are updated, when a new Model for the type arrives // Make sure the subjects are updated, when a new Model for the type arrives
this.DS.changeObservable.subscribe(newModel => { this.DS.changeObservable.subscribe(newModel => {
@ -141,9 +142,9 @@ 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.category, category_id: formMotion.categoryId,
supporters_id: formMotion.supporters, supporters_id: formMotion.supporterIds,
submitters: formMotion.submitters, submitters_id: formMotion.submitterIds,
state_id: formMotion.stateId, state_id: formMotion.stateId,
recommendation_id: formMotion.recommendationId, recommendation_id: formMotion.recommendationId,
identifier: formMotion.identifier, identifier: formMotion.identifier,
@ -167,7 +168,7 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
category_id: [''], category_id: [''],
state_id: [''], state_id: [''],
recommendation_id: [''], recommendation_id: [''],
submitters: [''], submitters_id: [''],
supporters_id: [''], supporters_id: [''],
origin: [''] origin: ['']
}); });
@ -191,12 +192,15 @@ export class MotionDetailComponent extends BaseComponent implements OnInit {
*/ */
public saveMotion(): void { public saveMotion(): void {
const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value }; const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value };
const fromForm = new Motion();
fromForm.deserialize(newMotionValues);
if (this.newMotion) { if (this.newMotion) {
this.repo.create(newMotionValues).subscribe(response => { this.repo.create(fromForm).subscribe(response => {
this.router.navigate(['./motions/' + response.id]); this.router.navigate(['./motions/' + response.id]);
}); });
} else { } else {
this.repo.update(newMotionValues, this.motionCopy).subscribe(); this.repo.update(fromForm, this.motionCopy).subscribe();
} }
} }

View File

@ -6,7 +6,6 @@ import { WorkflowState } from '../../../shared/models/motions/workflow-state';
import { BaseModel } from '../../../shared/models/base/base-model'; import { BaseModel } from '../../../shared/models/base/base-model';
import { BaseViewModel } from '../../base/base-view-model'; import { BaseViewModel } from '../../base/base-view-model';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
/** /**
* Motion class for the View * Motion class for the View
* *
@ -58,10 +57,18 @@ export class ViewMotion extends BaseViewModel {
return this._submitters; return this._submitters;
} }
public get submitterIds(): number[] {
return this.motion ? this.motion.submitters_id : null;
}
public get supporters(): User[] { public get supporters(): User[] {
return this._supporters; return this._supporters;
} }
public get supporterIds(): number[] {
return this.motion ? this.motion.supporters_id : null;
}
public get workflow(): Workflow { public get workflow(): Workflow {
return this._workflow; return this._workflow;
} }
@ -101,6 +108,32 @@ export class ViewMotion extends BaseViewModel {
return this.state && this.workflow ? this.state.getNextStates(this.workflow) : null; return this.state && this.workflow ? this.state.getNextStates(this.workflow) : null;
} }
public set supporters(users: User[]) {
const userIDArr: number[] = [];
users.forEach(user => {
userIDArr.push(user.id);
});
this._supporters = users;
this._motion.supporters_id = userIDArr;
}
public set submitters(users: User[]) {
// For the newer backend with weight:
// const submitterArr: MotionSubmitter[] = []
// users.forEach(user => {
// const motionSub = new MotionSubmitter();
// submitterArr.push(motionSub);
// });
// this._motion.submitters = submitterArr;
this._submitters = users;
const submitterIDArr: number[] = [];
// for the older backend:
users.forEach(user => {
submitterIDArr.push(user.id);
});
this._motion.submitters_id = submitterIDArr;
}
public constructor( public constructor(
motion?: Motion, motion?: Motion,
category?: Category, category?: Category,
@ -129,18 +162,32 @@ export class ViewMotion extends BaseViewModel {
*/ */
public updateValues(update: BaseModel): void { public updateValues(update: BaseModel): void {
if (update instanceof Workflow) { if (update instanceof Workflow) {
if (this.motion && update.id === this.motion.workflow_id) { this.updateWorkflow(update as Workflow);
this._workflow = update as Workflow;
}
} else if (update instanceof Category) { } else if (update instanceof Category) {
if (this.motion && update.id === this.motion.category_id) { this.updateCategory(update as Category);
this._category = update as Category;
}
} }
// TODO: There is no way (yet) to add Submitters to a motion // TODO: There is no way (yet) to add Submitters to a motion
// Thus, this feature could not be tested // Thus, this feature could not be tested
} }
/**
* Updates the Category
*/
public updateCategory(update: Category): void {
if (this.motion && update.id === this.motion.category_id) {
this._category = update as Category;
}
}
/**
* updates the Workflow
*/
public updateWorkflow(update: Workflow): void {
if (this.motion && update.id === this.motion.workflow_id) {
this._workflow = update as Workflow;
}
}
public hasSupporters(): boolean { public hasSupporters(): boolean {
return !!(this.supporters && this.supporters.length > 0); return !!(this.supporters && this.supporters.length > 0);
} }

View File

@ -34,26 +34,17 @@ export class CategoryRepositoryService extends BaseRepository<ViewCategory, Cate
return new ViewCategory(category); return new ViewCategory(category);
} }
public create(update: object, viewCategory?: ViewCategory): Observable<any> { public create(update: Category, viewCategory?: ViewCategory): Observable<any> {
const categories = this.DS.getAll(Category); console.log('update: ', update);
const categoryIds: number[] = []; console.log('viewCategory: ', viewCategory);
if (update instanceof Category) { if (this.osInDataStore(viewCategory)) {
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); return this.update(update, viewCategory);
} else { } else {
return this.dataSend.saveModel(viewCategory.category); return this.dataSend.saveModel(viewCategory.category);
} }
} }
public update(update: object, viewCategory?: ViewCategory): Observable<any> { public update(update: Category, viewCategory?: ViewCategory): Observable<any> {
let updateCategory: Category; let updateCategory: Category;
if (viewCategory) { if (viewCategory) {
updateCategory = viewCategory.category; updateCategory = viewCategory.category;

View File

@ -65,8 +65,8 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
* @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
* 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(motion: Motion): Observable<any> {
return this.update(update, viewMotion); return this.dataSend.saveModel(motion);
} }
/** /**
@ -78,46 +78,10 @@ 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 update(update: any, viewMotion?: ViewMotion): Observable<any> { public update(update: Partial<Motion>, viewMotion: ViewMotion): Observable<any> {
let updateMotion: Motion; const motion = viewMotion.motion;
if (viewMotion) { motion.patchValues(update);
// implies that an existing motion was updated return this.dataSend.saveModel(motion);
updateMotion = viewMotion.motion;
} else {
// implies that a new motion was created
updateMotion = new Motion();
}
// submitters: User[] -> submitter: MotionSubmitter[]
const submitters = update.submitters as User[];
// The server doesn't really accept MotionSubmitter arrays on create.
// We simply need to send an number[] on create.
// MotionSubmitter[] should be send on update
update.submitters = undefined;
const submitterIds: number[] = [];
if (submitters.length > 0) {
submitters.forEach(submitter => {
submitterIds.push(submitter.id);
});
}
update.submitters_id = submitterIds;
// supporters[]: User -> supporters_id: number[];
const supporters = update.supporters_id as User[];
const supporterIds: number[] = [];
if (supporters.length > 0) {
supporters.forEach(supporter => {
supporterIds.push(supporter.id);
});
}
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);
return this.dataSend.saveModel(updateMotion);
} }
/** /**

View File

@ -1,5 +1,4 @@
import { BaseViewModel } from '../../base/base-view-model'; import { BaseViewModel } from '../../base/base-view-model';
import { BaseModel } from '../../../shared/models/base/base-model';
import { Config } from '../../../shared/models/core/config'; import { Config } from '../../../shared/models/core/config';
export class ViewConfig extends BaseViewModel { export class ViewConfig extends BaseViewModel {
@ -30,9 +29,7 @@ export class ViewConfig extends BaseViewModel {
return this.key; return this.key;
} }
public updateValues(update: BaseModel): void { public updateValues(update: Config): void {
if (update instanceof Config && this.id === update.id) { this._config = update;
this._config = update;
}
} }
} }

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 update(config: Config, viewConfig: ViewConfig): Observable<Config> { public update(config: Partial<Config>, viewConfig: ViewConfig): Observable<Config> {
return null; return null;
} }
@ -47,7 +47,7 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config>
* *
* Function exists solely to correctly implement {@link BaseRepository} * Function exists solely to correctly implement {@link BaseRepository}
*/ */
public create(config: Config, viewConfig: ViewConfig): Observable<Config> { public create(config: Config): Observable<Config> {
return null; return null;
} }

View File

@ -7,6 +7,10 @@ export class ViewUser extends BaseViewModel {
private _user: User; private _user: User;
private _groups: Group[]; private _groups: Group[];
public get id(): number {
return this._user ? this._user.id : null;
}
public get user(): User { public get user(): User {
return this._user; return this._user;
} }
@ -47,22 +51,30 @@ export class ViewUser extends BaseViewModel {
console.log('replace group - not yet implemented, ', newGroup); console.log('replace group - not yet implemented, ', newGroup);
} }
public updateValues(update: BaseModel): void {
if (update instanceof Group) {
this.updateGroup(update as Group);
}
if (update instanceof User) {
this.updateUser(update as User);
}
}
public updateGroup(update: Group): void {
if (this.user && this.user.groups_id) {
if (this.user.containsGroupId(update.id)) {
this.replaceGroup(update);
}
}
}
/** /**
* Updates values. Triggered through observables. * Updates values. Triggered through observables.
* *
* @param update a new User or Group * @param update a new User or Group
*/ */
public updateValues(update: BaseModel): void { public updateUser(update: User): void {
if (update instanceof User) { if (this.user.id === update.id) {
if (this.user.id === update.id) { this._user = update;
this._user = update;
}
} else if (update instanceof Group) {
if (this.user && this.user.groups_id) {
if (this.user.containsGroupId(update.id)) {
this.replaceGroup(update);
}
}
} }
} }
} }

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 update(user: User, viewUser: ViewUser): Observable<User> { public update(user: Partial<User>, viewUser: ViewUser): Observable<User> {
return null; return null;
} }
@ -46,7 +46,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 create(user: User, viewFile: ViewUser): Observable<User> { public create(user: User): Observable<User> {
return null; return null;
} }