# ngx-lib

ngx-lib is an Angular 5+ library providing foundational functionality to Cleaveland/Price applications.
Use of the library requires some simple configuration of the module(s) that you plan to consume in your application.

### _Please note that this library is not intended to be used for non-Cleaveland/Price applications._
The Angular services in this library make use of back-end web services that are proprietary and not part of this library.
While the code in this library is publicly available, it's unlikely to be highly useful to developers who are not part of Cleaveland/Price.  However, you can certainly use this as a reference to indicate how you might want to develop the public APIs of your back-end services.  You're welcome to use this library as you see fit, just know that a lot of the "heavy lifting" (interfacing with Active Directory, SharePoint, Exchange, etc.) is done by web services that are not available via this library.

## Installation

To install this library, run:

```bash
$ npm install @cleavelandprice/ngx-lib
```

Please note that some modules may have additional requirements.  Those requirements are documented in the module-specific sections of this document.

## Prerequisites

Ngx-lib requires the following packages that are not installed by default:

* @angular/cdk
* @angular/material
* hammerjs (_third-party, required by Angular Material_)
* angular2-jwt (_third-party, required for Authentication token management_)

These packages must be installed prior to using ngx-lib.

```bash
npm install @angular/cdk @angular/material
npm install hammerjs
npm install angular2-jwt
```

## Consuming ngx-lib

After installing ngx-lib, import the module(s) that you need in your `AppModule` file:

```typescript
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http'; // Required by ngx-lib modules that call web services
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

// Cleaveland/Price desired module(s)
import {
  AuthenticationModule,
  AuthenticationServiceConfig,
  DialogModule,
  EmailModule,
  EmailServiceConfig,
  EmployeeModule,
  SharePointModule,
  UploadModule
} from '@cleavelandprice/ngx-lib';

// Define the base URL for C/P API endpoints
// Definining this prefix here makes it easier to define the module-specific Urls
// i.e. The Email module will utilize a different endpoint than the SharePoint module
const webServices = 'http://SomeServer/api';

// Define a configuration for the Email module
const emailConfig = {
  emailApiUrl: `${webServices}/email`,
  server: 'EmailServerAddress',
  smtpPort: 25,
  sender: {
    displayName: 'IT Application Process',
    emailAddress: 'address@domain.com',
    userName: 'emailuser',
    password: 'emailpassword'
  }
};

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,

    // Import the desired module(s)
    // For the Email module, pass in the configuration that we defined above
    // At this point, all of the functionality exported by each module will be available throughout this app
    // This includes services, components, pipes, interfaces, classes, etc.
    DialogModule,
    SharePointModule.forRoot({ sharePointApiUrl: `${webServices}/sharepoint` }),
    AuthenticationModule.forRoot({ authenticationApiUrl: `${webServices}/authenticate` }),
    EmailModule.forRoot(emailConfig),
    EmployeeModule.forRoot({ employeeApiUrl: `${webServices}/users` }),

    // Configure UploadModule to upload to the filesystem
    UploadModule.forRoot({
      uploadApiUrl: `${webServices}/upload/filesystem`,

      // Note: uploadPath is optional for UploadModule
      // If not specified, files will be uploaded to the "Uploads" folder of the web API
      uploadPath: '\\\\server\\share'
    })
    // OR... Configure UploadModule to upload to the database
    // UploadModule.forRoot({ uploadApiUrl: `${webServices}/upload/database`})
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

Once modules are imported, you can use their components, directives, services, and pipes in your Angular application:

```xml
<!-- app.component.html example -->
<nav>
  <cp-login></cp-login>
</nav>
```
---
### Using AuthenticationModule
The Authentication module provides a component and a service that allows you to perform Active/Directory authentication.  You can tap into this functionality "manually" - meaning that you construct the interface to acquire user credentials and pass it into the service call, and you have complete control over the user interface before, during, and after authentication.

Or, you may choose to simply use the `cp-login` component, which performs all of the heavy lifting for you.  By simply including the `cp-login` tag in your HTML markup, the Authentication module provides its own login interface, built with Angular Material components, and displays the user's photo after authentication.  This method requires no manual coding or user interface construction.  It's well suited as a drop-in component for a toolbar.

Regardless of which of the two approaches you decide to take, the Authentication module will handle management of the authentication token, saving it in the browser's Local Storage and automatically retrieving it the next time the user visits the page - preventing them from having to re-authenticate each time.

The Authentication module also allows the developer to (optionally) specify an Active Directory group that a user must belong to in order to be considered an administrator of the application.  When the web service returns the authentication token, an Admin flag will be set to true or false depending on whether or not the user belongs to the group specified.  It is then up to the developer to customize the user's experience within the app based on this value.  In some cases, it may even be desireable for the app to restrict login to only members of the specified group.  In such a case, you would perform the authentication and implement some additional logic after authentication completes via the observable `subscribe()` method.  If the user isn't an Admin (because they don't belong to the group), you would call the `logout()` method of the AuthenticationService and display a message informing them that they can't login without the required group membership.

##### LoginComponent requirements
If you plan to use the `cp-login` tag in your HTML markup, please note that it requires [font-awesome](https://www.npmjs.com/package/font-awesome).  Your application will need to install the font-awesome package and reference the font-awesome style sheet.

Since the LoginComponent also displays a login form that utiliizes Angular Material components, you will need to install [@angular/material](https://www.npmjs.com/package/@angular/material) and reference an Angular Material theme stylesheet.  Some Material components also require [HammerJS](https://www.npmjs.com/package/hammerjs).  So you'll need to include that package as well and add an `import` statement for it to your main.ts file.  For more thorough up-to-date documentation on Angular Material, please visit [the Angular Material site](https://material.angular.io).

**Installing font-awesome**
```
npm install font-awesome
```

**Installing Angular Material** (four packages required)
```
npm install @angular/material @angular/cdk @angular/animations hammerjs
```

**styles.css**
```
/* font-awesome stylesheet */
@import "~font-awesome/css/font-awesome.min.css";

/* Angular Material 'Indigo-Pink' theme (or choose one that you like) */
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
```

**main.ts**
```typescript
import 'hammerjs';
```

#### Using the Authentication service (manual login/logout)
```typescript
import { AuthenticationService } from '@cleavelandprice/ngx-lib';

@Component({
  selector: 'app-authentication',
  template: `
    <input type="text" [(ngModel)]="username">
    <input type="text" [(ngModel)]="password">
    <img *ngIf="authenticationService.authenticated && authenticationService.authenticatedUser.photoUrl"
      [src]="authenticationService.authenticatedUser.photoUrl" />
    <button (click)="login()">Login</button>
    <button (click)="logout()">Logout</button>
    `
})
export class AuthenticationComponent implements OnInit {
  username: string;
  password: string;
  adminGroup = 'App Admins'; // Optional AD group specifying who is an admin of this app
  requiredGroup: boolean; // Optional login restriction

  // Inject the AuthenticationService into this component
  constructor(public authenticationService: AuthenticationService) {}

  ngOnInit(): void {
    // If we're requiring the user to be in a certain group to login, then check it after login
    // If not a valid member, then log them out
    this.authenticationService.authenticatedChanged
      .subscribe(() => {
        if (this.authenticationService.authenticated) {
          if (this.adminGroup && this.requiredGroup) {
            if (!this.authenticationService.authenticatedUser.admin) {
              this.logout();
              alert(`Sorry bro, you're not a member of '${this.adminGroup}'`);
              return;
            }
          }
          alert('Logged in');
        } else {
          alert('Logged out');
        }
      });
  }

  login() {
    if (this.username && this.password) {
      this.authenticationService.login(this.username, this.password, this.adminGroup);
    }
  }

  logout() {
    this.authenticationService.logout();
  }
}
```

##### Interfaces provided by AuthenticationModule
* AuthenticationToken
* AuthenticationServiceConfig
---
### Using DialogModule
The Dialog module provides a simple way to display 3 types of dialog boxes built with Angular Material components:

1. **Message Box** displays a message to the user.  
 The only button displayed is an OK button.
2. **Confirmation Box** displays a simple question for the user and returns a true/false value indicating which button they selected.  
 The buttons can be of the following types: `Ok/Cancel` (default option), `Yes/No`, `True/False`.  
 Regardless of which option is selected by the developer, it will always return a true/false value, with `Ok`, `Yes`, `True` = true and `Cancel`, `No`, `False` = false.
3. **Input Box** displays a dialog box with an input field that the user can type into.  
 The user can then click Ok, or Cancel.  
 The developer may provide a placeholder for the input field.

All three types of dialogs allow the developer to specify the Title and the Message to be displayed.  Additionally, the developer may optionally provide a message to be displayed via a checkbox.  If the `checkboxMessage` is provided, then the dialog box will include a checkbox at the bottom and the value (true/false) will be returned to the calling code.  This also applies to dialogs of type Message Box - which otherwise doesn't return a value.  Once the value is returned, it's up to the developer to decide what to do with the checkbox value.  Typically, this is used for options like *Don't show me this message in the future*, etc.

To prevent the user from closing the dialog without responding (i.e. clicking outside of the dialog), simply pass in the optional `disableClose` parameter provided by Angular Material (this is native functionality provided by Angular).

```typescript
import { MatDialog } from '@angular/material/dialog';
import {
  ConfirmationDialogComponent,
  ConfirmationDialogMode,
  InputDialogComponent,
  MessageDialogComponent
} from '@cleavelandprice/ngx-lib';
@Component({
  selector: 'app-dialogs',
  template: `
    <input type="text" [(ngModel)]="title">
    <input type="text" [(ngModel)]="message">
    <input type="text" [(ngModel)]="checkboxMessage">
    <input type="text" [(ngModel)]="placeholder">
  `
})
export class DialogsComponent {
  confirmationMode = ConfirmationDialogMode.OkCancel;
  title: string;
  message: string;
  checkboxMessage: string; // Optional
  placeholder: string; // For input box
  disableClose = false; // Optional Angular Material option to specify modal

  // MatDialog provided by Angular Material
  constructor(public dialog: MatDialog) { }

  messageBox(): void {
    this.dialog
      .open(MessageDialogComponent, {
        data: {
          title: this.title,
          message: this.message,
          checkboxMessage: this.checkboxMessage
        },
        disableClose: this.disableClose
      })
      .afterClosed()
      .subscribe(response => {
        if (this.checkboxMessage) {
          console.log(response);
        }
      });
  }

  confirmationBox(): void {
    this.dialog
      .open(ConfirmationDialogComponent, {
        data: {
          title: this.title,
          message: this.message,
          checkboxMessage: this.checkboxMessage,
          mode: this.confirmationMode
        },
        disableClose: this.disableClose
      })
      .afterClosed()
      .subscribe(response => console.log(response));
  }

  inputBox(): void {
    this.dialog
      .open(InputDialogComponent, {
        data: {
          title: this.title,
          message: this.message,
          checkboxMessage: this.checkboxMessage,
          placeholder: this.placeholder
        },
        disableClose: this.disableClose
      })
      .afterClosed()
      .subscribe(response => console.log(response));
  }
}
```

##### Enumerations provided by DialogModule
* ConfirmationDialogMode
---
### Using EmailModule
The Email module provides a simple mechanism for sending emails via the Cleaveland/Price web services.

```typescript
import { EmailService, Email } from '@cleavelandprice/ngx-lib';

@Component({
  selector: 'app-email',
  template: '<button (click)="sendMail()">Send Email</button>'
})
export class EmailComponent {
  // Inject the EmailService into this component
  constructor(private emailService: EmailService) { }

  sendEmail(): void {
    // Define an email object
    const email: Email = {
      subject: 'Test Email',
      recipients: {
        to: ['recipient@domain.com']
      },
      content: { html: '<html><body>hooray!</body></html>' }
    };

    // Send the email
    this.emailService.send(email).subscribe();
  }
}
```

##### Interfaces provided by EmailModule
* Email
* EmailServiceConfig
---
### Using EmployeesModule
The Employees module provides a service for retrieving a list of employees from Active Directory (or a single employee), and a wealth of information about employees.  Additionally, the module also provides some pre-built pipes for things like filtering,  sorting, and determining group membership.

```typescript
import { EmployeeService, Employee } from '@cleavelandprice/ngx-lib';
@Component({
  selector: 'app-employees',
  template: `
    <div *ngFor="let emp of employees">
      <div>{{ emp.displayName }}</div>
      <div>{{ emp.department }}</div>
    </div>`
})
export class EmployeesComponent implements OnInit {
  employees: Employee[];

  // Inject the EmployeeService into this component
  constructor(private employeeService: EmployeeService) { }

  ngOnInit(): void {
    this.employeeService.getEmployees(true)
      .subscribe(employees => this.employees = employees);
  }
}

```

#### Employee pipes
1. **departments** returns a list of unique departments.  
 `<div *ngFor="let dept of employees | departments">{{ dept}}</div>`

2. **employeeByDn** returns an employee matching an Active Directory distinguishedName value.  
 `{{ (emp.manager | employeeByDn).displayName }}`

3. **employeePhotoUrl** returns the Url of an employee's photo (from SharePoint), provided an array of SharePointEmployeePhoto (see SharePointModule section for information about retrieving employee photos).  
 `<div *ngFor="let emp of employees"><img [src]="emp | employeePhotoUrl:photos"></div>`

4. **employeesWithPropertyValue** returns a list of employees with a given Active Directory attribute value (exact match).  
 `<div *ngFor="let emp of employees | employeesWithPropertyValue:'department':'Information Technology'">{{ emp.displayName }}</div>`

5. **filterEmployees** returns a filtered list of employees based on a given search string (partial or exact).  The pipe will check for matches based on displayName, sAMAccountName, department, and title.  
 `<div *ngFor="let emp of employees | filterEmployees:'daniel">{{ emp.displayName }}</div>`

6. **isMemberOf** returns a true/false value indicating whether or not an employee is a member of the specified Active Directory group.  
 `<div *ngFor="let emp of employees" [hidden]="!(emp | isMemberOf:'Domain Admins')"></div>`

7. **sortEmployees** returns a list of employees sorted by the specified Active Directory attribute.  By default, employees are sorted by displayName.  If the value of the attribue is a number or a date, you should also provide a `dataType` parameter.  The default dataType is "string".  Additionally, you can provide a true/false value as a final parameter to specify whether or not you want the array to be sorted in reverse order.  
 `<div *ngFor="let emp of employees | sortEmployees:'lastName'"></div>`  
 `<div *ngFor="let emp of employees | sortEmployees:'lastName':'string':true"></div>`  
 `<div *ngFor="let emp of employees | sortEmployees:'employeeNumber':'number'"></div>`  
 `<div *ngFor="let emp of employees | sortEmployees:'hireDate':'date'"></div>`

##### Interfaces provided by EmployeeModule
* Employee
* EmployeeServiceConfig
---
### Using SharePointModule
The SharePointModule provides a service that can be used to retrieve data from SharePoint.  Built-in methods allow for quick retrieval of employee photos, people (employees with SharePoint accounts), and customer visits.  Photos and customer visits come directly from SharePoint lists.  Methods are provided to retrieve them because they are commonly needed in many applications.  However, you can retrieve data from any list on any SharePoint site by constructing a `SharePointList` request value and passing it to the `getListItems()` method.

```typescript
import { SharePointService,
  SharePointList,
  SharePointPerson,
  SharePointPicture,
  SharePointEmployeePicture,
  SharePointCustomerVisit,
  SharePointListItem
} from '@cleavelandprice/ngx-lib';

@Component({
  selector: 'app-sharepoint',
  template: `
<div *ngIf="documents">
  <h1>Documents</h1>
    <div *ngFor="let doc of documents">
      <div>{{ doc.ows_Title }}</div>
    </div>
</div>

<div *ngIf="visits">
  <h1>Customer Visits</h1>
  <div *ngFor="let visit of visits">
    <div>{{ visit.ows_Title }}</div>
    {{ visit.ows_EventDate | date:'shortDate' }}
  </div>
</div>

<div *ngIf="people">
  <h1>People on SharePoint</h1>
  <div *ngFor="let person of people">
    <div>{{ person.displayName }}</div>
    <div>{{ person.department }} </div>
    <div>{{ person.accountName }}</div>
  </div>
</div>

<div *ngIf="photos">
  <h1>Employee Photos from SharePoint - with hover tooltips</h1>
  <img *ngFor="let photo of photos" [src]="photo | sharePointPhotoUrl" alt="" [title]="photo.ows_Title">
</div>

  `
})
export class SharePointComponent implements OnInit {
  photos: SharePointEmployeePicture[];
  people: SharePointPerson[];
  visits: SharePointCustomerVisit[];
  documents: SharePointListItem[]; // retrieved via a custom request

  // Inject the SharePointService into this component
  constructor(private sharepointService: SharePointService) { }

  ngOnInit() {
    this.getCustomerVisits();
    this.getPeople();
    this.getPhotos();
    this.getDocuments();
  }

  private getCustomerVisits(): void {
    this.sharepointService.getCustomerVisits()
      .subscribe(data => this.visits = data);
  }

  private getPeople(): void {
    this.sharepointService.getPeople(true)
      .subscribe(data => this.people = data);
  }

  private getPhotos(): void {
    this.sharepointService.getPhotos(true)
      .subscribe(data => this.photos = data);
  }

  private getDocuments(): void {
    // Define a SharePointList to be used in the request
    // NOTE: the 'site' property is only needed if the library exists in a subsite (different Url than the one used to configure the service)
    const list: SharePointList = {
      list: 'Documents',
      view: 'All Documents',
      site: 'http://MySharePointSiteUrl'
    };

    this.sharepointService.getListItems(list)
      .subscribe(data => this.documents = data);
  }
}
```

##### Interfaces provided by SharePointModule
* SharePointCalendarEvent
* SharePointCustomerVisit
* SharePointEmployeePicture
* SharePointListItem
* SharePointList
* SharePointPerson
* SharePointPicture
* SharePointServiceConfig
---
### Using UploadModule
The UploadModule provides a component that can be used to upload one or more files simultaneously.  Upload status is displayed via an Angular Material progress bar.  By default, the UploadService (automatically called by the component) will attempt to upload files to the "Uploads" folder of the API (if configured in filesystem mode).  Alternatively, an "uploadPath" configuration parameter can be provided to specify a more appropriate application-specific upload location.  If upload failures occur, the most likely cause is a permissions problem (the API may not have write permissions to the destination).

If UploadModule is configured to store files in the database instead of the filesystem, the API will store them in the ITDev database in the FileUpload.Uploads table.  In database mode, UploadModule will not allow a file to be overwritten (database enforces uniqueness of the file name).

When the user attempts to add a file to the upload component (prior to sending it to the server), the component will perform a call to the server to check for the existence of a file of the same name.  If it already exists, the file will be rejected by the component and a message will be displayed.  Files that do not already exist will be added to the component, ready to be sent to the server.

```typescript
@Component({
  selector: 'app-upload',
  template: '<cp-upload></cp-upload>'
})
export class MyUploadComponent {
}
```

##### Interfaces provided by UploadModule
* UploadServiceConfig
---
## License

MIT © [Dan Rullo](mailto:drullo@cleavelandprice.com)

This project was built using the [ng-packagr](https://github.com/dherges/ng-packagr) project.