Implement tags
This commit is contained in:
parent
e3cf8102de
commit
126a16b553
@ -10,6 +10,7 @@ import { ConfigAppConfig } from '../../site/config/config.config';
|
||||
import { AgendaAppConfig } from '../../site/agenda/agenda.config';
|
||||
import { AssignmentsAppConfig } from '../../site/assignments/assignments.config';
|
||||
import { UsersAppConfig } from '../../site/users/users.config';
|
||||
import { TagAppConfig } from '../../site/tags/tag.config';
|
||||
import { MainMenuService } from './main-menu.service';
|
||||
|
||||
/**
|
||||
@ -22,6 +23,7 @@ const appConfigs: AppConfig[] = [
|
||||
AssignmentsAppConfig,
|
||||
MotionsAppConfig,
|
||||
MediafileAppConfig,
|
||||
TagAppConfig,
|
||||
UsersAppConfig
|
||||
];
|
||||
|
||||
|
@ -42,6 +42,10 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'users',
|
||||
loadChildren: './users/users.module#UsersModule'
|
||||
},
|
||||
{
|
||||
path: 'tags',
|
||||
loadChildren: './tags/tag.module#TagModule'
|
||||
}
|
||||
],
|
||||
canActivateChild: [AuthGuard]
|
||||
|
33
client/src/app/site/tags/components/tag-list.component.html
Normal file
33
client/src/app/site/tags/components/tag-list.component.html
Normal file
@ -0,0 +1,33 @@
|
||||
<os-head-bar [nav]="false" [backButton]=true [allowEdit]="true" [editMode]="editTag" editIcon="add" (editEvent)="setEditMode($event)"
|
||||
(saveEvent)="saveTag()">
|
||||
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2 *ngIf="!editTag && !newTag" translate>Tags</h2>
|
||||
<form *ngIf="editTag" [formGroup]="tagForm" (ngSubmit)="saveTag()" (keydown)="keyDownFunction($event)">
|
||||
<mat-form-field>
|
||||
<input type="text" matInput osAutofocus required formControlName="name" placeholder="{{ 'New tag name' | translate}}">
|
||||
<mat-error *ngIf="tagForm.invalid" translate>A tag name is required</mat-error>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- remove button -->
|
||||
<div class="extra-controls-slot on-transition-fade">
|
||||
<button *ngIf="editTag && !newTag" type="button" mat-button (click)="deleteSelectedTag()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span translate>Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</os-head-bar>
|
||||
|
||||
<mat-table class='os-listview-table on-transition-fade' [dataSource]="dataSource" matSort>
|
||||
<ng-container matColumnDef="name">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
|
||||
<mat-cell *matCellDef="let tag"> {{tag.getTitle()}} </mat-cell>
|
||||
</ng-container>
|
||||
<mat-header-row *matHeaderRowDef="['name']"></mat-header-row>
|
||||
<mat-row (click)='selectTag(row)' *matRowDef="let row; columns: ['name']"></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>
|
@ -0,0 +1,26 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TagListComponent } from './tag-list.component';
|
||||
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||
|
||||
describe('TagListComponent', () => {
|
||||
let component: TagListComponent;
|
||||
let fixture: ComponentFixture<TagListComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
declarations: [TagListComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TagListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
140
client/src/app/site/tags/components/tag-list.component.ts
Normal file
140
client/src/app/site/tags/components/tag-list.component.ts
Normal file
@ -0,0 +1,140 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { Tag } from '../../../shared/models/core/tag';
|
||||
import { ListViewBaseComponent } from '../../base/list-view-base';
|
||||
import { TagRepositoryService } from '../services/tag-repository.service';
|
||||
import { ViewTag } from '../models/view-tag';
|
||||
import { FormGroup, FormControl, Validators } from '@angular/forms';
|
||||
import { PromptService } from '../../../core/services/prompt.service';
|
||||
|
||||
/**
|
||||
* Listview for the complete lsit of available Tags
|
||||
* ### Usage:
|
||||
* ```html
|
||||
* <os-tag-list></os-tag-list>
|
||||
* ```
|
||||
*/
|
||||
@Component({
|
||||
selector: 'os-tag-list',
|
||||
templateUrl: './tag-list.component.html',
|
||||
styleUrls: ['./tag-list.component.css']
|
||||
})
|
||||
export class TagListComponent extends ListViewBaseComponent<ViewTag> implements OnInit {
|
||||
public editTag = false;
|
||||
public newTag = false;
|
||||
public selectedTag: ViewTag;
|
||||
|
||||
@ViewChild('tagForm')
|
||||
public tagForm: FormGroup;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param titleService
|
||||
* @param translate
|
||||
* @param repo the repository
|
||||
*/
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
translate: TranslateService,
|
||||
private repo: TagRepositoryService,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(titleService, translate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init function.
|
||||
* Sets the title, inits the table and calls the repo.
|
||||
*/
|
||||
public ngOnInit(): void {
|
||||
super.setTitle('Tags');
|
||||
this.initTable();
|
||||
this.tagForm = new FormGroup({ name: new FormControl('', Validators.required) });
|
||||
this.repo.getViewModelListObservable().subscribe(newTags => {
|
||||
this.dataSource.data = newTags;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a new or updates tag to the dataStore
|
||||
*/
|
||||
public saveTag(): void {
|
||||
if (this.editTag && this.newTag) {
|
||||
this.submitNewTag();
|
||||
} else if (this.editTag && !this.newTag) {
|
||||
this.submitEditedTag();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a newly created tag.
|
||||
*/
|
||||
public submitNewTag(): void {
|
||||
if (this.tagForm.value && this.tagForm.valid) {
|
||||
this.repo.create(this.tagForm.value).subscribe(response => {
|
||||
if (response) {
|
||||
this.tagForm.reset();
|
||||
this.cancelEditing();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an edited tag.
|
||||
*/
|
||||
public submitEditedTag(): void {
|
||||
if (this.tagForm.value && this.tagForm.valid) {
|
||||
const updateData = new Tag({ name: this.tagForm.value.name });
|
||||
|
||||
this.repo.update(updateData, this.selectedTag).subscribe(response => {
|
||||
if (response) {
|
||||
this.cancelEditing();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the selected Tag after a successful confirmation.
|
||||
* @async
|
||||
*/
|
||||
public async deleteSelectedTag(): Promise<any> {
|
||||
const content = this.translate.instant('Delete') + ` ${this.selectedTag.name}?`;
|
||||
if (await this.promptService.open(this.translate.instant('Are you sure?'), content)) {
|
||||
this.repo.delete(this.selectedTag).subscribe(response => {
|
||||
this.cancelEditing();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public cancelEditing(): void {
|
||||
this.newTag = false;
|
||||
this.editTag = false;
|
||||
this.tagForm.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a row in the table
|
||||
* @param viewTag
|
||||
*/
|
||||
public selectTag(viewTag: ViewTag): void {
|
||||
this.selectedTag = viewTag;
|
||||
this.setEditMode(true, false);
|
||||
this.tagForm.setValue({ name: this.selectedTag.name });
|
||||
}
|
||||
|
||||
public setEditMode(mode: boolean, newTag: boolean = true): void {
|
||||
this.editTag = mode;
|
||||
this.newTag = newTag;
|
||||
if (!mode) {
|
||||
this.cancelEditing();
|
||||
}
|
||||
}
|
||||
public keyDownFunction(event: KeyboardEvent): void {
|
||||
if (event.keyCode === 27) {
|
||||
this.cancelEditing();
|
||||
}
|
||||
}
|
||||
}
|
42
client/src/app/site/tags/models/view-tag.ts
Normal file
42
client/src/app/site/tags/models/view-tag.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { Tag } from '../../../shared/models/core/tag';
|
||||
import { BaseViewModel } from '../../base/base-view-model';
|
||||
|
||||
/**
|
||||
* Tag view class
|
||||
*
|
||||
* Stores a Tag including all (implicit) references
|
||||
* Provides "safe" access to variables and functions in {@link Tag}
|
||||
* @ignore
|
||||
*/
|
||||
export class ViewTag extends BaseViewModel {
|
||||
private _tag: Tag;
|
||||
|
||||
public constructor(tag: Tag) {
|
||||
super();
|
||||
this._tag = tag;
|
||||
}
|
||||
|
||||
public get tag(): Tag {
|
||||
return this._tag;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this.tag ? this.tag.id : null;
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this.tag ? this.tag.name : null;
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the local objects if required
|
||||
* @param update
|
||||
*/
|
||||
public updateValues(update: Tag): void {
|
||||
this._tag = update;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { TagRepositoryService } from './tag-repository.service';
|
||||
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||
|
||||
describe('TagRepositoryService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
providers: [TagRepositoryService]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
const service = TestBed.get(TagRepositoryService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
54
client/src/app/site/tags/services/tag-repository.service.ts
Normal file
54
client/src/app/site/tags/services/tag-repository.service.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Tag } from '../../../shared/models/core/tag';
|
||||
import { ViewTag } from '../models/view-tag';
|
||||
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';
|
||||
import { HTTPMethod } from 'app/core/services/http.service';
|
||||
|
||||
/**
|
||||
* Repository Services for Tags
|
||||
*
|
||||
* 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 TagRepositoryService extends BaseRepository<ViewTag, Tag> {
|
||||
/**
|
||||
* Creates a TagRepository
|
||||
* Converts existing and incoming Tags to ViewTags
|
||||
* Handles CRUD using an observer to the DataStore
|
||||
* @param DataSend
|
||||
*/
|
||||
public constructor(protected DS: DataStoreService, private dataSend: DataSendService) {
|
||||
super(DS, Tag);
|
||||
}
|
||||
|
||||
protected createViewModel(tag: Tag): ViewTag {
|
||||
return new ViewTag(tag);
|
||||
}
|
||||
|
||||
public create(update: Tag): Observable<any> {
|
||||
const newTag = new Tag();
|
||||
newTag.patchValues(update);
|
||||
return this.dataSend.createModel(newTag);
|
||||
}
|
||||
|
||||
public update(update: Partial<Tag>, viewTag: ViewTag): Observable<any> {
|
||||
const updateTag = new Tag();
|
||||
updateTag.patchValues(viewTag.tag);
|
||||
updateTag.patchValues(update);
|
||||
return this.dataSend.updateModel(updateTag, HTTPMethod.PUT);
|
||||
}
|
||||
|
||||
public delete(viewTag: ViewTag): Observable<any> {
|
||||
return this.dataSend.deleteModel(viewTag.tag);
|
||||
}
|
||||
}
|
11
client/src/app/site/tags/tag-routing.module.ts
Normal file
11
client/src/app/site/tags/tag-routing.module.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { TagListComponent } from './components/tag-list.component';
|
||||
|
||||
const routes: Routes = [{ path: '', component: TagListComponent }];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class TagRoutingModule {}
|
16
client/src/app/site/tags/tag.config.ts
Normal file
16
client/src/app/site/tags/tag.config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { AppConfig } from '../base/app-config';
|
||||
import { Tag } from '../../shared/models/core/tag';
|
||||
|
||||
export const TagAppConfig: AppConfig = {
|
||||
name: 'tag',
|
||||
models: [{ collectionString: 'core/tag', model: Tag }],
|
||||
mainMenuEntries: [
|
||||
{
|
||||
route: '/tags',
|
||||
displayName: 'Tags',
|
||||
icon: 'turned_in',
|
||||
weight: 1100,
|
||||
permission: 'core.can_manage_tags'
|
||||
}
|
||||
]
|
||||
};
|
13
client/src/app/site/tags/tag.module.spec.ts
Normal file
13
client/src/app/site/tags/tag.module.spec.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { TagModule } from './tag.module';
|
||||
|
||||
describe('MotionsModule', () => {
|
||||
let tagModule: TagModule;
|
||||
|
||||
beforeEach(() => {
|
||||
tagModule = new TagModule();
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
expect(tagModule).toBeTruthy();
|
||||
});
|
||||
});
|
12
client/src/app/site/tags/tag.module.ts
Normal file
12
client/src/app/site/tags/tag.module.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { TagRoutingModule } from './tag-routing.module';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { TagListComponent } from './components/tag-list.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, TagRoutingModule, SharedModule],
|
||||
declarations: [TagListComponent]
|
||||
})
|
||||
export class TagModule {}
|
@ -26,6 +26,12 @@ module.exports = function(config) {
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false
|
||||
singleRun: false,
|
||||
proxies: {
|
||||
'/apps/': 'http://localhost:8000/apps/',
|
||||
'/media/': 'http://localhost:8000/media/',
|
||||
'/rest/': 'http://localhost:8000/rest/',
|
||||
'/ws/site/': 'ws://localhost:8000/ws/site'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -29,12 +29,12 @@
|
||||
|
||||
<table os-perms="core.can_manage_tags" class="table table-striped table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th ng-click="toggleSort('name')" class="sortable">
|
||||
<translate>Name</translate>
|
||||
<i class="pull-right fa" ng-show="sortColumn === 'name' && header.sortable != false"
|
||||
<tr>
|
||||
<th ng-click="toggleSort('name')" class="sortable">
|
||||
<translate>Name</translate>
|
||||
<i class="pull-right fa" ng-show="sortColumn === 'name' && header.sortable != false"
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
</i>
|
||||
<tbody>
|
||||
<tr ng-repeat="tag in tags | filter: filter.search |
|
||||
orderBy: sortColumn:reverse">
|
||||
|
Loading…
Reference in New Issue
Block a user