Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal programmatic and data attribute api interface for components. #1238

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions app/views/examples/options-data-api/index.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
{% extends "template.njk" %}

{% from "character-count/macro.njk" import govukCharacterCount %}
{% from "error-summary/macro.njk" import govukErrorSummary %}

{% block head %}
<!--[if !IE 8]><!-->
<link rel="stylesheet" href="/public/app.css">
<!--<![endif]-->
<!--[if IE 8]>
<link rel="stylesheet" href="/public/app-ie8.css">
<![endif]-->
<!--[if lt IE 9]>
<script src="/vendor/html5-shiv/html5shiv.js"></script>
<![endif]-->
{% endblock %}

{% block content %}
<h1 class="govuk-heading-xl">
Options Data Api
</h1>

<h2 class="govuk-heading-l">Character count<h2>
<h3 class="govuk-heading-m">Data API</h3>
<div id="character-count-data">
{{ govukCharacterCount({
name: "with-hint",
id: "with-hint",
maxlength: 200,
label: {
text: "Can you provide more detail?"
},
hint: {
text: "Do not include personal or financial information like your National Insurance number or credit card details."
}
}) }}
</div>
<h3 class="govuk-heading-m">Programmatic API</h3>
<div id="character-count-programmatic">
{{ govukCharacterCount({
name: "programmatic-api",
id: "programmatic-api",
label: {
text: "Can you provide more detail?"
},
hint: {
text: "Do not include personal or financial information like your National Insurance number or credit card details."
}
}) }}
</div>
<h3 class="govuk-heading-m">Overriden Data API</h3>
<div id="character-count-overriden">
{{ govukCharacterCount({
name: "overriden-api",
id: "overriden-api",
maxlength: 200,
label: {
text: "Can you provide more detail?"
},
hint: {
text: "Do not include personal or financial information like your National Insurance number or credit card details."
}
}) }}
</div>

<h2 class="govuk-heading-l">Error summary<h2>
<h3 class="govuk-heading-m">Data API</h3>
<div id="error-summary-data">
{{ govukErrorSummary({
autofocus: false,
titleText: "There is a problem",
errorList: [
{
text: "The date your passport was issued must be in the past",
href: "#passport-issued-error"
},
{
text: "Enter a postcode, like AA1 1AA",
href: "#postcode-error"
}
]
}) }}
</div>
<h3 class="govuk-heading-m">Programmatic API</h3>
<div id="error-summary-programmatic">
{{ govukErrorSummary({
titleText: "There is a problem",
errorList: [
{
text: "The date your passport was issued must be in the past",
href: "#passport-issued-error"
},
{
text: "Enter a postcode, like AA1 1AA",
href: "#postcode-error"
}
]
}) }}
</div>
<h3 class="govuk-heading-m">Overriden Data API</h3>
<div id="error-summary-overriden">
{{ govukErrorSummary({
titleText: "There is a problem",
errorList: [
{
text: "The date your passport was issued must be in the past",
href: "#passport-issued-error"
},
{
text: "Enter a postcode, like AA1 1AA",
href: "#postcode-error"
}
]
}) }}
</div>
{% endblock %}

{% block bodyEnd %}
<script src="/public/all.js"></script>
<script>
var CharacterCount = window.GOVUKFrontend.CharacterCount

// Character Count
var $characterCountDataAPI = document.querySelector('#character-count-data [data-module="character-count"]')
new CharacterCount($characterCountDataAPI).init()

var $characterCountProgrammaticAPI = document.querySelector('#character-count-programmatic [data-module="character-count"]')
new CharacterCount($characterCountProgrammaticAPI, {
maxwords: 200
}).init()

// Should the programmatic API override the data? Or the other way around?
var $characterCountOverriddenDataAPI = document.querySelector('#character-count-overriden [data-module="character-count"]')
new CharacterCount($characterCountOverriddenDataAPI, {
maxlength: 300
}).init()
</script>
<script>
var ErrorSummary = window.GOVUKFrontend.ErrorSummary

// Character Count
var $errorSummaryDataAPI = document.querySelector('#error-summary-data [data-module="error-summary"]')
new ErrorSummary($errorSummaryDataAPI).init()

var $errorSummaryProgrammaticAPI = document.querySelector('#error-summary-programmatic [data-module="error-summary"]')
new ErrorSummary($errorSummaryProgrammaticAPI, {
autofocus: false
}).init()

// Should the programmatic API override the data? Or the other way around?
var $errorSummaryOverriddenDataAPI = document.querySelector('#error-summary-overriden [data-module="error-summary"]')
new ErrorSummary($errorSummaryOverriddenDataAPI, {
autofocus: true
}).init()
</script>
{% endblock %}
29 changes: 29 additions & 0 deletions src/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,32 @@ export function generateUniqueID () {
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
})
}

export function extractDatasetOptions ($element) {
var dataset = {}
var attributes = $element.attributes
if (!attributes) {
return dataset
}
for (var i = 0; i < attributes.length; i++) {
var attribute = attributes[i]
var match = attribute.name.match(/^data-(.+)/)
if (!match) {
continue
}

var value = attribute.value
var key = match[1]

if (!isNaN(value)) {
dataset[key] = parseInt(value, 10)
} else if(value === 'true') {
dataset[key] = true
} else if(value === 'false') {
dataset[key] = false
} else {
dataset[key] = value
}
}
return dataset
}
38 changes: 9 additions & 29 deletions src/components/character-count/character-count.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import '../../vendor/polyfills/Function/prototype/bind'
import '../../vendor/polyfills/Event' // addEventListener and event.target normaliziation
import '../../vendor/polyfills/Element/prototype/classList'

function CharacterCount ($module) {
import { extractDatasetOptions } from '../../common.js'

function CharacterCount ($module, options) {
this.$module = $module
this.$textarea = $module.querySelector('.js-character-count')

// If there are no options default to an empty object
this.options = typeof options !== 'undefined' ? options : {}
}

CharacterCount.prototype.defaults = {
Expand All @@ -22,19 +27,10 @@ CharacterCount.prototype.init = function () {
}

// Read options set using dataset ('data-' values)
this.options = this.getDataset($module)

// Determine the limit attribute (characters or words)
var countAttribute = this.defaults.characterCountAttribute
if (this.options.maxwords) {
countAttribute = this.defaults.wordCountAttribute
}

// Save the element limit
this.maxLength = $module.getAttribute(countAttribute)
this.options = Object.assign({}, extractDatasetOptions($module), this.options)

// Check for limit
if (!this.maxLength) {
if (!this.options.maxlength && !this.options.maxwords) {
return
}

Expand All @@ -57,22 +53,6 @@ CharacterCount.prototype.init = function () {
}
}

// Read data attributes
CharacterCount.prototype.getDataset = function (element) {
var dataset = {}
var attributes = element.attributes
if (attributes) {
for (var i = 0; i < attributes.length; i++) {
var attribute = attributes[i]
var match = attribute.name.match(/^data-(.+)/)
if (match) {
dataset[match[1]] = attribute.value
}
}
}
return dataset
}

// Counts characters or words in text
CharacterCount.prototype.count = function (text) {
var length
Expand Down Expand Up @@ -136,7 +116,7 @@ CharacterCount.prototype.updateCountMessage = function () {

// Determine the remaining number of characters/words
var currentLength = this.count(countElement.value)
var maxLength = this.maxLength
var maxLength = options.maxlength || options.maxwords
var remainingNumber = maxLength - currentLength

// Set threshold if presented in options
Expand Down
2 changes: 2 additions & 0 deletions src/components/character-count/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
errorMessage: params.errorMessage,
attributes: params.attributes
}) }}
{% if params.maxlength or params.maxwords %}
<span id="{{ params.id }}-info" class="govuk-hint govuk-character-count__message" aria-live="polite">
You can enter up to {{ params.maxlength or params.maxwords }} {{'words' if params.maxwords else 'characters' }}
</span>
{% endif %}
</div>
25 changes: 21 additions & 4 deletions src/components/error-summary/error-summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,39 @@ import '../../vendor/polyfills/Function/prototype/bind'
import '../../vendor/polyfills/Event' // addEventListener
import '../../vendor/polyfills/Element/prototype/closest'

function ErrorSummary ($module) {
import { extractDatasetOptions } from '../../common.js'

function ErrorSummary ($module, options) {
this.$module = $module

// If there are no options default to an empty object
this.options = typeof options !== 'undefined' ? options : {}
}

ErrorSummary.prototype.init = function () {
var $module = this.$module
if (!$module) {
return
}
window.addEventListener('load', function () {
$module.focus()
})

// Read options set using dataset ('data-' values)
this.options = Object.assign({}, extractDatasetOptions($module), this.options)

var autofocus = typeof this.options.autofocus === 'boolean' ? this.options.autofocus : true

if (autofocus) {
window.addEventListener('load', function () {
this.focus()
}.bind(this))
}

$module.addEventListener('click', this.handleClick.bind(this))
}

ErrorSummary.prototype.focus = function () {
this.$module.focus()
}

/**
* Click event handler
*
Expand Down
1 change: 1 addition & 0 deletions src/components/error-summary/template.njk
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<div class="govuk-error-summary
{%- if params.classes %} {{ params.classes }}{% endif %}" aria-labelledby="error-summary-title" role="alert" tabindex="-1"
{% if params.autofocus === false or params.autofocus === true %} data-autofocus="{{ params.autofocus }}"{% endif %}
{%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %} data-module="error-summary">
<h2 class="govuk-error-summary__title" id="error-summary-title">
{{ params.titleHtml | safe if params.titleHtml else params.titleText }}
Expand Down