From 910e3f2a5961cf2c711ffd26d6c1010372082183 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Thu, 2 Aug 2018 09:12:58 +0100 Subject: [PATCH] Allow nested objects in session E.g. --- CHANGELOG.md | 6 +++ docs/documentation/session.md | 63 +++++++++++++++++++++++- docs/views/examples/pass-data/index.html | 46 ++++++++++++++++- lib/utils.js | 22 ++++++--- package.json | 1 + 5 files changed, 129 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c1cd7545..a5b8b166e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# Unreleased + +New Features: + +- [Allow nested field values in session](https://github.com/alphagov/govuk-prototype-kit/pull/573) + # 7.1.0 New Features: diff --git a/docs/documentation/session.md b/docs/documentation/session.md index d9a4ce9632..e78a75c236 100644 --- a/docs/documentation/session.md +++ b/docs/documentation/session.md @@ -12,7 +12,66 @@ 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 + + +``` + +You'll have a `req.session.data` object in your route function: + +```js +{ + firstName: 'Sarah', + lastName: 'Philips' +} +``` + +These two field values can be accessed in JavaScript as: + +```js +req.session.data.firstName +req.session.data.lastName +``` + +### Accessing nested fields from the session + +Session data can also be nested for easy grouping. For example answers from multiple family members: + +```html + + + + + +``` + +You'll have a nested `req.session.data` object in your route function: + +```js +{ + claimant: { + firstName: 'Sarah', + lastName: 'Philips' + }, + partner: { + firstName: 'Michael', + lastName: 'Philips' + } +} +``` + +These four field values can be accessed in your route function as: + +```js +req.session.data.claimant.firstName +req.session.data.claimant.lastName +req.session.data.partner.firstName +req.session.data.partner.lastName +``` You can see a full example here: @@ -20,4 +79,4 @@ You can see a full example here: You can read more about Express Session here: -[https://github.com/expressjs/session](https://github.com/expressjs/session) \ No newline at end of file +[https://github.com/expressjs/session](https://github.com/expressjs/session) diff --git a/docs/views/examples/pass-data/index.html b/docs/views/examples/pass-data/index.html index ebfb61b2d8..0ec54d4dba 100644 --- a/docs/views/examples/pass-data/index.html +++ b/docs/views/examples/pass-data/index.html @@ -119,16 +119,60 @@

} ] }) }} +{% endraw %} + +

+ Using the data in Nunjucks macros (nested fields) +

+ +

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

+ +
{% 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 %}

Using the data on the server

-

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

+

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

var firstName = req.session.data['first-name']
+

+ More information on passing data from page to page here. +

+

Ignoring inputs

diff --git a/lib/utils.js b/lib/utils.js index 635c648367..497c8fe6a0 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -4,6 +4,7 @@ const fs = require('fs') // NPM dependencies const basicAuth = require('basic-auth') const marked = require('marked') +const Notation = require('notation') const path = require('path') const portScanner = require('portscanner') const prompt = require('prompt') @@ -34,7 +35,8 @@ exports.addCheckedFunction = function (env) { return '' } - var storedValue = this.ctx.data[name] + var storedData = new Notation(this.ctx.data) + var storedValue = storedData.get(name) // Check the requested data exists if (storedValue === undefined) { @@ -243,7 +245,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) { @@ -254,7 +256,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 } @@ -264,9 +266,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 + return storeData(val, data[i]) } - store.data[i] = val + data[i] = val } } @@ -289,8 +299,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 diff --git a/package.json b/package.json index b5945f6bc3..917b4aeb00 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "gulp-util": "^3.0.7", "marked": "^0.4.0", "minimist": "1.2.0", + "notation": "^1.3.6", "notifications-node-client": "^4.1.0", "nunjucks": "^3.1.3", "portscanner": "^2.1.1",