-
Notifications
You must be signed in to change notification settings - Fork 327
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
Add character count component #959
Add character count component #959
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for this @alex-ju, it's looking great 👍 I left a few comments but I didn't get through all of it, will continue reviewing this next week.
if (this.maxLength && this.countMessage) { | ||
// Replace maxlength attribute with data-maxlength to avoid hard limit | ||
$module.setAttribute('maxlength', '') | ||
$module.setAttribute('data-maxlength', $module.maxLength) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these be done on the textarea instead? Thedata-maxlength
on the outer wrapper is shown as undefined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm skeptic about moving these to the textarea instead because in my opinion they are being used at the component level not at the textarea element level. Also we should keep it flexible and easier to extend in the future so that the field could be an input or a textarea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad I assumed maxlength
was set on $textarea
. The maxlength value should be from the character count instance, not from the module as $module.maxLength
is undefined
. Think it should be:
$module.setAttribute('data-maxlength', this.maxLength)
var length | ||
if (options.maxwords) { | ||
// var tokens = text.split(' ') | ||
// length = tokens.length-1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could these comments be deleted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, removed them at cleanup.
if (remainingNumber < 0) { | ||
countElement.classList.add('govuk-textarea--error') | ||
if (options.validation) { | ||
countElement.parentNode.parentNode.classList.add('govuk-form-group--error') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested this locally be setting validation: true
and the form group error class gets set on the wrong div so that just needs changing. Would it be possible to do this more robustly by looking for the form-group class and adding the class to that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validation border via JavaScript is no longer supported as we'll only use it for server side validation. Removed the code and updated the PR description to reflect that.
text: "Can you provide more detail?", | ||
hintText: "Don't include personal or financial information, eg your National Insurance number or credit card details." | ||
} | ||
}) }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we have this file for other components. Does it help to demonstrate how you would use the component?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Example files were removed in #890 – I imagine the work on this started before that change was made.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wasn't aware. Will check and update.
|
||
CharacterCount.prototype.handleFocus = function () { | ||
// Check if value changed on focus | ||
this.valueChecker = setInterval(this.checkIfValueChanged.bind(this), 1000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we can take advantage of this possible alternative:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good suggestion. I'll try.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, does not support IE8. But we could be OK with this since it'll likely only be used for users of assistive technologies that we can assume will be using a newer version of IE?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: Did you try this out, we could raise an issue later to investigate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried it and behaves as expected (starts on focus, stops on blur). Could do a second test to be sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll raise an issue after we merge this to investigate the approach defined in that issue.
|
||
CharacterCount.prototype.handleFocus = function () { | ||
// Check if value changed on focus | ||
this.valueChecker = setInterval(this.checkIfValueChanged.bind(this), 1000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, does not support IE8. But we could be OK with this since it'll likely only be used for users of assistive technologies that we can assume will be using a newer version of IE?
A side note on this feature, might not be relevant to this PR directly: I think the logic (eg the regex) that determines how many words are present should be surfaced somehow, as this will need to be reused in any server side validation so that both frontend and backend are consistent. |
Updated the component code based on the working group feedback and rebased. |
var countAttribute = (this.options.maxwords) ? this.defaults.wordCountAttribute : this.defaults.characterCountAttribute | ||
|
||
// Set the element limit | ||
if ($module.getAttribute) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain this if statement? I'm not sure this would ever fail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha.. good catch. It was $module.getAttribute(countAttribute)
to make sure an attribute is set and if not the script will stop, but got lost on it's way.. will update!
// If there's a maximum length defined and the count message was successfully applied | ||
if (this.maxLength && this.countMessage) { | ||
// Replace maxlength attribute with data-maxlength to avoid hard limit | ||
$module.setAttribute('maxlength', '') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a maxlength to replace anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this is what I think it is, it's relevant to something I was concerned about - if users don't use the macro they could well add maxlength to the texarea html tag themselves. We could remove it in code as it looks like we are doing here, and/or talk about this aspect in the guidance ('do not add 'maxlength' because ...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, just spoke about this with @joelanman. This is removing the hard limit if any set (needs a bit of refactor and I'll use removeAttribute
instead of setting an empty string). How do you feel about this approach @NickColley?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels reasonable if we think this would be an accident for users to do this, would be good to add a comment to explain this.
|
||
CharacterCount.prototype.handleFocus = function () { | ||
// Check if value changed on focus | ||
this.valueChecker = setInterval(this.checkIfValueChanged.bind(this), 1000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: Did you try this out, we could raise an issue later to investigate?
{# {% set componentGuidanceLink = 'new link here' %} #} | ||
|
||
{% block componentArguments %} | ||
{{ govukTable({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have moved component arguments into the .yaml file now, we can do this for you...
{% set describedBy = "" %} | ||
|
||
<div class="govuk-character-count" data-module="character-count" | ||
{% if params.maxlength %} data-maxlength="{{ params.maxlength }}"{% endif %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking, if we used the whitespace stripping syntax these would end up on the same line when the markup is rendered.
<div class="govuk-character-count" data-module="character-count"
data-maxlength="blah"
></div>
vs
<div class="govuk-character-count" data-module="character-count" data-maxlength="blah"></div>
@NickColley thank you for reviewing this! update this PR with 4 commits addressing your comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fab work @alex-ju. Just need a CHANGELOG entry, the new README committed and another person from the team to approve this 👏
This commit: - removes the `govuk-character-count` markup and includes the textarea Nunjucks macro instead - add the label and hint with the texarea - removes now redundant "govuk-form-group" wrapper - this is now added with textarea macro - adds an outer wrapper div with `data-module=character-count`, this div also now has maxlength/maxwords set on it and renders classes Note that the hint work is not part of this card - for now, the hint is being passed through to the textarea to get the aria-describedby to show up.
This commit: - removes the `govuk-character-count` CSS as we now use the textarea styling for this (but leaves some classes to do with errors as these will be tackled in the hint work) - however, this does add CSS to adjust padding to stop the textarea from "jumping" when the border width changes (on adding error class)
Ajust JS so that $module is now `.js-character-count` inside the component and that maxlength/maxwords is grabbed from parent component.
Fix tests by using `js-character-count` for tests as this contains the functionality - although this is different from how we do it in other components, this should be looked at as part of the tests card. The hint and error message tests are still failing as they are "hijacked" by the character count - we'll look at this as part of the hint card.
and rely on JavaScript for the info message
Rebased against master again 🤞 |
// Check for existing info count message | ||
var countMessage = document.getElementById(elementId + '-info') | ||
// If there is no existing info count message we add one right after the field | ||
if (elementId && !countMessage) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we no longer need to create this count message, since we have a progressively enhanced way of doing this.
I don't think this is blocking but thought it was worth mentioning since it could simplify a lot of logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right about this, we can now remove the createCountMessage
function and the associated logic; what I'm questioning thought is what will happen if they forget to add the default message. With the macro we can ensure that, but without it may get lost. I'm up for simplifying the code if we think people will add the default message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I suppose this would force people to do a more progressive enhancement approach, but even as is I think it's still good to go.
var thresholdPercent = options.threshold ? options.threshold : 0 | ||
var thresholdValue = maxLength * thresholdPercent / 100 | ||
if (thresholdValue > currentLength) { | ||
countMessage.classList.add('govuk-character-count__message--disabled') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be polyfilled? (Maybe related to #674)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the classList
polyfill
|
||
CharacterCount.prototype.handleFocus = function () { | ||
// Check if value changed on focus | ||
this.valueChecker = setInterval(this.checkIfValueChanged.bind(this), 1000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll raise an issue after we merge this to investigate the approach defined in that issue.
|
||
{#- a record of other elements that we need to associate with the input using | ||
aria-describedby – for example hints or error messages -#} | ||
{% set describedBy = "" %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not following how describedBy gets set to something other than '', where does that happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the id of the default message as a default value for describedBy - which can be amended with hint and error message ids in the textarea component.
@alex-ju I've added some more review comments, I think the only one that is blocking is the polyfill one if we do need to polyfill classList |
This is excellent work, thanks so much Alex this will helps lots of teams across GOV. I love the care that has gone into the research and efforts to make this component work well in assistive technologies like Dragon. 👏 If we can release this today we might be able to publish it today 🚀 ! |
This PR is a proposal for integrating the character count component to govuk-frontend.
Preserved optional features
Threshold value
data-threshold="75"
(recommended)or.init( { threshold: 75 } )
Word count
data-maxwords="200"
(recommended)or.init( { maxwords: 200 } )
Hint and Error message
Dropped optional features
Known issues
JAWS repeats the label of the textarea each time an aria-live associated with the textarea is updated.
Design System backlog
Trello card