Skip to content

Commit 0e2c41c

Browse files
author
Duncan Lock
committed
Improve labels for better accessibility:
* Associate each label with it's control, by making sure that each control has an id, and using the labels `for` attribute to bind to this. * Don't output label elements for button controls
1 parent 454fa0e commit 0e2c41c

File tree

6 files changed

+6994
-32
lines changed

6 files changed

+6994
-32
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"scripts": {
88
"prebuild": "npm run test",
99
"build": "webpack --progress --config webpack.build.config.js",
10-
"dev": "webpack-dev-server --config webpack.dev.config.js --inline --hot --content-base dev/",
10+
"dev": "webpack-dev-server --config webpack.dev.config.js --inline --hot --content-base dev/ --port 8082",
1111
"lint": "eslint --ext=.js,.vue src test/unit/specs",
1212
"coverall": "cat ./test/unit/coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
1313
"coverage": "npm run test && npm run coverall",

src/fields/abstractField.js

+23
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,29 @@ export default {
9595
this.$set(this.schema, "errors", []); // Be reactive
9696
else
9797
this.schema.errors.splice(0); // Clear
98+
},
99+
100+
getFieldID(schema) {
101+
// Try to get a reasonable default id from the schema,
102+
// then slugify it.
103+
if (typeof schema.id !== 'undefined') {
104+
// If an ID's been explicitly set, use it unchanged
105+
return schema.id
106+
} else {
107+
return (schema.inputName || schema.label || schema.model)
108+
.toString()
109+
.trim()
110+
.toLowerCase()
111+
// Spaces to dashes
112+
.replace(/ /g, '-')
113+
// Multiple dashes to one
114+
.replace(/-{2,}/g, '-')
115+
// Remove leading & trailing dashes
116+
.replace(/^-+|-+$/g, '')
117+
// Remove anything that isn't a (English/ASCII) letter or number.
118+
.replace(/([^a-zA-Z0-9\._-]+)/g, '')
119+
;
120+
}
98121
}
99122
}
100123
};

src/fields/fieldInput.vue

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<template lang="jade">
22
.wrapper
33
input.form-control(
4-
:type="schema.inputType",
4+
:id="getFieldID(schema)",
5+
:type="schema.inputType",
56
:value="value",
67
@input="value = $event.target.value",
78
number="schema.inputType == 'number'"
@@ -62,12 +63,12 @@
6263
return new Date(value).getTime();
6364
}
6465
}
65-
66+
6667
return value;
6768
}
6869
}
6970
};
70-
71+
7172
</script>
7273

7374
<style lang="sass">

src/fields/fieldTextArea.vue

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<template lang="jade">
2-
textarea.form-control(v-model="value", :disabled="disabled", :maxlength="schema.max", :minlength="schema.min", :placeholder="schema.placeholder", :readonly="schema.readonly", :rows="schema.rows || 2", :name="schema.inputName")
2+
textarea.form-control(
3+
v-model="value",
4+
:id="getFieldID(schema)",
5+
:disabled="disabled",
6+
:maxlength="schema.max",
7+
:minlength="schema.min",
8+
:placeholder="schema.placeholder",
9+
:readonly="schema.readonly",
10+
:rows="schema.rows || 2",
11+
:name="schema.inputName")
312
</template>
413

514
<script>
@@ -8,7 +17,7 @@
817
export default {
918
mixins: [ abstractField ]
1019
};
11-
20+
1221
</script>
1322

1423

src/formGenerator.vue

+40-26
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ div
33
fieldset.vue-form-generator(v-if='schema != null')
44
template(v-for='field in fields')
55
.form-group(v-if='fieldVisible(field)', :class='getFieldRowClasses(field)')
6-
label
6+
label(v-if="fieldTypeHasLabel(field)", :for="getFieldID(field)")
77
| {{ field.label }}
88
span.help(v-if='field.help')
99
i.icon
@@ -20,6 +20,7 @@ div
2020
<script>
2121
// import Vue from "vue";
2222
import {each, isFunction, isNil, isArray, isString} from "lodash";
23+
import getFieldID from "./fields/abstractField";
2324
2425
// Load all fields from '../fields' folder
2526
let Fields = require.context("./fields/", false, /^\.\/field([\w-_]+)\.vue$/);
@@ -32,7 +33,9 @@ div
3233
3334
export default {
3435
components: fieldComponents,
35-
36+
37+
mixins: [ getFieldID ],
38+
3639
props: {
3740
schema: Object,
3841
@@ -58,7 +61,7 @@ div
5861
default: false
5962
}
6063
},
61-
64+
6265
data () {
6366
return {
6467
errors: [] // Validation errors
@@ -111,15 +114,15 @@ div
111114
}
112115
});
113116
},
114-
117+
115118
methods: {
116119
// Get style classes of field
117120
getFieldRowClasses(field) {
118121
let baseClasses = {
119-
error: field.errors && field.errors.length > 0,
120-
disabled: this.fieldDisabled(field),
121-
readonly: field.readonly,
122-
featured: field.featured,
122+
error: field.errors && field.errors.length > 0,
123+
disabled: this.fieldDisabled(field),
124+
readonly: field.readonly,
125+
featured: field.featured,
123126
required: field.required
124127
};
125128
@@ -140,6 +143,17 @@ div
140143
return "field-" + fieldSchema.type;
141144
},
142145
146+
// Should field type have a label?
147+
fieldTypeHasLabel(field) {
148+
switch (field.type) {
149+
case 'button':
150+
case 'submit':
151+
return false;
152+
default:
153+
return true;
154+
}
155+
},
156+
143157
// Get disabled attr of field
144158
fieldDisabled(field) {
145159
if (isFunction(field.disabled))
@@ -160,7 +174,7 @@ div
160174
return true;
161175
162176
return field.visible;
163-
},
177+
},
164178
165179
// Validating the model properties
166180
validate() {
@@ -190,7 +204,7 @@ div
190204
191205
each(this.$children, (child) => {
192206
child.clearValidationErrors();
193-
});
207+
});
194208
},
195209
modelUpdated(newVal, schema){
196210
// console.log("a child model has updated", newVal, schema);
@@ -205,19 +219,19 @@ div
205219
}
206220
}
207221
};
208-
222+
209223
</script>
210224

211225
<style lang="sass">
212-
226+
213227
$errorColor: #F00;
214228
215229
fieldset.vue-form-generator {
216230
217231
* {
218232
box-sizing: border-box;
219-
}
220-
233+
}
234+
221235
.form-control {
222236
// Default Bootstrap .form-control style
223237
display: block;
@@ -231,10 +245,10 @@ div
231245
border: 1px solid #ccc;
232246
border-radius: 4px;
233247
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
234-
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
248+
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
235249
236250
} // .form-control
237-
251+
238252
span.help {
239253
margin-left: 0.3em;
240254
position: relative;
@@ -283,13 +297,13 @@ div
283297
left: 0;
284298
position: absolute;
285299
width: 100%;
286-
}
300+
}
287301
288302
&:hover .helpText {
289303
opacity: 1;
290304
pointer-events: auto;
291305
transform: translateY(0px);
292-
}
306+
}
293307
294308
} // span.help
295309
@@ -301,11 +315,11 @@ div
301315
margin-left: 4px;
302316
}
303317
304-
button, input[type=submit] {
318+
button, input[type=submit] {
305319
// Default Bootstrap button style
306320
display: inline-block;
307321
padding: 6px 12px;
308-
margin: 0px;
322+
margin: 0px;
309323
font-size: 14px;
310324
font-weight: normal;
311325
line-height: 1.42857143;
@@ -340,7 +354,7 @@ div
340354
341355
} // button, input[submit]
342356
343-
} // .field-wrap
357+
} // .field-wrap
344358
345359
.hint {
346360
font-style: italic;
@@ -362,7 +376,7 @@ div
362376
&.featured {
363377
label {
364378
font-weight: bold;
365-
}
379+
}
366380
}
367381
368382
&.required {
@@ -373,14 +387,14 @@ div
373387
position: absolute;
374388
padding-left: 0.2em;
375389
font-size: 1em;
376-
}
390+
}
377391
}
378392
379393
&.disabled {
380394
label {
381395
color: #666;
382396
font-style: italic;
383-
}
397+
}
384398
}
385399
386400
&.error {
@@ -403,11 +417,11 @@ div
403417
font-weight: 600;
404418
}
405419
406-
} // .errors
420+
} // .errors
407421
408422
} // .error
409423
410424
} // .form-group
411425
412426
} // fieldset
413-
</style>
427+
</style>

0 commit comments

Comments
 (0)