diff --git a/cloudapp/jsconfig.json b/cloudapp/jsconfig.json
new file mode 100644
index 0000000..ea58a4b
--- /dev/null
+++ b/cloudapp/jsconfig.json
@@ -0,0 +1,6 @@
+{
+ "compilerOptions": {
+ "experimentalDecorators": true,
+ "target": "es5"
+ }
+}
diff --git a/cloudapp/src/app/app-routing.module.ts b/cloudapp/src/app/app-routing.module.ts
new file mode 100644
index 0000000..a7ac35d
--- /dev/null
+++ b/cloudapp/src/app/app-routing.module.ts
@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+import { MainComponent } from './main/main.component';
+
+const routes: Routes = [
+ { path: '', component: MainComponent },
+];
+
+@NgModule({
+ imports: [RouterModule.forRoot(routes, { useHash: true })],
+ exports: [RouterModule]
+})
+export class AppRoutingModule { }
diff --git a/cloudapp/src/app/app.component.ts b/cloudapp/src/app/app.component.ts
new file mode 100644
index 0000000..7af5162
--- /dev/null
+++ b/cloudapp/src/app/app.component.ts
@@ -0,0 +1,12 @@
+import { Component } from '@angular/core';
+import { AppService } from './app.service';
+
+@Component({
+ selector: 'app-root',
+ template: ''
+})
+export class AppComponent {
+
+ constructor(private appService: AppService) { }
+
+}
diff --git a/cloudapp/src/app/app.module.ts b/cloudapp/src/app/app.module.ts
new file mode 100644
index 0000000..3b74e27
--- /dev/null
+++ b/cloudapp/src/app/app.module.ts
@@ -0,0 +1,31 @@
+import { NgModule } from '@angular/core';
+import { HttpClientModule } from '@angular/common/http';
+import { BrowserModule } from '@angular/platform-browser';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
+import { MaterialModule, getTranslateModule, AlertModule } from '@exlibris/exl-cloudapp-angular-lib';
+
+import { AppComponent } from './app.component';
+import { AppRoutingModule } from './app-routing.module';
+import { MainComponent } from './main/main.component';
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ MainComponent
+ ],
+ imports: [
+ MaterialModule,
+ BrowserModule,
+ BrowserAnimationsModule,
+ AppRoutingModule,
+ HttpClientModule,
+ AlertModule,
+ getTranslateModule(),
+ ],
+ providers: [
+ { provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'standard' } },
+ ],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git a/cloudapp/src/app/app.service.ts b/cloudapp/src/app/app.service.ts
new file mode 100644
index 0000000..48b7707
--- /dev/null
+++ b/cloudapp/src/app/app.service.ts
@@ -0,0 +1,12 @@
+
+import { Injectable } from '@angular/core';
+import { InitService } from '@exlibris/exl-cloudapp-angular-lib';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AppService {
+
+ constructor(private initService: InitService) {}
+
+}
\ No newline at end of file
diff --git a/cloudapp/src/app/main/main.component.html b/cloudapp/src/app/main/main.component.html
new file mode 100644
index 0000000..20b904e
--- /dev/null
+++ b/cloudapp/src/app/main/main.component.html
@@ -0,0 +1,42 @@
+
+
+ Welcome!
+
+ Use this sample app to get you started. The app includes the following:
+
+ - Listener for the
onPageLoad
event
+ - Performs an API call using the
restService
service
+ - Updates the object using the
restService
service
+ - Refreshes the page in Alma
+
+ Use these building blocks to be on your way to developing your own Cloud App.
+
+
+
+
+
+ Page Entity
+
+
+ {{ pageEntities | json }}
+
+
+
+
+
+
+
+ API Result
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cloudapp/src/app/main/main.component.scss b/cloudapp/src/app/main/main.component.scss
new file mode 100644
index 0000000..3a3fb20
--- /dev/null
+++ b/cloudapp/src/app/main/main.component.scss
@@ -0,0 +1,31 @@
+section>h1 {
+ padding: 5px 10px;
+ border-radius: 4px;
+ margin-top: 10px;
+}
+
+textarea {
+ padding: 2px;
+ border-width: 1px;
+ $width: calc(100% - 6px);
+ width: $width;
+ max-width: $width;
+ height: 150px;
+ font-family: monospace;
+ display: block;
+}
+
+textarea,
+section {
+ margin-bottom: 10px;
+}
+
+pre {
+ overflow: auto;
+ max-height: 200px;
+}
+
+pre,
+textarea {
+ font-size: 0.9em;
+}
\ No newline at end of file
diff --git a/cloudapp/src/app/main/main.component.theme.scss b/cloudapp/src/app/main/main.component.theme.scss
new file mode 100644
index 0000000..6ea1cc9
--- /dev/null
+++ b/cloudapp/src/app/main/main.component.theme.scss
@@ -0,0 +1,6 @@
+@mixin main-component-theme($theme, $typgraphy) {
+ section>h1 {
+ background-color: mat-color(map-get($theme, primary));
+ color: mat-color(map-get($theme, primary), default-contrast);
+ }
+}
\ No newline at end of file
diff --git a/cloudapp/src/app/main/main.component.ts b/cloudapp/src/app/main/main.component.ts
new file mode 100644
index 0000000..5384310
--- /dev/null
+++ b/cloudapp/src/app/main/main.component.ts
@@ -0,0 +1,103 @@
+import { Subscription } from 'rxjs';
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import {
+ CloudAppRestService, CloudAppEventsService, Request, HttpMethod,
+ Entity, PageInfo, RestErrorResponse, AlertService
+} from '@exlibris/exl-cloudapp-angular-lib';
+
+@Component({
+ selector: 'app-main',
+ templateUrl: './main.component.html',
+ styleUrls: ['./main.component.scss']
+})
+export class MainComponent implements OnInit, OnDestroy {
+
+ private pageLoad$: Subscription;
+ pageEntities: Entity[];
+ private _apiResult: any;
+
+ hasApiResult: boolean = false;
+ loading = false;
+
+ constructor(private restService: CloudAppRestService,
+ private eventsService: CloudAppEventsService,
+ private alert: AlertService ) { }
+
+ ngOnInit() {
+ this.pageLoad$ = this.eventsService.onPageLoad(this.onPageLoad);
+ }
+
+ ngOnDestroy(): void {
+ this.pageLoad$.unsubscribe();
+ }
+
+ get apiResult() {
+ return this._apiResult;
+ }
+
+ set apiResult(result: any) {
+ this._apiResult = result;
+ this.hasApiResult = result && Object.keys(result).length > 0;
+ }
+
+ onPageLoad = (pageInfo: PageInfo) => {
+ this.pageEntities = pageInfo.entities;
+ if ((pageInfo.entities || []).length == 1) {
+ const entity = pageInfo.entities[0];
+ this.restService.call(entity.link).subscribe(result => this.apiResult = result);
+ } else {
+ this.apiResult = {};
+ }
+ }
+
+ update(value: any) {
+ this.loading = true;
+ let requestBody = this.tryParseJson(value);
+ if (!requestBody) {
+ this.loading = false;
+ return this.alert.error('Failed to parse json');
+ }
+ this.sendUpdateRequest(requestBody);
+ }
+
+ refreshPage = () => {
+ this.loading = true;
+ this.eventsService.refreshPage().subscribe({
+ next: () => this.alert.success('Success!'),
+ error: e => {
+ console.error(e);
+ this.alert.error('Failed to refresh page');
+ },
+ complete: () => this.loading = false
+ });
+ }
+
+ private sendUpdateRequest(requestBody: any) {
+ let request: Request = {
+ url: this.pageEntities[0].link,
+ method: HttpMethod.PUT,
+ requestBody
+ };
+ this.restService.call(request).subscribe({
+ next: result => {
+ this.apiResult = result;
+ this.refreshPage();
+ },
+ error: (e: RestErrorResponse) => {
+ this.alert.error('Failed to update data');
+ console.error(e);
+ this.loading = false;
+ }
+ });
+ }
+
+ private tryParseJson(value: any) {
+ try {
+ return JSON.parse(value);
+ } catch (e) {
+ console.error(e);
+ }
+ return undefined;
+ }
+
+}
diff --git a/cloudapp/src/assets/manifest.json b/cloudapp/src/assets/manifest.json
new file mode 100644
index 0000000..6a14022
--- /dev/null
+++ b/cloudapp/src/assets/manifest.json
@@ -0,0 +1,5 @@
+{
+ "id": "my-alma-cloud-app",
+ "title": "My Alma Cloud App",
+ "description": "My Alma Cloud App"
+}
\ No newline at end of file
diff --git a/cloudapp/src/main.scss b/cloudapp/src/main.scss
new file mode 100644
index 0000000..cf89247
--- /dev/null
+++ b/cloudapp/src/main.scss
@@ -0,0 +1,11 @@
+
+@import './app/main/main.component.theme';
+
+@mixin themed-styles($theme, $typography) {
+ /* Include themed component mixins or theme dependent styles here */
+ @include main-component-theme($theme, $typography);
+}
+
+@mixin global-styles {
+ /* Add global styles here */
+}