Skip to content

Commit

Permalink
Add 'vocabulary' method
Browse files Browse the repository at this point in the history
Add search form with portal_types vocabulary field on demo site
  • Loading branch information
tdesvenain committed Nov 9, 2017
1 parent 02bc6f4 commit 4adf0eb
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 11 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 1.4.0 (Unreleased)

## New features

- Add `vocabulary` method to retrieve zope vocabularies. [Thomas Desvenain]

# 1.3.1 (2017-11-08)

## Bug fixes
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/services.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ Methods:

`type(typeId)`: return the JSON schema of the specified resource type.

`vocabulary(vocabularyId)`: return the specified vocabulary object. Returns an observable.

API service
-----------

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export { Comments, Comment, CommentAdd } from './components/comments';
export { GlobalNavigation } from './components/global.navigation';
export { Navigation } from './components/navigation';
export { NavigationLevel } from './components/navigation.level';
export { Term, Vocabulary } from './vocabularies';
1 change: 0 additions & 1 deletion src/services/loading.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ export class LoadingInterceptor implements HttpInterceptor {
return next
.handle(req)
.do((event: HttpEvent<any>) => {
// debugger;
if (event instanceof HttpResponse) {
this.loading.finish(loadingId);
}
Expand Down
67 changes: 67 additions & 0 deletions src/services/resource.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AuthenticationService } from './authentication.service';
import { ResourceService } from './resource.service';
import { CacheService } from './cache.service';
import { LoadingService } from './loading.service';
import { Vocabulary } from '../vocabularies';

describe('ResourceService', () => {
beforeEach(() => {
Expand Down Expand Up @@ -383,4 +384,70 @@ describe('ResourceService', () => {

expect(title).toBe('A folder');
});

it('should get vocabulary', () => {
const service = TestBed.get(ResourceService);
const http = TestBed.get(HttpTestingController);
let vocabulary: Vocabulary<string> = new Vocabulary([]);
const response = {
'@id': 'http://fake/Plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes',
'terms': [
{
'@id': 'http://fake/Plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes/Collection',
'title': 'Collection',
'token': 'Collection'
},
{
'@id': 'http://fake/Plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes/Discussion Item',
'title': 'Comment',
'token': 'Discussion Item'
},
{
'@id': 'http://fake/Plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes/Event',
'title': 'Event',
'token': 'Event'
},
{
'@id': 'http://fake/Plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes/File',
'title': 'File',
'token': 'File'
},
{
'@id': 'http://fake/Plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes/Folder',
'title': 'Folder',
'token': 'Folder'
},
{
'@id': 'http://fake/Plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes/Image',
'title': 'Image',
'token': 'Image'
},
{
'@id': 'http://fake/Plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes/Link',
'title': 'Link',
'token': 'Link'
},
{
'@id': 'http://fake/Plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes/News Item',
'title': 'News Item',
'token': 'News Item'
},
{
'@id': 'http://fake/Plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes/Document',
'title': 'Page',
'token': 'Document'
}
]
};

service.vocabulary('plone.app.vocabularies.ReallyUserFriendlyTypes').subscribe((ruftVocabulary: Vocabulary<string>) => {
vocabulary = ruftVocabulary;
});

http.expectOne('http://fake/Plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes').flush(response);

expect(vocabulary.terms().length).toBe(9);
expect(vocabulary.byToken('Document').title).toBe('Page');
});

});
10 changes: 8 additions & 2 deletions src/services/resource.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { APIService } from './api.service';
import { ConfigurationService } from './configuration.service';
import { NavLink, SearchOptions, SearchResults } from '../interfaces';
import { CacheService } from './cache.service';
import { Vocabulary } from '../vocabularies';


interface NavigationItem {
Expand Down Expand Up @@ -56,7 +57,7 @@ export class ResourceService {
}

find(
query: any,
query: {[key: string]: any},
path: string = '/',
options: SearchOptions = {}
): Observable<SearchResults> {
Expand All @@ -65,7 +66,7 @@ export class ResourceService {
}
let params: string[] = [];
Object.keys(query).map(index => {
let criteria = query[index];
const criteria = query[index];
if (typeof criteria === 'boolean') {
params.push(index + '=' + (criteria ? '1' : '0'));
} else if (typeof criteria === 'string') {
Expand Down Expand Up @@ -162,6 +163,11 @@ export class ResourceService {
return this.cache.get('/@types/' + typeId);
}

vocabulary(vocabularyId: string): Observable<Vocabulary<string | number>> {
return this.cache.get('/@vocabularies/' + vocabularyId)
.map((jsonObject: any): Vocabulary<string | number> => new Vocabulary(jsonObject.terms));
}

/*
Make the observable emit resourceModified event when it emits
*/
Expand Down
46 changes: 46 additions & 0 deletions src/vocabularies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export interface Term<T extends number | string> {
'@id': string;
token: T;
title: string;
}

export class Vocabulary<T extends number | string> implements Iterable<Term<T>> {
protected _terms: Term<T>[];
protected _byToken: any = {}; // can't declare indexer with generics

constructor(terms: Term<T>[]) {
this._terms = terms;
for (const term of terms) {
this._byToken[term.token] = term;
}
}

public terms() {
return this._terms;
}

public byToken(token: T): Term<T> {
const term = this._byToken[token];
if (term === undefined) {
throw Error(`${token} token doesn't exist in vocabulary`);
} else {
return term;
}
}

[Symbol.iterator]() {
let counter = 0;
const terms = this._terms;

return <Iterator<Term<T>>>{

next(): IteratorResult<Term<T>> {
if (counter < terms.length) {
return { done: false, value: terms[counter++] };
} else {
return { done: true, value: undefined } as any as IteratorResult<Term<T>>;
}
}
};
}
}
7 changes: 4 additions & 3 deletions tests/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { PloneViews, Services } from '@plone/restapi-angular';

import { CustomViewView } from './custom';
import { AuthenticatedStatus, LoadingStatus } from '@plone/restapi-angular';
import { AuthenticatedStatus, LoadingStatus, Vocabulary, SearchView } from '@plone/restapi-angular';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
export class AppComponent implements OnInit {

loading = 'OK';
error = '';
Expand All @@ -33,6 +33,7 @@ export class AppComponent {
.subscribe((auth: AuthenticatedStatus) => {
this.logged = auth.state;
});

this.services.api.status
.subscribe((status: LoadingStatus) => {
this.loading = status.loading ? 'Loading...' : 'OK';
Expand Down
10 changes: 5 additions & 5 deletions tests/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { BrowserModule } from '@angular/platform-browser';

import { RESTAPIModule } from '@plone/restapi-angular';

import { AppComponent } from './app.component';
import { CustomViewView } from './custom';
import { CustomBreadcrumbs } from './custom';
import { CustomGlobalNavigation } from './custom';
import { environment } from '../environments/environment';
import { AppComponent } from './app.component';
import { Search } from './components/search';
import { CustomBreadcrumbs, CustomGlobalNavigation, CustomViewView } from './custom';

@NgModule({
declarations: [
AppComponent,
CustomViewView,
CustomBreadcrumbs,
CustomGlobalNavigation,
Search,
],
entryComponents: [
CustomViewView,
Expand Down
10 changes: 10 additions & 0 deletions tests/src/app/components/search.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<form #searchForm="ngForm" (ngSubmit)="search(searchForm)">
<select ngModel name="type">
<option value="">All types</option>
<option *ngFor="let typeTerm of typesVocabulary" [value]="typeTerm.token">
{{ typeTerm.title }}
</option>
</select>
<input ngModel type="text" name="text" />
<input type="submit" value="Search" />
</form>
31 changes: 31 additions & 0 deletions tests/src/app/components/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Component, OnInit } from '@angular/core';
import { Services, Vocabulary } from '@plone/restapi-angular';

@Component({
selector: 'app-search',
templateUrl: './search.html',
})
export class Search implements OnInit {

typesVocabulary: Vocabulary<string>;

constructor(protected services: Services) {
}

ngOnInit() {

this.services.resource.vocabulary('plone.app.vocabularies.ReallyUserFriendlyTypes')
.subscribe((typesVocabulary: Vocabulary<string>) => {
this.typesVocabulary = typesVocabulary;
});
}

search(form) {
let searchString = `/@@search?SearchableText=${form.value.text}`;
if (form.value.type) {
searchString += `&portal_type=${form.value.type}`;
}
this.services.traverser.traverse(searchString);
}

}

0 comments on commit 4adf0eb

Please sign in to comment.