Skip to content

Commit 80702e5

Browse files
committed
Add "reaching out" tutorial
1 parent 845a190 commit 80702e5

File tree

12 files changed

+247
-71
lines changed

12 files changed

+247
-71
lines changed

cloudapp/src/app/app-routing.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import { NewrouteComponent } from './newroute/newroute.component';
55
import { ThemingComponent } from './theming/theming.component';
66
import { SettingsComponent } from './settings/settings.component';
77
import { ParallelComponent } from './parallel/parallel.component';
8+
import { ExternalComponent } from './external/external.component';
89

910
const routes: Routes = [
1011
{ path: '', component: MainComponent },
1112
{ path: 'newroute', component: NewrouteComponent },
1213
{ path: 'theming', component: ThemingComponent },
1314
{ path: 'settings', component: SettingsComponent },
1415
{ path: 'parallel', component: ParallelComponent },
16+
{ path: 'external', component: ExternalComponent },
1517
];
1618

1719
@NgModule({

cloudapp/src/app/app.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { NewrouteComponent } from './newroute/newroute.component';
1414
import { ThemingComponent } from './theming/theming.component';
1515
import { SettingsComponent } from './settings/settings.component';
1616
import { ParallelComponent } from './parallel/parallel.component';
17+
import { ExternalComponent } from './external/external.component';
1718

1819
export function getToastrModule() {
1920
return ToastrModule.forRoot({
@@ -30,7 +31,8 @@ export function getToastrModule() {
3031
NewrouteComponent,
3132
ThemingComponent,
3233
SettingsComponent,
33-
ParallelComponent
34+
ParallelComponent,
35+
ExternalComponent
3436
],
3537
imports: [
3638
MaterialModule,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<form>
2+
<section class="settings-section">
3+
<mat-form-field>
4+
<mat-label>Identifier Type</mat-label>
5+
<mat-select value="isbn" #identifierType>
6+
<mat-option value="oclc">OCLC Number</mat-option>
7+
<mat-option value="lccn">LCCN</mat-option>
8+
<mat-option value="issn">ISSN</mat-option>
9+
<mat-option value="isbn">ISBN</mat-option>
10+
<mat-option value="htid">HathiTrust Volume ID</mat-option>
11+
<mat-option value="recordnumber">HathiTrust record number</mat-option>
12+
</mat-select>
13+
</mat-form-field>
14+
</section>
15+
<section class="settings-section">
16+
<mat-form-field>
17+
<mat-label>Identifier</mat-label>
18+
<input matInput #identifier value="0394530934">
19+
</mat-form-field>
20+
</section>
21+
</form>
22+
23+
<div class="commands-container">
24+
<button mat-stroked-button type="button" color="primary" (click)="search(identifierType.value, identifier.value)">Search</button>
25+
<mat-spinner diameter="30" class="spinner" *ngIf="running"></mat-spinner>
26+
</div>
27+
28+
<mat-card *ngIf="record">
29+
<mat-card-header>
30+
<mat-card-title>HathiTrust Record</mat-card-title>
31+
</mat-card-header>
32+
<mat-card-content>
33+
<img *ngIf="record.items.length>0" src="https://babel.hathitrust.org/cgi/imgsrv/cover?id={{record.items[0].htid}}" class="pull-right">
34+
<ul>
35+
<li><strong>Record ID: </strong> <a href="{{record.recordURL}}" target="_blank">{{ record.id }}</a></li>
36+
<li><strong>Title: </strong>{{ record.titles[0] }}</li>
37+
<li><strong>Held By:</strong>
38+
<ul>
39+
<li *ngFor="let item of record.items"><a href="{{item.itemURL}}" target="_blank">{{item.orig}}</a></li>
40+
</ul>
41+
</li>
42+
</ul>
43+
</mat-card-content>
44+
</mat-card>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.commands-container {
2+
margin: 20px 0;
3+
width: fit-content;
4+
}
5+
6+
.spinner {
7+
float: right;
8+
}
9+
10+
.commands-container button {
11+
margin-right: 10px;
12+
}
13+
14+
.settings-section {
15+
display: flex;
16+
align-content: center;
17+
align-items: center;
18+
margin: 10px 0;
19+
}
20+
21+
.settings-control {
22+
margin: 0 10px;
23+
}
24+
25+
.pull-right {
26+
margin-right: 15px;
27+
float: right;
28+
}
29+
30+
li {
31+
margin-bottom: 5px;
32+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import { AppService } from '../app.service';
3+
import { HttpClient } from '@angular/common/http';
4+
import { map, finalize } from 'rxjs/operators';
5+
import { ToastrService } from 'ngx-toastr';
6+
7+
@Component({
8+
selector: 'app-external',
9+
templateUrl: './external.component.html',
10+
styleUrls: ['./external.component.scss']
11+
})
12+
export class ExternalComponent implements OnInit {
13+
running = false;
14+
record: any;
15+
16+
constructor(
17+
private appService: AppService,
18+
private http: HttpClient,
19+
private toastr: ToastrService
20+
) { }
21+
22+
ngOnInit() {
23+
this.appService.setTitle('Reaching out');
24+
}
25+
26+
search(identifierType: string, identifier: string) {
27+
this.running = true;
28+
this.record = null;
29+
this.http.get<any>(`https://catalog.hathitrust.org/api/volumes/brief/${identifierType}/${identifier}.json`)
30+
.pipe(
31+
map(res => {
32+
if (Object.keys(res.records).length == 0) {
33+
throw new Error('Record not found');
34+
} else {
35+
return Object.assign({items: res.items},
36+
{ id: Object.keys(res.records)[0], ...Object.values(res.records)[0] })
37+
}
38+
}),
39+
finalize(() => this.running = false)
40+
)
41+
.subscribe( {
42+
next: response=>this.record = response,
43+
error: (e) => this.toastr.error(e.message)
44+
});
45+
}
46+
}

cloudapp/src/app/main/main.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ <h1>
88
<li><a [routerLink]="['theming']">Using Material components & theming</a></li>
99
<li><a [routerLink]="['settings']">Using the Settings Service</a></li>
1010
<li><a [routerLink]="['parallel']">Making parallel API requests</a></li>
11+
<li><a [routerLink]="['external']">Reaching outside your app</a></li>
1112
</ul>
1213
</section>

cloudapp/src/app/parallel/parallel.component.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Component, OnInit } from '@angular/core';
2-
import { CloudAppRestService } from '@exlibris/exl-cloudapp-angular-lib';
3-
import { mergeMap, map } from 'rxjs/operators';
4-
import { from } from 'rxjs';
2+
import { CloudAppRestService, RestErrorResponse } from '@exlibris/exl-cloudapp-angular-lib';
3+
import { mergeMap, map, catchError } from 'rxjs/operators';
4+
import { from, of } from 'rxjs';
55
import { AppService } from '../app.service';
66

77
const CONCURRENT_REQUESTS = 5;
@@ -30,13 +30,46 @@ export class ParallelComponent implements OnInit {
3030

3131
loadUsers(users: any[]) {
3232
from(users).pipe(
33-
mergeMap(user => this.restService.call(`/users/${user.primary_id}?expand=fees`), CONCURRENT_REQUESTS),
33+
mergeMap(user => this.restService.call(`/users/${user.primary_id}?expand=fees`),
34+
CONCURRENT_REQUESTS),
3435
map(user=>({name: user.full_name, fees: user.fees}))
3536
)
3637
.subscribe(s=>this.users.push(s));
3738
}
3839

40+
loadUsersWithErrors(users: any[]) {
41+
from(this.addErrors(users)).pipe(
42+
mergeMap(user => this.getUser(user),
43+
CONCURRENT_REQUESTS)
44+
)
45+
.subscribe(s=> {
46+
if (isRestErrorResponse(s)) {
47+
console.log('Error retrieving user:', s.message);
48+
} else {
49+
this.users.push(s)
50+
}
51+
});
52+
}
53+
3954
getUsers() {
4055
return this.restService.call('/users?limit=50');
4156
}
42-
}
57+
58+
getUser(user) {
59+
return this.restService.call(`/users/${user.primary_id}?expand=fees`)
60+
.pipe(
61+
map(user=>({name: user.full_name, fees: user.fees})),
62+
catchError(e=>of(e))
63+
)
64+
}
65+
66+
addErrors(users: any[]) {
67+
for (let i=0; i<Math.floor(users.length*.25); i++) {
68+
users.splice(getRandomInt(users.length-1), 0, { primary_id: getRandomInt(1000000) });
69+
};
70+
return users;
71+
}
72+
}
73+
74+
const getRandomInt = (max: number) => Math.floor(Math.random() * Math.floor(max));
75+
const isRestErrorResponse = (object: any): object is RestErrorResponse => 'error' in object;

cloudapp/src/app/settings/settings.component.html

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
<mat-checkbox labelPosition="before" formControlName="showValue">Show Value</mat-checkbox>
44
</section>
55
<section class="settings-section">
6-
<label class="example-margin">Align:</label>
7-
<mat-radio-group formControlName="pageSize">
8-
<mat-radio-button class="settings-control" [value]="5">5</mat-radio-button>
9-
<mat-radio-button class="settings-control" [value]="10">10</mat-radio-button>
10-
<mat-radio-button class="settings-control" [value]="20">20</mat-radio-button>
11-
</mat-radio-group>
12-
</section>
6+
<label class="example-margin">Align:</label>
7+
<mat-radio-group formControlName="pageSize">
8+
<mat-radio-button class="settings-control" [value]="5">5</mat-radio-button>
9+
<mat-radio-button class="settings-control" [value]="10">10</mat-radio-button>
10+
<mat-radio-button class="settings-control" [value]="20">20</mat-radio-button>
11+
</mat-radio-group>
12+
</section>
1313
</form>
1414

1515
<div class="commands-container">

manifest.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
22
"id": "cloud-app-tutorials",
3-
"title": "CloudApp Tutorials",
4-
"subtitle": "Accompanying code for the CloudApp tutorials in the Developer Network",
5-
"description": "The Ex Libris Developer Network includes a number of detailed tutorials which demonstrate various capabilities of CloudApps. For more information, see https://developers.exlibrisgroup.com/cloudapps/tutorials/.",
3+
"title": "Cloud App Tutorials",
4+
"subtitle": "Accompanying code for the Cloud App tutorials in the Developer Network",
5+
"description": "The Ex Libris Developer Network includes a number of detailed tutorials which demonstrate various capabilities of Cloud Apps. For more information, see https://developers.exlibrisgroup.com/cloudapps/tutorials/.",
66
"author": "Josh Weisman",
77
"contentSecurity": {
8-
"sandbox": {
9-
"popups": true
10-
}
8+
"connectSrc": [
9+
"https://catalog.hathitrust.org/"
10+
]
1111
},
1212
"pages": {
1313
"settings": "/#/settings",

manifest.schema.json

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,26 @@
182182
"properties": {
183183
"connectSrc": {
184184
"$id": "#/properties/contentSecurity/connectSrc",
185-
"title": "CloudApp External URLs",
186-
"description": "External URLs used in the CloudApp",
185+
"title": "External URLs",
186+
"description": "External URLs that the app can connect to",
187187
"type": "array",
188188
"items": {
189189
"type": "string"
190190
},
191191
"minItems": 0,
192192
"uniqueItems": true
193193
},
194+
"frameSrc" : {
195+
"$id": "#/properties/contentSecurity/frameSrc",
196+
"title": "External Frames",
197+
"description": "External URLs that app uses frames from",
198+
"type": "array",
199+
"items": {
200+
"type": "string"
201+
},
202+
"minItems": 0,
203+
"uniqueItems": true
204+
},
194205
"sandbox": {
195206
"$id": "#/properties/contentSecurity/sandbox",
196207
"title": "CloudApp sandboxing options",
@@ -216,4 +227,4 @@
216227
}
217228
}
218229
}
219-
}
230+
}

0 commit comments

Comments
 (0)