Skip to content

Commit

Permalink
Allow nested objects in session
Browse files Browse the repository at this point in the history
E.g.

<input name="example1[name]" value="Hello 123">
<input name="example2[name]" value="Hello 456">
<input name="example3[name]" value="Hello 789">
  • Loading branch information
colinrotherham committed Sep 6, 2018
1 parent 0542338 commit 24b111c
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

New features:

- [Allow nested field values in session](https://github.com/alphagov/govuk-prototype-kit/pull/573)
- [Restart the app if environment variables change](https://github.com/alphagov/govuk-prototype-kit/pull/389)
- [Make it more difficult to accidentally clear the session data](https://github.com/alphagov/govuk-prototype-kit/pull/588)

Expand Down
79 changes: 77 additions & 2 deletions docs/documentation/session.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,87 @@ The easiest way to clear session data is to use 'Incognito mode' for each user,

In a route function, refer to `req.session`.

For example you might have `req.session.over18` or `req.session.firstName`.
### Accessing fields from the session

For example, when submitting the following (simplified) HTML:

```html
<input name="first-name" value="Sarah">
<input name="last-name" value="Philips">
```

You'll have a `req.session.data` object in your route function:

```js
{
'first-name': 'Sarah',
'last-name': 'Philips'
}
```

These two field values can be accessed in JavaScript as:

```js
req.session.data['first-name']
req.session.data['last-name']
```

Or in views as:

```
{{ data['first-name'] }}
{{ data['last-name'] }}
```

### Accessing nested fields from the session

Session data can also be nested for easy grouping. For example answers from multiple family members:

```html
<input name="claimant[first-name]" value="Sarah">
<input name="claimant[last-name]" value="Philips">

<input name="partner[first-name]" value="Michael">
<input name="partner[last-name]" value="Philips">
```

You'll have a nested `req.session.data` object in your route function:

```js
{
claimant: {
'first-name': 'Sarah',
'last-name': 'Philips'
},
partner: {
'first-name': 'Michael',
'last-name': 'Philips'
}
}
```

These four field values can be accessed in your route function as:

```js
req.session.data['claimant']['first-name']
req.session.data['claimant']['last-name']
req.session.data['partner']['first-name']
req.session.data['partner']['last-name']
```

Or in views as:

```
{{ data['claimant']['first-name'] }}
{{ data['claimant']['last-name'] }}
{{ data['partner']['first-name'] }}
{{ data['partner']['last-name'] }}
```

You can see a full example here:

[https://github.com/expressjs/session#example](https://github.com/expressjs/session#example)

You can read more about Express Session here:

[https://github.com/expressjs/session](https://github.com/expressjs/session)
[https://github.com/expressjs/session](https://github.com/expressjs/session)
62 changes: 60 additions & 2 deletions docs/views/examples/pass-data/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ <h2 class="govuk-heading-m">How to use</h2>

<pre class="app-code"><code>&lt;p&gt;{%raw%}{{ data['first-name'] }}{%endraw%}&lt;/p&gt;</code></pre>

<p>Or with nested fields:</p>

<pre class="app-code"><code>&lt;p&gt;{%raw%}{{ data['claimant']['first-name'] }}{%endraw%}&lt;/p&gt;</code></pre>

<h3 class="govuk-heading-s">
Clearing data
</h3>
Expand All @@ -69,7 +73,11 @@ <h3 class="govuk-heading-s">

<p>For a radio or checkbox input you need to use the 'checked' function:</p>

<pre class="app-code"><code>&lt;input type="radio" name="over-18" value="yes" {%raw%}{{ checked('over-18','yes') }}{%endraw%}&gt;</code></pre>
<pre class="app-code"><code>&lt;input type="radio" name="over-18" value="yes" {%raw%}{{ checked("over-18", "yes") }}{%endraw%}&gt;</code></pre>

<p>Or with nested fields:</p>

<pre class="app-code"><code>&lt;input type="radio" name="claimant[over-18]" value="yes" {%raw%}{{ checked("['claimant']['over-18']", "yes") }}{%endraw%}&gt;</code></pre>

<h3 class="govuk-heading-s">
Setting default data
Expand Down Expand Up @@ -119,16 +127,66 @@ <h3 class="govuk-heading-s">
}
]
}) }}
{% endraw %}<code></pre>

<h3 class="govuk-heading-s">
Using the data in Nunjucks macros (nested fields)
</h3>

<p>Example using the 'checked' function in a checkbox component macro (nested fields for multiple vehicles):</p>

<pre class="app-code"><code>{% raw %}
{{ govukCheckboxes({
name: "vehicle1[vehicle-features]"
fieldset: {
legend: {
text: "Which of these applies to your vehicle?"
}
},
hint: {
text: "Select all that apply"
},
items: [
{
value: "Heated seats",
text: "Heated seats",
id: "vehicle1-vehicle-features-heated-seats",
checked: checked("['vehicle1']['vehicle-features']", "Heated seats")
},
{
value: "GPS",
text: "GPS",
id: "vehicle1-vehicle-features-gps",
checked: checked("['vehicle1']['vehicle-features']", "GPS")
},
{
value: "Radio",
text: "Radio",
id: "vehicle1-vehicle-features-radio",
checked: checked("['vehicle1']['vehicle-features']", "Radio")
}
]
}) }}
{% endraw %}<code></pre>

<h3 class="govuk-heading-s">
Using the data on the server
</h3>

<p>You can access the data on the server in a route, for example for an input with name="first-name":</p>
<p>You can access the data on the server in a route function</p>

<p>For example for an input with name="first-name":</p>

<pre class="app-code"><code>var firstName = req.session.data['first-name']</code></pre>

<h3 class="govuk-heading-s">
Using the data on the server (nested fields)
</h3>

<p>For example for an input with name="claimant[first-name]":</p>

<pre class="app-code"><code>var firstName = req.session.data['claimant']['first-name']</code></pre>

<h3 class="govuk-heading-s">
Ignoring inputs
</h3>
Expand Down
26 changes: 20 additions & 6 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const fs = require('fs')

// NPM dependencies
const basicAuth = require('basic-auth')
const getKeypath = require('keypather/get')
const marked = require('marked')
const path = require('path')
const portScanner = require('portscanner')
Expand Down Expand Up @@ -34,7 +35,12 @@ exports.addCheckedFunction = function (env) {
return ''
}

var storedValue = this.ctx.data[name]
// Use string keys or object notation to support:
// checked("field-name")
// checked("['field-name']")
// checked("['parent']['field-name']")
name = !name.match(/[.[]/g) ? `['${name}']` : name
var storedValue = getKeypath(this.ctx.data, name)

// Check the requested data exists
if (storedValue === undefined) {
Expand Down Expand Up @@ -243,7 +249,7 @@ exports.matchMdRoutes = function (req, res) {
}

// Store data from POST body or GET query in session
var storeData = function (input, store) {
var storeData = function (input, data) {
for (var i in input) {
// any input where the name starts with _ is ignored
if (i.indexOf('_') === 0) {
Expand All @@ -254,7 +260,7 @@ var storeData = function (input, store) {

// Delete values when users unselect checkboxes
if (val === '_unchecked' || val === ['_unchecked']) {
delete store.data[i]
delete data[i]
continue
}

Expand All @@ -264,9 +270,17 @@ var storeData = function (input, store) {
if (index !== -1) {
val.splice(index, 1)
}
} else if (typeof val === 'object') {
// Store nested objects that aren't arrays
if (typeof data[i] !== 'object') {
data[i] = {}
}

// Add nested values
storeData(val, data[i])
}

store.data[i] = val
data[i] = val
}
}

Expand All @@ -289,8 +303,8 @@ exports.autoStoreData = function (req, res, next) {

req.session.data = Object.assign({}, sessionDataDefaults, req.session.data)

storeData(req.body, req.session)
storeData(req.query, req.session)
storeData(req.body, req.session.data)
storeData(req.query, req.session.data)

// Send session data to all views

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"gulp-sass": "^4.0.1",
"gulp-sourcemaps": "^2.6.0",
"gulp-util": "^3.0.7",
"keypather": "^3.0.0",
"marked": "^0.4.0",
"minimist": "1.2.0",
"notifications-node-client": "^4.1.0",
Expand Down

0 comments on commit 24b111c

Please sign in to comment.