# CASA Looper Plugin

This plugin provides a method of implementing the ["Add Another Thing" pattern](https://design-system.dwp.gov.uk/patterns/add-another-thing) described in the [DWP Design System](https://design-system.dwp.gov.uk/).

## Usage

This plugin requires that you create at least 2 separate Express sub-apps:

* One for your "parent" app that houses your main Plan, and
* One for the "looper" app that captures data multiple times

You must also mount your looper CASA app on a parameterised route (see below):

```javascript
import express from 'express';
import { MemoryStore } from 'express-session';
import { configure } from '@dwp/govuk-casa';
import { looper, looperParent } from '../plugins/looper/index.js';

// Mount URLs for the "main" Plan, and the "loop" Plan
const MOUNT_MAIN = '/main/';
const MOUNT_LOOP = '/loop/';

// Both apps must share a session
const session = {
  secret: 'secret',
  store: new MemoryStore(),
};

// Create a CASA app for your looping journey
const { mount: mountLoop } = configure({
  session,
  plugins: [
    looper({
      // The waypoint on your "parent" journey that will display a summary of
      // all captured data from each loop across the "looper" Plan
      seedWaypoint: 'summary',

      // The URL on which your "parent" app is mounted
      parentMountUrl: MOUNT_MAIN,

      // The last waypoint in your "looper" Plan. All journeys in the looper
      // must end on this waypoint.
      lastWaypoint: 'check-your-loop-answers',
    }),
  ],
});
const loopApp = mountLoop(express(), {
  route: '/:contextid', // Must use "contextid" parameter here
});

// Create a CASA app for your main, "parent" Plan
const { mount: mountMain } = configure({
  session,
  plugins: [
    looperParent({
      // As above
      seedWaypoint: 'summary',

      // The URL on which your "looper" app is mounted
      looperMountUrl: MOUNT_LOOP,

      // The last waypoint in your "looper" Plan. All journeys in the looper
      // must end on this waypoint.
      lastWaypoint: 'check-your-loop-answers',
    }),
  ],
});
const mainApp = mount(express());

// Mount everything into a top-level app
const app = express();
app.use(MOUNT_MAIN, mainApp);
app.use(MOUNT_LOOP, loopApp);
app.listen();
```

## Templates

In your "seed" waypoint template, you will have the following variables available:

| Variable | Description |
|----------|-------------|
| `loopContexts[]` | Array of ephemeral `JourneyContext` instances associated with this seed waypoint. These are additionally decorated with attributes below ... |
| `loopContexts[].editUrl` | Send the user to this URL to edit the context. You can use `waypointUrl()` to generate your own URL instead, but this default link will take you to the last page in the loop journey, assuming it is the "check your answers" summary for the loop questions. Refer to the ["Add Another Thing" pattern](https://design-system.dwp.gov.uk/patterns/add-another-thing#changing-and-removing-items-from-the-summary-list-page) for details on how this may need altering for you use-case. |
| `loopContexts[].removeUrl` | Send the user to this URL so they can confirm removal of this contest |
| `loopContexts[].isComplete` | If the user has reached the last waypoint in the loop Plan, then the context will be flagged as "complete". Incomplete contexts will prevent the user from progressing beyond the seed waypoint. |
| `createUrl` | Send the user to this URL to create a new loop context |

## Hooks

The plugin provides a couple of extra hooks that you can use on the looping journey app. These are just Express middleware functions

| Hook | Description |
|------|-------------|
| `looper.remove_prerender` | Called just before the "remove" page is rendered. Executes on both `GET` and `POST` requests |
| `looper.remove_preredirect` | Called just before the user is redirected after removing (or cancelling the removal) of a loop context. Executed on `POST` requests |

Example usage:

```javascript
const { mount } = configure({
  session,
  plugins: [
    looper({
      seedWaypoint: 'summary',
      parentMountUrl: '/main/',
      lastWaypoint: 'check-your-loop-answers',
    }),
  ],
  hooks: [{
    hook: 'looper.remove_prerender',
    middleware: (req, res, next) => {
      // E.g. add some variables to `res.locals`
      next();
    }
  }],
});
```

## Handling missing looper contexts

Sometimes, a journey context will no longer be available to the loop app; for example if the user removes a loop entry and then attempts to navigate back to that removed entry. In such cases, the user will be redirected to a `/not-found` route on your loop's mount point.

You can intercept this route handler to replace it with your own error page or route behaviours:

```javascript
ancillaryRouter.prependUse('/not-found', (req, res, next) => {
  console.log('Intercepting /not-found route');
  next();
});
```

## Notes

### `showSeedWaypointFirst`

By default, the "seed waypoint" will be skipped on the first iteration, and only shown once that iteration is complete.

You can alter this behaviour by using the `showSeedWaypointFirst` flag on the looper parent application. This allows you, for example, to populate the seed waypoint template with data pulled from other sources (e.g. external APIs) before the user can then choose to "add another".

```javascript
looperParent({
  // ...
  showSeedWaypointFirst: true
})
```

### Different design patterns

Documentation is available [here](./docs/hmrc-add-another.md) to explain how to use different design patterns
for the `Add another item` page.

### Customising seed waypoint

Documentation is available [here](./docs/customising-seed-waypoint.md) to explain how the seed waypoint (`Add another item` page) can be customised.

### Nested loops

This plugin does not currently support nested loops, so you cannot attach loop journeys to another loop journey. We have to draw the line somewhere!
