# turas-design-system npm package

## Introduction
 
The npm package contains CSS and script intended for use by a Turas application, 
and designed to work with markup emitted by Tag Helpers in the `NES.Turas.DesignSystem NuGet package`.

Azure DevOps pipelines are set up to publish `turas-design-system` to the public `npmjs.com` registry, scoped to the `nestechnology` npm organisation.

Packages will be available here: https://www.npmjs.com/package/@nestechnology/turas-design-system.

## Dependencies

If you inspect the `package.json` file, the `dependencies` entry lists the other npm packages that are required 
by the Turas Design System to be present, such as `bootstrap`, `jquery` and `datatables.net`.

You must ensure that files from Turas Design System and its dependencies are located in the relevant locations
within `wwwroot`.

If you have opted to call `AddTurasDesignSystem` in your app startup code, CSS and script references will be injected
into the page automatically. The files it references must be in your project in those locations.

Otherwise, you can add your own CSS and script references and have more control over where the dependencies are located.

### Font Awesome

Each Turas application should have its own Font Awesome Kit ID. The UX team can assist in obtaining one if you do not have one already.

If you have opted to call `AddTurasDesignSystem` in your app startup code and supplied a Font Awesome Kit ID, a reference will be injected into the page automatically
to fetch the Font Awesome Kit from `https://kit.fontawesome.com/{KitID}.js`, with a fallback URL of `wwwroot/lib/font-awesome-kit/font-awesome-kit.js`.

Once you have a Kit ID, you should ensure that you have manually downloaded the Kit from the relevant URL and placed it in the above path in `wwwroot`.

Otherwise, your app will not work correctly offline as it will not be able to reach the Font Awesome URL and have no local fallback file.

## Importing the package

As a developer of a Turas application, you have different options for importing the package and its dependencies.

### LibMan

If you are using Visual Studio, the easiest way to import client-side dependencies is to use 
Microsoft Library Manager, aka LibMan. This can be accessed by right-clicking `wwwroot` and selecting
`Add > Client-side Library`.

The dialog allows you to select the desired version of an npm package, and optionally pick specific files in the
package, to be downloaded to the specified place in your project (usually under `wwwroot\lib\<package_name>`.

The dialog stores the package information in the `libman.json` file. You can edit this file by hand, and Visual Studio 
will automatically update the files in your project.

This approach can be used to obtain the `turas-design-system` npm package, but it does not automatically fetch
the dependencies. So you must also ensure that the dependencies from the `package.json` within `turas-design-system`
are added to `libman.json`, such as `bootstrap` and `jquery`, taking care to reference the same versions as in `package.json`.

**NOTE** LibMan offers several providers to choose from, the recommended provider is **jsDelivr**.

### npm

Instead of using LibMan, you can use npm to fetch the `turas-design-system` npm package and its dependencies.

The upside is that, unlike LibMan, npm will automatically fetch the dependencies such as `bootstrap`.

The downsides are that you must have node and npm installed on your machine, and the packages are downloaded
to the `node_modules` folder rather than `wwwroot`, so you must take extra steps to copy the files into `wwwroot`
for use in your web app. This may involve setting up Gulp tasks in a `gulpfile.json`.

### Other

There are other npm package managers available, such as yarn and pnpm.

## Initialisation

Included in the `turas-design-system.init.js` file is a document ready handler which calls `TurasDesignSystem.Initialise(document)`.

If your application uses the `NES.Turas.DesignSystem NuGet package` and calls `AddTurasDesignSystem` then by default it injects 
a reference to that file into the `body` element.

If you have disabled auto-inject or auto-initialise in the call to `AddTurasDesignSystem` and you do not use `<turas-scripts initialise/>`,
or if your application does not call that method or use the package, then you should call `TurasDesignSystem.Initialise(document)` manually.

If your application performs partial refreshes by injecting HTML into the page (e.g. fetched via Ajax) then you should call
`TurasDesignSystem.Initialise` passing the root element of the refreshed content.

If you have code you want to be executed once the Turas Design System has finished initialising, you can call e.g.
`TurasDesignSystem.PostInitialise(document, customPostInitFunction);`. When the `customPostInitFunction` is invoked, it is passed
the jQuery-wrapped container as the only argument (so in this case it will be passed `$(document)`).

The `customPostInitFunction` callback can be an asynchronous function; it is `await`ed when invoked by `TurasDesignSystem.Initialise`.

If you have multiple post-initialise functions to register, you can call `PostInitialise` multiple times. The functions
will be invoked in the order they were registered.

## Using the Turas Design System components

### FileUploadComponent

A sample file upload component using the tag helper provided by the `NES.Turas.DesignSystem NuGet package`
could look like this, configured to upload a single file immediately with Ajax:

```html
<turas-file-upload file-upload-component-id="FileUploadComponent1"
                   asp-for="ImmediateUploadedFile"
                   filename="UploadedFilename"
                   file-type="UploadedFileType"
                   file-size="UploadedFileSize"
                   download-url="UploadedFileDownloadUrl"
                   view-url="UploadedFileViewUrl"
                   accept="@Model.FileAccept"
                   max-file-size-mb="20"
                   immediate-upload-url="/forms/UploadSingleFile">
  <turas-file-action state="NES.Turas.DesignSystem.Enums.FileUploadState.Complete">
    <button type="button" custom-file-delete><i class="fa-regular fa-trash-can"></i>Delete</button>
  </turas-file-action>
</turas-file-upload>
```

To obtain a reference to the `FileUploadComponent` API in your script:

```javascript
var fileUploadComponent = new TurasDesignSystem.FileUploadComponent($("#FileUploadComponent1"));
```

The supplied element must be a `.fileUploadComponent` (or a descendant of one) that was generated by the Turas Design System.<br/>
If a descendant element is supplied, the closest `.fileUploadComponent` is located and initialised.<br/>
If the constructor was previously called for that `.fileUploadComponent`, the same reference is returned.

The API exposes functions to let you attach event handlers or a custom upload implementation:
- `OnUploadReady`
- `OnUploadComplete`
- `OnError`
- `UploadFile`

The `SetDefaults` API lets you set global defaults that apply to all `FileUploadComponents` that are subsequently created:

```javascript
// Register a default file upload handler, replacing the built-in default which is to use an XMLHttpRequest. You may decide to use fetch or jQuery Ajax instead for example.
// Register a default error handler for file uploads, replacing the built-in default which is to display a bootstrap modal.
TurasDesignSystem.SetDefaults({
  FileUploadHandler: async function (file, url, method, $uploadContainer) {
    console.log("custom upload handler fills the progress bar immediately and returns fake info");
    this.SetUploadProgress($uploadContainer, 123456, 123456);
    var fakeResponse = { filename: "foo.pdf", fileType: "application/pdf", fileSize: 123456, downloadUrl: "/foo/download", viewUrl: "/foo/view" };
    return { response: JSON.stringify(fakeResponse), status: 200, cancelled: false };
  },
  FileUploadError: (event, eventData) => alert(eventData.message)
});
```

If you are calling `SetDefaults`, you should do so before any calls to `new TurasDesignSystem.FileUploadComponent(...)`, 
as the settings will not apply to any components created earlier.

### DataGridComponent

A sample data grid component using the tag helper provided by the `NES.Turas.DesignSystem NuGet package` could look like this:

```html
<turas-data-grid grid-id="PersonGrid" asp-for="PersonGrid" row-id="PersonGrid.Row.RowId" camel-case>
  <turas-data-grid-column asp-for="PersonGrid.Row.Name" span="4" sortable />
  <turas-data-grid-column asp-for="PersonGrid.Row.DateOfBirth" span="2" sortable />
  <turas-data-grid-column asp-for="PersonGrid.Row.Job" span="4" sortable />
</turas-data-grid>
```

To obtain a reference to the `DataGridComponent` API in your script:

```javascript
var personGridComponent = new TurasDesignSystem.DataGridComponent($("#PersonGrid"));
```

The supplied element must be a `.dataGridComponent` (or a descendant of one) that was generated by the Turas Design System.<br/>
If a descendant element is supplied, the closest `.dataGridComponent` is located and initialised.<br/>
If the constructor was previously called for that `.dataGridComponent`, the same reference is returned.

If you wish to customise a particular `DataGridComponent`, you may pass an options object when the constructor is first invoked.

```javascript
var personGridOptions = {
  BuildGridDataFetchOption: () => customGridDataFetchOptions,
  RenderCell: customRenderCellFunction
};

var personGridComponent = new TurasDesignSystem.DataGridComponent($("#PersonGrid"), personGridOptions);
```

A custom error handler can be attached:

```javascript
const replacesDefaultHandler = true;
personGridComponent.OnError(function(event, eventData) { 
  this.$component.find(".dataTables_processing").hide(); 
  alert(eventData.message); 
}, replacesDefaultHandler);
```

The `DataGridComponent` relies on the [datatables.net](https://www.npmjs.com/package/datatables.net) npm package. To obtain a reference to the underlying `datatables` API:
```javascript
var dataTableApi = personGridComponent.dataTable;
```

The `SetDefaults` API lets you set global defaults that apply to all `DataGridComponents` that are subsequently created:

```javascript
// Register default grid data fetch options
// Register a default RenderCell implementation
// Register a default error handler, replacing the built-in default which is to display a Bootstrap modal.
TurasDesignSystem.SetDefaults({
  GridDataFetch: () => customGridDataFetchOptions,
  GridRenderCell: customRenderCellFunction,
  GridError: function(event, eventData) { this.$component.find(".dataTables_processing").hide(); alert(eventData.message); },
});
```

If you are calling `SetDefaults`, you should do so before any calls to `new TurasDesignSystem.DataGridComponent(...)`, 
as the settings will not apply to any components created earlier.

## File download modals

When the Turas Design System has been initialised, a spinner modal is available for display when a file download is in progress.

The modal is wired up to be displayed automatically when an anchor tag with both `download` and `turas-file` attributes is clicked:
```html
<a href="/download-file" download turas-file>Download the file</a>
```

With those attributes present, the Turas Design System will:
- Automatically display the spinner modal.
- Set a `turasFileDownload` cookie with a 10 minute expiry.
- Start a timer to poll every second to check that the cookie still exists.
- If the cookie does not exist, or if a `turasFileDownloadError` cookie exists, hide the spinner modal.
- If a `turasFileDownloadError` cookie exists, show a file download error modal.

**Note:** This approach relies on the server clearing the `turasFileDownload` cookie before returning the file response,
and setting the `turasFileDownloadError` if a file could not be sent in the response. If the server has not been configured 
to do this, then **do not** set the `turas-file` attribute on the anchors.

To show and hide the modal manually:

```javascript
var modal = TurasDesignSystem.ShowFileDownloadModal();
// ...
modal.hide();
```