├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── README.md ├── bower.json ├── package.json ├── src ├── axsdialog.css ├── axsdialog.js ├── index.html └── styles.css └── steps ├── step1.js ├── step10.js ├── step2.js ├── step3.js ├── step4.js ├── step5.js ├── step6.js ├── step7.js ├── step8.js └── step9.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # JS files must always use LF for tools to work 5 | *.js eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules/ 3 | /bower_components/ 4 | .idea 5 | .idea/* 6 | .idea/workspace.xml 7 | atlassian-ide-plugin.xml 8 | .settings 9 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true 21 | } 22 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | // Load all grunt tasks 5 | require('load-grunt-tasks')(grunt); 6 | 7 | // Show elapsed time at the end 8 | require('time-grunt')(grunt); 9 | 10 | // Project configuration. 11 | grunt.initConfig({ 12 | 13 | pkg: grunt.file.readJSON('package.json'), 14 | 15 | 16 | jshint: { 17 | options: { 18 | reporter: require('jshint-stylish') 19 | }, 20 | gruntfile: { 21 | options: { 22 | jshintrc: '.jshintrc' 23 | }, 24 | src: 'Gruntfile.js' 25 | }, 26 | src: { 27 | options: { 28 | jshintrc: '.jshintrc' 29 | }, 30 | src: ['src/**/*.js', 'steps/**/*.js', '!steps/step1.js', '!steps/step2.js'] 31 | } 32 | }, 33 | watch: { 34 | gruntfile: { 35 | files: '<%= jshint.gruntfile.src %>', 36 | tasks: ['jshint:gruntfile'] 37 | }, 38 | src: { 39 | files: '<%= jshint.src.src %>', 40 | tasks: ['jshint:src', 'jshint'] 41 | } 42 | } 43 | }); 44 | 45 | // Default task. 46 | grunt.registerTask('default', ['jshint']); 47 | }; 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # READ ME 2 | 3 | > *Watch the talk*: https://youtu.be/baR9OvL4g7w (F2E Summit 2015) 4 | 5 | ## Goal 6 | Use the creation of a dialog to demonstrate a number of core accessibility principles like 7 | * keyboard accessibility 8 | * focus management 9 | * ARIA roles 10 | * ARIA properties 11 | 12 | The choice of a dialog for this example is due to the way in which this involves a number of user experience concerns not often considered by developers. 13 | 14 | ## Steps 15 | 16 | ### Project Scaffolding 17 | 18 | * Create .bowerrc file 19 | * Create .editorconfig file 20 | * Create .gitattributes file 21 | * Create .gitignore file 22 | * Create .jshintrc file 23 | * Create bower.json file 24 | * Create package.json file 25 | * Create Gruntfile 26 | 27 | ### Install Deps 28 | 29 | Run `npm install && bower install` 30 | 31 | ### Develop 32 | 33 | #### Base 34 | * Create Web Page, CSS, and Script for the base of the demo. 35 | 36 | #### Step 1 37 | * Create our dialog object with an open method and a close method 38 | 39 | #### Step 2 40 | * Create the functionality to append the overlay, open the dialog, and close the dialog 41 | 42 | #### Step 3 43 | * Add ability to open the dialog from the Sign In Button 44 | 45 | #### Step 4 46 | * Shift focus to the new dialog 47 | 48 | #### Step 5 49 | * Allow dialog to close from escape key 50 | 51 | #### Step 6 52 | * add accessible keydown on Sign In button 53 | 54 | #### Step 7 55 | * add a Close button; make close button do the close business 56 | 57 | #### Step 8 58 | * Add role(s) and labels 59 | * Close button 60 | * Dialog 61 | * Change focus to 1st focusable item (the close button) 62 | 63 | #### Step 9 64 | * Explicitly set next logical element for focus 65 | 66 | #### Step 10 67 | * Hide all the things 68 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intuit-fe-summit", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "normalize.css": "~3.0.3" 6 | }, 7 | "devDependencies": { 8 | "normalize.css": "~3.0.3" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intuit-fe-summit-2015", 3 | "version": "0.0.1", 4 | "description": "Everything you need to know about JavaScript accessibility in 40 minutes", 5 | "keywords": [ 6 | "accessibility" 7 | ], 8 | "homepage": "https://github.com/karlgroves/intuit-fe-summit-2015", 9 | "bugs": "https://github.com/karlgroves/intuit-fe-summit-2015/issues", 10 | "author": { 11 | "name": "Karl Groves", 12 | "email": "karlgroves@gmail.com", 13 | "url": "https://github.com/karlgroves" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git@github.com:karlgroves/intuit-fe-summit-2015.git" 18 | }, 19 | "licenses": [ 20 | { 21 | "type": "MIT" 22 | } 23 | ], 24 | "devDependencies": { 25 | "grunt": "~0.4.5", 26 | "grunt-contrib-jshint": "~0.10.0", 27 | "grunt-contrib-watch": "~0.6.1", 28 | "time-grunt": "~1.0.0", 29 | "load-grunt-tasks": "~1.0.0", 30 | "jshint-stylish": "~1.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/axsdialog.css: -------------------------------------------------------------------------------- 1 | .axs_overlay { 2 | position: fixed; 3 | top: 0; 4 | right: 0; 5 | bottom: 0; 6 | left: 0; 7 | height: 100%; 8 | width: 100%; 9 | margin: 0; 10 | padding: 0; 11 | background: #000000; 12 | opacity: .25; 13 | filter: alpha(opacity=25); 14 | -moz-opacity: .25; 15 | z-index: 101; 16 | } 17 | 18 | .axs_dialog_wrapper { 19 | display: block; 20 | position: fixed; 21 | width: 380px; 22 | height: 275px; 23 | top: 50%; 24 | left: 50%; 25 | margin-left: -190px; 26 | margin-top: -100px; 27 | background-color: #ffffff; 28 | border: 2px solid #333; 29 | border-radius: 10px; 30 | padding: 0; 31 | z-index: 102; 32 | font-family: Verdana; 33 | font-size: 10pt; 34 | } 35 | 36 | .axs_dialog_wrapper .axs_dialog_header { 37 | margin: 0; 38 | border-bottom: solid 2px #333; 39 | background-color: #333; 40 | padding: 4px; 41 | color: White; 42 | font-weight: bold; 43 | height: 25px; 44 | } 45 | 46 | .axs_dialog_header h2 { 47 | float: left; 48 | margin-top: 0; 49 | margin-left: .5em; 50 | } 51 | 52 | .axs_dialog_header button { 53 | float: right; 54 | color: #333; 55 | padding: 3px; 56 | background-color: #fff; 57 | text-decoration: none; 58 | margin-right: .5em; 59 | border: 1px solid #fff; 60 | border-radius: 3px; 61 | width: 20px; 62 | height: 20px; 63 | line-height: 10px; 64 | margin-top: 4px; 65 | } 66 | 67 | .axs_dialog_header button:focus, .axs_dialog_header button:hover { 68 | float: right; 69 | color: #fff; 70 | padding: 3px; 71 | background-color: #666; 72 | text-decoration: none; 73 | margin-right: .5em; 74 | border: 1px solid #fff; 75 | border-radius: 3px; 76 | width: 20px; 77 | height: 20px; 78 | line-height: 10px; 79 | margin-top: 4px; 80 | outline: none; 81 | } 82 | 83 | .axs_dialog_content { 84 | padding: 1em; 85 | } 86 | 87 | .align_right { 88 | text-align: right; 89 | } 90 | 91 | .axs_dialog_wrapper form label { 92 | display: block; 93 | margin-bottom: 1.7em; 94 | } 95 | 96 | .axs_dialog_wrapper form label input { 97 | width: 350px; 98 | height: 2em; 99 | } 100 | 101 | .axs_dialog_wrapper form label span { 102 | display: block; 103 | font-weight: bold; 104 | } 105 | 106 | .axs_hidden{ 107 | display: none; 108 | } 109 | -------------------------------------------------------------------------------- /src/axsdialog.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var axsdialog = { 4 | 5 | overlay: document.createElement('div'), 6 | 7 | open: function(d, w) { 8 | var tehDialog = document.getElementById(d), 9 | dParent = tehDialog.parentNode, 10 | cButton = document.getElementById('close-button'); 11 | 12 | var tehWrapper = document.getElementById(w); 13 | tehWrapper.setAttribute('aria-hidden', 'true'); 14 | 15 | this.overlay.classList.add('axs_overlay'); 16 | dParent.insertBefore(this.overlay, tehDialog); 17 | 18 | tehDialog.classList.remove('axs_hidden'); 19 | tehDialog.classList.add('axs_dialog_wrapper'); 20 | 21 | tehDialog.setAttribute('tabindex', '-1'); 22 | tehDialog.setAttribute('role', 'dialog'); 23 | tehDialog.setAttribute('aria-labelledby', 'dLabel'); 24 | tehDialog.style.outline = 'none'; 25 | cButton.focus(); 26 | }, 27 | 28 | close: function(d, n, w) { 29 | 30 | var tehDialog = document.getElementById(d), 31 | nextLocation = document.getElementById(n); 32 | this.overlay.remove(); 33 | 34 | var tehWrapper = document.getElementById(w); 35 | tehWrapper.setAttribute('aria-hidden', 'false'); 36 | 37 | tehDialog.classList.remove('axs_dialog_wrapper'); 38 | tehDialog.classList.add('axs_hidden'); 39 | 40 | nextLocation.focus(); 41 | 42 | } 43 | 44 | 45 | /** 46 | * 47 | * @param opts object containing options to be used by other dialog functions 48 | */ 49 | init: function (opts) { 50 | 51 | this.focusable = 'a[href], area[href], button, select, textarea, *[tabindex="0"], input:not([type="hidden"])'; 52 | 53 | this.role = opts.role; 54 | 55 | // String: ID of the element that opened the dialog 56 | this.opener = opts.opener; 57 | 58 | // String: ID of the close control 59 | this.closer = opts.closer; 60 | 61 | // String: ID of the dialog container 62 | this.dialogElement = opts.dialogElement; 63 | 64 | // explicit reference to the opener element 65 | this.dialogOpener = document.getElementById(this.opener); 66 | 67 | // explicit reference to the closing element 68 | this.dialogCloser = document.getElementById(this.closer); 69 | 70 | // explicit reference to the dialog 71 | this.theDialog = document.getElementById(this.dialogElement); 72 | 73 | this.theDialog.classList.add('axs_hidden'); 74 | 75 | // explicit reference to the dialog's parent node 76 | this.dialogParent = this.theDialog.parentNode; 77 | 78 | // nodelist of the items on the page that can get focus 79 | this.couldFocus = document.querySelectorAll(this.focusable); 80 | 81 | this.cLength = this.couldFocus.length; 82 | 83 | // nodelist of the dialog wrapper's child elements 84 | this.dialogChildren = document.querySelectorAll('#' + this.dialogElement + ' *'); 85 | 86 | this.dLength = this.dialogChildren.length; 87 | 88 | this.dialogOpener.addEventListener('click', function () { 89 | axsdialog.open(); 90 | }, false 91 | ); 92 | 93 | this.dialogOpener.addEventListener('keydown', function (event) { 94 | var code = event.charCode || event.keyCode; 95 | if (event.type === 'keydown') { 96 | if (code === 32 || code === 13) { 97 | event.preventDefault(); 98 | axsdialog.open(); 99 | } 100 | } 101 | }, false 102 | ); 103 | 104 | this.dialogCloser.setAttribute('role', 'button'); 105 | this.dialogCloser.setAttribute('aria-label', 'Close Dialog'); 106 | 107 | this.dialogCloser.addEventListener('click', function () { 108 | axsdialog.close(); 109 | }, false 110 | ); 111 | 112 | this.dialogCloser.addEventListener('keydown', function (event) { 113 | var code = event.charCode || event.keyCode; 114 | if (event.type === 'keydown') { 115 | if (code === 32 || code === 13) { 116 | event.preventDefault(); 117 | axsdialog.close(); 118 | } 119 | } 120 | }, false 121 | ); 122 | 123 | 124 | // if the dialog is open allow the escape key to close it. 125 | document.onkeydown = function (event) { 126 | if (!axsdialog.theDialog.classList.contains('axs_hidden')) { 127 | if (event.keyCode === 27) { 128 | axsdialog.close(); 129 | } 130 | } 131 | }; 132 | } 133 | 134 | }; 135 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Everything you need to know about JavaScript Accessibility in 40 minutes. 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | Sign In 17 |
18 | 39 |
40 |

The Awful German Language (by Mark Twain)

41 |
A little learning makes the whole world kin.
-- Proverbs xxxii, 7. 42 |
43 | 44 |

I went often to look at the collection of curiosities in Heidelberg Castle, and one day I surprised the keeper of it with my German. I spoke entirely in that language. He was greatly 45 | interested; and after I had talked a while he said my German was very rare, possibly a "unique"; and wanted to add it to his museum. 46 |

47 | 48 |

If he had known what it had cost me to acquire my art, he would also have known that it would break any collector to buy it. Harris and I had been hard at work on our German during several weeks at that time, and although we had made good 49 | progress, it had been accomplished under great difficulty and annoyance, for three of our teachers had died in the mean time. A person who has not studied German can form no idea of what a perplexing language it is. 50 |

51 | 52 |

Surely there is not another language that is so slipshod and systemless, and so slippery and elusive to the grasp. One is washed about in it, hither and thither, in the most helpless way; and when at last he thinks he has captured a rule which 53 | offers firm ground to take a rest on amid the general rage and turmoil of the ten parts of speech, he turns over the page and reads, "Let the pupil make careful note of the following exceptions." He runs his 54 | eye down and finds that there are more exceptions to the rule than instances of it. So overboard he goes again, to hunt for another Ararat and find another quicksand. Such has been, and continues to be, my experience. Every time I think 55 | I have got one of these four confusing "cases" where I am master of it, a seemingly insignificant preposition intrudes itself into my sentence, clothed with an awful and unsuspected power, and crumbles the ground from under me. 56 | For instance, my book inquires after a certain bird -- (it is always inquiring after things which are of no sort of consequence to anybody): "Where is the bird?" Now the answer to this question -- according to the book -- is 57 | that the bird is waiting in the blacksmith shop on account of the rain. Of course no bird would do that, but then you must stick to the book. Very well, I begin to cipher out the German for that answer. I begin at the wrong end, necessarily, 58 | for that is the German idea. I say to myself, "Regen (rain) is masculine -- or maybe it is feminine -- or possibly neuter -- it is too much trouble to look now. Therefore, it is either der (the) Regen, 59 | or die (the) Regen, or das (the) Regen, according to which gender it may turn out to be when I look. In the interest of science, I will cipher it out on the hypothesis that it is masculine. Very well -- 60 | then 61 | the rain is der Regen, if it is simply in the quiescent state of being mentioned, without enlargement or discussion -- Nominative case; but if this rain is lying around, in a kind of a 62 | general way on the ground, it is then definitely located, it is doing something -- that is, 63 | resting (which is one of the German grammar's ideas of doing something), and this throws the rain into the Dative case, and makes it 64 | dem Regen. However, this rain is not resting, but is doing something 65 | actively, -- it is falling -- to interfere with the bird, likely -- and this indicates movement, which has the effect of sliding it into the Accusative case and changing dem Regen into 66 | den Regen." Having completed the grammatical horoscope of this matter, I answer up confidently and state in German that the bird is staying in the blacksmith shop "wegen (on account of) den Regen." 67 | Then the teacher lets me softly down with the remark that whenever the word "wegen" drops into a sentence, it always throws that subject into the Genitive case, regardless of consequences -- 68 | and that therefore this bird stayed in the blacksmith shop "wegen 69 | des Regens."

70 | 71 |

N. B. -- I was informed, later, by a higher authority, that there was an "exception" which permits one to say "wegen dem Regen" in certain peculiar and complex circumstances, but that this exception is 72 | not extended to anything but rain.

73 | 74 |

There are ten parts of speech, and they are all troublesome. An average sentence, in a German newspaper, is a sublime and impressive curiosity; it occupies a quarter of a column; it contains all the ten parts of speech -- not in regular order, 75 | but mixed; it is built mainly of compound words constructed by the writer on the spot, and not to be found in any dictionary -- six or seven words compacted into one, without joint or seam -- that is, without hyphens; it treats of fourteen 76 | or fifteen different subjects, each inclosed in a parenthesis of its own, with here and there extra parentheses which reinclose three or four of the minor parentheses, making pens within pens: finally, all the parentheses and reparentheses 77 | are massed together between a couple of king-parentheses, one of which is placed in the first line of the majestic sentence and the other in the middle of the last line of it -- 78 | after which comes the VERB, and you find out for the first time what the man has been talking about; and after the verb -- merely by way of ornament, as far as I can make out -- the writer shovels in "haben sind 79 | gewesen gehabt haben geworden sein," or words to that effect, and the monument is finished. I suppose that this closing hurrah is in the nature of the flourish to a man's signature -- not necessary, but pretty. German books are 80 | easy enough to read when you hold them before the looking-glass or stand on your head -- so as to reverse the construction -- but I think that to learn to read and understand a German newspaper is a thing which must always remain an impossibility 81 | to a foreigner.

82 | 83 |

Yet even the German books are not entirely free from attacks of the Parenthesis distemper -- though they are usually so mild as to cover only a few lines, and therefore when you at last get down to the verb it carries some meaning to your 84 | mind because you are able to remember a good deal of what has gone before. Now here is a sentence from a popular and excellent German novel -- with a slight parenthesis in it. I will make a perfectly literal translation, and throw in the 85 | parenthesis-marks and some hyphens for the assistance of the reader -- though in the original there are no parenthesis-marks or hyphens, and the reader is left to flounder through to the remote verb the best way he can:

86 | 87 |

"But when he, upon the street, the (in-satin-and-silk-covered-now-very-unconstrained-after-the-newest-fashioned-dressed) government counselor's wife met," etc., etc.

88 | 89 |
1. Wenn er aber auf der Strasse der in Sammt und Seide gehüllten jetzt sehr ungenirt nach der neusten Mode gekleideten Regierungsräthin begegnet. 90 |
91 | 92 |

That is from The Old Mamselle's Secret, by Mrs. Marlitt. And that sentence is constructed upon the most approved German model. You observe how far that verb is 93 | from the reader's base of operations; well, in a German newspaper they put their verb away over on the next page; and I have heard that sometimes after stringing along the exciting preliminaries and parentheses for a column or two, they 94 | get in a hurry and have to go to press without getting to the verb at all. Of course, then, the reader is left in a very exhausted and ignorant state.

95 | 96 |

We have the Parenthesis disease in our literature, too; and one may see cases of it every day in our books and newspapers: but with us it is the mark and sign of an unpracticed writer or a cloudy intellect, whereas with the Germans it is doubtless 97 | the mark and sign of a practiced pen and of the presence of that sort of luminous intellectual fog which stands for clearness among these people. For surely it is not clearness -- it necessarily can't be clearness. Even 98 | a jury would have penetration enough to discover that. A writer's ideas must be a good deal confused, a good deal out of line and sequence, when he starts out to say that a man met a counselor's wife in the street, and then right in the 99 | midst of this so simple undertaking halts these approaching people and makes them stand still until he jots down an inventory of the woman's dress. That is manifestly absurd. It reminds a person of those dentists who secure your instant 100 | and breathless interest in a tooth by taking a grip on it with the forceps, and then stand there and drawl through a tedious anecdote before they give the dreaded jerk. Parentheses in literature and dentistry are in bad taste.

101 | 102 |

The Germans have another kind of parenthesis, which they make by splitting a verb in two and putting half of it at the beginning of an exciting chapter and the other half at the end of it. Can any one conceive of anything 103 | more confusing than that? These things are called "separable verbs." The German grammar is blistered all over with separable verbs; and the wider the two portions of one of them are spread apart, the better the author of the 104 | crime is pleased with his performance. A favorite one is reiste ab -- which means departed. Here is an example which I culled from a novel and reduced to English:

105 | 106 |
"The trunks being now ready, he DE- after kissing his mother and sisters, and once more pressing to his bosom his adored Gretchen, who, dressed in simple white muslin, with a single tuberose in the ample folds of her 107 | rich brown hair, had tottered feebly down the stairs, still pale from the terror and excitement of the past evening, but longing to lay her poor aching head yet once again upon the breast of him whom she loved more dearly than life itself, 108 | PARTED." 109 |
110 | 111 |

However, it is not well to dwell too much on the separable verbs. One is sure to lose his temper early; and if he sticks to the subject, and will not be warned, it will at last either soften his brain or petrify it. Personal pronouns and adjectives 112 | are a fruitful nuisance in this language, and should have been left out. For instance, the same sound, sie, means 113 | you, and it means she, and it means her, and it means 114 | it, and it means they, and it means them. Think of the ragged poverty of a language which has to make one word do the work of six -- and a poor little weak thing of only three letters 115 | at that. But mainly, think of the exasperation of never knowing which of these meanings the speaker is trying to convey. This explains why, whenever a person says sie to me, I generally try to kill him, if a stranger.

116 | 117 |

Now observe the Adjective. Here was a case where simplicity would have been an advantage; therefore, for no other reason, the inventor of this language complicated it all he could. When we wish to speak of our "good friend or friends," 118 | in our enlightened tongue, we stick to the one form and have no trouble or hard feeling about it; but with the German tongue it is different. When a German gets his hands on an adjective, he declines it, and keeps on declining it until 119 | the common sense is all declined out of it. It is as bad as Latin. He says, for instance:

120 | 121 | 142 | 143 |

Now let the candidate for the asylum try to memorize those variations, and see how soon he will be elected. One might better go without friends in Germany than take all this trouble about them. I have shown what a bother it is to decline a 144 | good (male) friend; well this is only a third of the work, for there is a variety of new distortions of the adjective to be learned when the object is feminine, and still another when the object is neuter. Now there are more adjectives 145 | in this language than there are black cats in Switzerland, and they must all be as elaborately declined as the examples above suggested. Difficult? -- troublesome? -- these words cannot describe it. I heard a Californian student in Heidelberg 146 | say, in one of his calmest moods, that he would rather decline two drinks than one German adjective.

147 | 148 |

The inventor of the language seems to have taken pleasure in complicating it in every way he could think of. For instance, if one is casually referring to a house, Haus, or a horse, Pferd, or a dog, Hund, 149 | he spells these words as I have indicated; but if he is referring to them in the Dative case, he sticks on a foolish and unnecessary e and spells them 150 | Hause, Pferde, Hunde. So, as an added e often signifies the plural, as the s does with us, the new student is likely to go on for a month making twins 151 | out of a Dative dog before he discovers his mistake; and on the other hand, many a new student who could ill afford loss, has bought and paid for two dogs and only got one of them, because he ignorantly bought that dog in the Dative singular 152 | when he really supposed he was talking plural -- which left the law on the seller's side, of course, by the strict rules of grammar, and therefore a suit for recovery could not lie. 153 |

154 | 155 |

In German, all the Nouns begin with a capital letter. Now that is a good idea; and a good idea, in this language, is necessarily conspicuous from its lonesomeness. I consider this capitalizing of nouns a good idea, because by reason of it 156 | you are almost always able to tell a noun the minute you see it. You fall into error occasionally, because you mistake the name of a person for the name of a thing, and waste a good deal of time trying to dig a meaning out of it. German 157 | names almost always do mean something, and this helps to deceive the student. I translated a passage one day, which said that "the infuriated tigress broke loose and utterly ate up the unfortunate fir forest" ( 158 | Tannenwald). When I was girding up my loins to doubt this, I found out that Tannenwald in this instance was a man's name.

159 | 160 |

Every noun has a gender, and there is no sense or system in the distribution; so the gender of each must be learned separately and by heart. There is no other way. To do this one has to have a memory like a memorandum-book. In German, a young 161 | lady has no sex, while a turnip has. Think what overwrought reverence that shows for the turnip, and what callous disrespect for the girl. See how it looks in print -- I translate this from a conversation in one of the best of the German 162 | Sunday-school books:

163 | 164 |
165 |
"Gretchen.
166 |
Wilhelm, where is the turnip?
167 |
Wilhelm.
168 |
She has gone to the kitchen.
169 |
Gretchen.
170 |
Where is the accomplished and beautiful English maiden? 171 |
172 |
Wilhelm.
173 |
It has gone to the opera."
174 |
175 | 176 |

To continue with the German genders: a tree is male, its buds are female, its leaves are neuter; horses are sexless, dogs are male, cats are female -- tomcats included, of course; a person's mouth, neck, bosom, elbows, fingers, nails, feet, 177 | and body are of the male sex, and his head is male or neuter according to the word selected to signify it, and not according to the sex of the individual who wears it -- for in Germany all the women either male heads or 178 | sexless ones; a person's nose, lips, shoulders, breast, hands, and toes are of the female sex; and his hair, ears, eyes, chin, legs, knees, heart, and conscience haven't any sex at all. The inventor of the language probably got what he 179 | knew about a conscience from hearsay.

180 | 181 |

Now, by the above dissection, the reader will see that in Germany a man may 182 | think he is a man, but when he comes to look into the matter closely, he is bound to have his doubts; he finds that in sober truth he is a most ridiculous mixture; and if he ends by trying to comfort himself with the thought 183 | that he can at least depend on a third of this mess as being manly and masculine, the humiliating second thought will quickly remind him that in this respect he is no better off than any woman or cow in the land.

184 | 185 |

In the German it is true that by some oversight of the inventor of the language, a Woman is a female; but a Wife (Weib) is not -- which is unfortunate. A Wife, here, has no sex; she is neuter; so, according to the grammar, 186 | a fish is he, his scales are she, but a fishwife is neither. To describe a wife as sexless may be called under-description; that is bad enough, but over-description is surely worse. A German speaks of 187 | an Englishman as the Engländer; to change the sex, he adds 188 | inn, and that stands for Englishwoman -- Engländerinn. That seems descriptive enough, but still it is not exact enough for a German; so he precedes the word with that article which indicates that 189 | the creature to follow is feminine, and writes it down thus: "die Engländer 190 | inn," -- which means "the she-Englishwoman." I consider that that person is over-described.

191 | 192 |

Well, after the student has learned the sex of a great number of nouns, he is still in a difficulty, because he finds it impossible to persuade his tongue to refer to things as "he" and "she," 193 | and " 194 | him" and "her," which it has been always accustomed to refer to it as " 195 | it." When he even frames a German sentence in his mind, with the hims and hers in the right places, and then works up his courage to the utterance-point, it is no use -- the moment he begins to speak his tongue flies 196 | the track and all those labored males and females come out as " 197 | its." And even when he is reading German to himself, he always calls those things "it," where as he ought to read in this way:

198 | 199 |
200 | 210 |
211 | 212 |
213 |
214 |
215 |

Log In

216 |
217 |
218 |
219 | 222 | 226 | 227 |
228 |
229 |
230 |
231 | 232 | 233 | 234 | 235 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | header { 2 | height: 50px; 3 | line-height: 50px; 4 | font-weight: bold; 5 | padding-left: 10%; 6 | padding-right: 10%; 7 | background-color: #eee; 8 | } 9 | 10 | header #tagline { 11 | float: left; 12 | } 13 | 14 | header #sign-in { 15 | float: right; 16 | height: 30px; 17 | width: auto; 18 | line-height: 30px; 19 | margin-top: 10px; 20 | background-color: #369; 21 | color: #eee; 22 | border: 1px solid #039; 23 | border-radius: 5px; 24 | text-decoration: none; 25 | padding-left: 3px; 26 | padding-right: 3px; 27 | } 28 | 29 | header #sign-in:focus, header #sign-in:hover { 30 | background-color: #fff; 31 | color: #369; 32 | outline: none; 33 | } 34 | 35 | h1 span { 36 | font-size: 16px; 37 | } 38 | 39 | nav ul { 40 | list-style: none; 41 | background-color: #444; 42 | text-align: center; 43 | padding: 0; 44 | margin: 0; 45 | } 46 | 47 | nav li { 48 | font-family: 'Oswald', sans-serif; 49 | font-size: 1.2em; 50 | line-height: 40px; 51 | text-align: left; 52 | } 53 | 54 | nav a { 55 | text-decoration: none; 56 | color: #fff; 57 | display: block; 58 | padding-left: 15px; 59 | border-bottom: 1px solid #888; 60 | transition: .3s background-color; 61 | } 62 | 63 | nav a:hover, nav a:focus { 64 | background-color: #005f5f; 65 | } 66 | 67 | nav li a:hover > ul, nav li a:focus > ul { 68 | display: block; 69 | } 70 | 71 | nav a.active { 72 | background-color: #aaa; 73 | color: #444; 74 | cursor: default; 75 | } 76 | 77 | /* Sub Menus */ 78 | nav li li { 79 | font-size: .8em; 80 | } 81 | 82 | /******************************************* 83 | Style menu for larger screens 84 | 85 | Using 650px (130px each * 5 items), but ems 86 | or other values could be used depending on other factors 87 | ********************************************/ 88 | 89 | @media screen and (min-width: 650px) { 90 | nav li { 91 | width: 160px; 92 | border-bottom: none; 93 | height: 50px; 94 | line-height: 50px; 95 | font-size: 1.4em; 96 | display: inline-block; 97 | margin-right: -4px; 98 | } 99 | 100 | nav a { 101 | border-bottom: none; 102 | } 103 | 104 | nav > ul > li { 105 | text-align: center; 106 | } 107 | 108 | nav > ul > li > a { 109 | padding-left: 0; 110 | } 111 | 112 | /* Sub Menus */ 113 | nav li ul { 114 | position: absolute; 115 | display: none; 116 | width: inherit; 117 | } 118 | 119 | nav li:hover ul, nav li:focus ul { 120 | display: block; 121 | } 122 | 123 | nav li ul li { 124 | display: block; 125 | } 126 | } 127 | 128 | main { 129 | padding-left: 5%; 130 | padding-right: 5%; 131 | } 132 | 133 | footer { 134 | background-color: #369; 135 | padding-top: 1em; 136 | padding-bottom: 1em; 137 | padding-right: 5%; 138 | padding-left: 5%; 139 | color: #fff; 140 | text-align: center; 141 | } 142 | 143 | footer ul, footer ul li { 144 | display: inline; 145 | margin: 0; 146 | padding: 0; 147 | } 148 | 149 | footer ul li a { 150 | color: #eee; 151 | margin-right: .5em; 152 | } 153 | 154 | #login form{ 155 | padding-top: 1em; 156 | } 157 | -------------------------------------------------------------------------------- /steps/step1.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // STEP 1: Create our dialog object with an open method and a close method 4 | var axsdialog = { 5 | 6 | open: function() {}, 7 | 8 | close: function() {} 9 | 10 | }; 11 | -------------------------------------------------------------------------------- /steps/step10.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // STEP 10: Hide all the things 4 | 'use strict'; 5 | 6 | var axsdialog = { 7 | 8 | overlay: document.createElement('div'), 9 | 10 | open: function(d, w) { 11 | var tehDialog = document.getElementById(d), 12 | dParent = tehDialog.parentNode, 13 | cButton = document.getElementById('close-button'); 14 | 15 | var tehWrapper = document.getElementById(w); 16 | tehWrapper.setAttribute('aria-hidden', 'true'); 17 | 18 | this.overlay.classList.add('axs_overlay'); 19 | dParent.insertBefore(this.overlay, tehDialog); 20 | 21 | tehDialog.classList.remove('axs_hidden'); 22 | tehDialog.classList.add('axs_dialog_wrapper'); 23 | 24 | tehDialog.setAttribute('tabindex', '-1'); 25 | tehDialog.setAttribute('role', 'dialog'); 26 | tehDialog.setAttribute('aria-labelledby', 'dLabel'); 27 | tehDialog.style.outline = 'none'; 28 | cButton.focus(); 29 | }, 30 | 31 | close: function(d, n, w) { 32 | 33 | var tehDialog = document.getElementById(d), 34 | nextLocation = document.getElementById(n); 35 | this.overlay.remove(); 36 | 37 | var tehWrapper = document.getElementById(w); 38 | tehWrapper.setAttribute('aria-hidden', 'false'); 39 | 40 | tehDialog.classList.remove('axs_dialog_wrapper'); 41 | tehDialog.classList.add('axs_hidden'); 42 | 43 | nextLocation.focus(); 44 | 45 | } 46 | 47 | }; 48 | 49 | var dOpener = document.getElementById('sign-in'); 50 | dOpener.setAttribute('tabindex', '0'); 51 | dOpener.setAttribute('role', 'button'); 52 | 53 | dOpener.addEventListener('click', function() { 54 | axsdialog.open('tehDialog', 'wrap'); 55 | }, false); 56 | 57 | dOpener.addEventListener('keydown', function(event) { 58 | 59 | var code = event.charCode || event.keyCode; 60 | if (event.type === 'keydown') { 61 | if (code === 32 || code === 13) { 62 | event.preventDefault(); 63 | axsdialog.open('tehDialog'); 64 | } 65 | } 66 | }, false); 67 | 68 | document.onkeydown = function(e) { 69 | // ESCAPE key pressed 70 | if (e.keyCode === 27) { 71 | axsdialog.close('tehDialog', 'sign-in', 'wrap'); 72 | } 73 | }; 74 | 75 | 76 | 77 | var closeButton = document.createElement('button'); 78 | closeButton.setAttribute('id', 'close-button'); 79 | closeButton.textContent = 'X'; 80 | 81 | var tehDialogHeader = document.getElementById('tehDialogHeader'); 82 | tehDialogHeader.appendChild(closeButton); 83 | 84 | var closer = document.getElementById('close-button'); 85 | 86 | closer.setAttribute('role', 'button'); 87 | closer.setAttribute('aria-label', 'Close Dialog'); 88 | 89 | closer.addEventListener('click', function() { 90 | axsdialog.close('tehDialog', 'sign-in'); 91 | }, false); 92 | 93 | closer.addEventListener('keydown', function(event) { 94 | 95 | var code = event.charCode || event.keyCode; 96 | if (event.type === 'keydown') { 97 | if (code === 32 || code === 13) { 98 | event.preventDefault(); 99 | axsdialog.close('tehDialog', 'sign-in'); 100 | } 101 | } 102 | }, false); 103 | -------------------------------------------------------------------------------- /steps/step2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // STEP 2: Create the functionality to append the overlay, open the dialog, and close the dialog 4 | var axsdialog = { 5 | 6 | overlay: document.createElement('div'), 7 | 8 | open: function(d) { 9 | var tehDialog = document.getElementById(d), 10 | dParent = tehDialog.parentNode; 11 | 12 | this.overlay.classList.add('axs_overlay'); 13 | 14 | dParent.insertBefore(this.overlay, tehDialog); 15 | 16 | tehDialog.classList.remove('axs_hidden'); 17 | tehDialog.classList.add('axs_dialog_wrapper'); 18 | }, 19 | 20 | close: function(d) { 21 | var tehDialog = document.getElementById(d); 22 | this.overlay.remove(); 23 | 24 | tehDialog.classList.remove('axs_dialog_wrapper'); 25 | tehDialog.classList.add('axs_hidden'); 26 | } 27 | 28 | }; 29 | -------------------------------------------------------------------------------- /steps/step3.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // STEP 3: Add ability to open the dialog from the Sign In Button 4 | var axsdialog = { 5 | 6 | overlay: document.createElement('div'), 7 | 8 | open: function(d) { 9 | var tehDialog = document.getElementById(d), 10 | dParent = tehDialog.parentNode; 11 | 12 | this.overlay.classList.add('axs_overlay'); 13 | dParent.insertBefore(this.overlay, tehDialog); 14 | 15 | tehDialog.classList.remove('axs_hidden'); 16 | tehDialog.classList.add('axs_dialog_wrapper'); 17 | }, 18 | 19 | close: function(d) { 20 | var tehDialog = document.getElementById(d); 21 | this.overlay.remove(); 22 | 23 | tehDialog.classList.remove('axs_dialog_wrapper'); 24 | tehDialog.classList.add('axs_hidden'); 25 | } 26 | 27 | }; 28 | 29 | // Step 3 adds this: 30 | var dOpener = document.getElementById('sign-in'); 31 | 32 | dOpener.addEventListener('click', function() { 33 | axsdialog.open('tehDialog'); 34 | }, false); 35 | -------------------------------------------------------------------------------- /steps/step4.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // STEP 4: Shift focus to the new dialog 4 | var axsdialog = { 5 | 6 | overlay: document.createElement('div'), 7 | 8 | open: function(d) { 9 | var tehDialog = document.getElementById(d), 10 | dParent = tehDialog.parentNode; 11 | 12 | this.overlay.classList.add('axs_overlay'); 13 | dParent.insertBefore(this.overlay, tehDialog); 14 | 15 | tehDialog.classList.remove('axs_hidden'); 16 | tehDialog.classList.add('axs_dialog_wrapper'); 17 | 18 | // Step 4 adds this bit for shifting focus to the new dialog when it opens 19 | tehDialog.setAttribute('tabindex', '-1'); 20 | tehDialog.style.outline = 'none'; 21 | tehDialog.focus(); 22 | }, 23 | 24 | close: function(d) { 25 | var tehDialog = document.getElementById(d); 26 | this.overlay.remove(); 27 | 28 | tehDialog.classList.remove('axs_dialog_wrapper'); 29 | tehDialog.classList.add('axs_hidden'); 30 | } 31 | 32 | }; 33 | 34 | var dOpener = document.getElementById('sign-in'); 35 | 36 | dOpener.addEventListener('click', function() { 37 | axsdialog.open('tehDialog'); 38 | }, false); 39 | -------------------------------------------------------------------------------- /steps/step5.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // STEP 5: Allow dialog to close from escape key 4 | var axsdialog = { 5 | 6 | overlay: document.createElement('div'), 7 | 8 | open: function(d) { 9 | var tehDialog = document.getElementById(d), 10 | dParent = tehDialog.parentNode; 11 | 12 | this.overlay.classList.add('axs_overlay'); 13 | dParent.insertBefore(this.overlay, tehDialog); 14 | 15 | tehDialog.classList.remove('axs_hidden'); 16 | tehDialog.classList.add('axs_dialog_wrapper'); 17 | 18 | tehDialog.setAttribute('tabindex', '-1'); 19 | tehDialog.style.outline = 'none'; 20 | tehDialog.focus(); 21 | }, 22 | 23 | close: function(d) { 24 | var tehDialog = document.getElementById(d); 25 | this.overlay.remove(); 26 | 27 | tehDialog.classList.remove('axs_dialog_wrapper'); 28 | tehDialog.classList.add('axs_hidden'); 29 | } 30 | 31 | }; 32 | 33 | var dOpener = document.getElementById('sign-in'); 34 | dOpener.addEventListener('click', function() { 35 | axsdialog.open('tehDialog'); 36 | }, false); 37 | 38 | // Step 5 adds functionality to allow the dialog to close from the escape key 39 | document.onkeydown = function(e) { 40 | // ESCAPE key pressed 41 | if (e.keyCode === 27) { 42 | axsdialog.close('tehDialog'); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /steps/step6.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // STEP 6 add accessible keydown on Sign In button 4 | var axsdialog = { 5 | 6 | overlay: document.createElement('div'), 7 | 8 | open: function(d) { 9 | var tehDialog = document.getElementById(d), 10 | dParent = tehDialog.parentNode; 11 | 12 | this.overlay.setAttribute('id', 'overlay'); 13 | dParent.insertBefore(this.overlay, tehDialog); 14 | 15 | tehDialog.classList.remove('axs_hidden'); 16 | tehDialog.classList.add('axs_dialog_wrapper'); 17 | 18 | tehDialog.setAttribute('tabindex', '-1'); 19 | tehDialog.style.outline = 'none'; 20 | tehDialog.focus(); 21 | }, 22 | 23 | close: function(d) { 24 | var tehDialog = document.getElementById(d); 25 | this.overlay.remove(); 26 | 27 | tehDialog.classList.remove('axs_dialog_wrapper'); 28 | tehDialog.classList.add('axs_hidden'); 29 | } 30 | 31 | }; 32 | 33 | var dOpener = document.getElementById('sign-in'); 34 | dOpener.setAttribute('tabindex', '0'); 35 | 36 | dOpener.addEventListener('click', function() { 37 | axsdialog.open('tehDialog'); 38 | }, false); 39 | 40 | // Step 6 adds the accessible keydown on Sign In button 41 | dOpener.addEventListener('keydown', function(event) { 42 | 43 | var code = event.charCode || event.keyCode; 44 | if (event.type === 'keydown') { 45 | if (code === 32 || code === 13) { 46 | event.preventDefault(); 47 | axsdialog.open('tehDialog'); 48 | } 49 | } 50 | }, false); 51 | 52 | document.onkeydown = function(e) { 53 | // ESCAPE key pressed 54 | if (e.keyCode === 27) { 55 | axsdialog.close('tehDialog'); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /steps/step7.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // STEP 7: add a Close button; make close button do the close business 4 | // For this step (and all that come after) you need to add this to 5 | // the dialog header: 6 | var axsdialog = { 7 | 8 | overlay: document.createElement('div'), 9 | 10 | open: function(d) { 11 | var tehDialog = document.getElementById(d), 12 | dParent = tehDialog.parentNode; 13 | 14 | this.overlay.classList.add('axs_overlay'); 15 | dParent.insertBefore(this.overlay, tehDialog); 16 | 17 | tehDialog.classList.remove('axs_hidden'); 18 | tehDialog.classList.add('axs_dialog_wrapper'); 19 | 20 | tehDialog.setAttribute('tabindex', '-1'); 21 | tehDialog.style.outline = 'none'; 22 | tehDialog.focus(); 23 | }, 24 | 25 | close: function(d) { 26 | var tehDialog = document.getElementById(d); 27 | this.overlay.remove(); 28 | 29 | tehDialog.classList.remove('axs_dialog_wrapper'); 30 | tehDialog.classList.add('axs_hidden'); 31 | } 32 | 33 | }; 34 | 35 | var dOpener = document.getElementById('sign-in'); 36 | dOpener.setAttribute('tabindex', '0'); 37 | dOpener.addEventListener('click', function() { 38 | axsdialog.open('tehDialog'); 39 | }, false); 40 | 41 | dOpener.addEventListener('keydown', function(event) { 42 | 43 | var code = event.charCode || event.keyCode; 44 | if (event.type === 'keydown') { 45 | if (code === 32 || code === 13) { 46 | event.preventDefault(); 47 | axsdialog.open('tehDialog'); 48 | } 49 | } 50 | }, false); 51 | 52 | document.onkeydown = function(e) { 53 | // ESCAPE key pressed 54 | if (e.keyCode === 27) { 55 | axsdialog.close('tehDialog'); 56 | } 57 | }; 58 | 59 | // Step 7: add a Close button; make close button do the close business 60 | // 61 | 62 | var closeButton = document.createElement('button'); 63 | closeButton.setAttribute('id', 'close-button'); 64 | closeButton.textContent = 'X'; 65 | 66 | var tehDialogHeader = document.getElementById('tehDialogHeader'); 67 | tehDialogHeader.appendChild(closeButton); 68 | 69 | var closer = document.getElementById('close-button'); 70 | closer.addEventListener('click', function() { 71 | axsdialog.close('tehDialog'); 72 | }, false); 73 | 74 | closer.addEventListener('keydown', function(event) { 75 | 76 | var code = event.charCode || event.keyCode; 77 | if (event.type === 'keydown') { 78 | if (code === 32 || code === 13) { 79 | event.preventDefault(); 80 | axsdialog.close('tehDialog'); 81 | } 82 | } 83 | }, false); 84 | -------------------------------------------------------------------------------- /steps/step8.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // STEP 8: Add role(s) and labels 4 | // * Close button 5 | // * Dialog 6 | // * Change focus to 1st focusable item (the close button) 7 | var axsdialog = { 8 | 9 | overlay: document.createElement('div'), 10 | 11 | open: function(d) { 12 | var tehDialog = document.getElementById(d), 13 | dParent = tehDialog.parentNode, 14 | 15 | // new variable for the close button 16 | cButton = document.getElementById('close-button'); 17 | 18 | this.overlay.classList.add('axs_overlay'); 19 | dParent.insertBefore(this.overlay, tehDialog); 20 | 21 | tehDialog.classList.remove('axs_hidden'); 22 | tehDialog.classList.add('axs_dialog_wrapper'); 23 | 24 | tehDialog.setAttribute('tabindex', '-1'); 25 | tehDialog.style.outline = 'none'; 26 | 27 | // Step 8 adds role and label to the dialog and gives focus to the close button 28 | tehDialog.setAttribute('role', 'dialog'); 29 | tehDialog.setAttribute('aria-labelledby', 'dLabel'); 30 | cButton.focus(); 31 | }, 32 | 33 | close: function(d) { 34 | var tehDialog = document.getElementById(d); 35 | this.overlay.remove(); 36 | 37 | tehDialog.classList.remove('axs_dialog_wrapper'); 38 | tehDialog.classList.add('axs_hidden'); 39 | } 40 | 41 | }; 42 | 43 | var dOpener = document.getElementById('sign-in'); 44 | dOpener.setAttribute('tabindex', '0'); 45 | 46 | // Step 8 adds role to the opener button 47 | dOpener.setAttribute('role', 'button'); 48 | 49 | dOpener.addEventListener('click', function() { 50 | axsdialog.open('tehDialog'); 51 | }, false); 52 | 53 | dOpener.addEventListener('keydown', function(event) { 54 | 55 | var code = event.charCode || event.keyCode; 56 | if (event.type === 'keydown') { 57 | if (code === 32 || code === 13) { 58 | event.preventDefault(); 59 | axsdialog.open('tehDialog'); 60 | } 61 | } 62 | }, false); 63 | 64 | document.onkeydown = function(e) { 65 | // ESCAPE key pressed 66 | if (e.keyCode === 27) { 67 | axsdialog.close('tehDialog'); 68 | } 69 | }; 70 | 71 | 72 | var closeButton = document.createElement('button'); 73 | closeButton.setAttribute('id', 'close-button'); 74 | closeButton.textContent = 'X'; 75 | 76 | var tehDialogHeader = document.getElementById('tehDialogHeader'); 77 | tehDialogHeader.appendChild(closeButton); 78 | 79 | var closer = document.getElementById('close-button'); 80 | 81 | // Step 8 adds a proper label to the close button 82 | closer.setAttribute('aria-label', 'Close Dialog'); 83 | 84 | closer.addEventListener('click', function() { 85 | axsdialog.close('tehDialog'); 86 | }, false); 87 | 88 | closer.addEventListener('keydown', function(event) { 89 | 90 | var code = event.charCode || event.keyCode; 91 | if (event.type === 'keydown') { 92 | if (code === 32 || code === 13) { 93 | event.preventDefault(); 94 | axsdialog.close('tehDialog'); 95 | } 96 | } 97 | }, false); 98 | -------------------------------------------------------------------------------- /steps/step9.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // STEP 9: Explicitly set next logical element for focus 4 | var axsdialog = { 5 | 6 | overlay: document.createElement('div'), 7 | 8 | open: function(d) { 9 | var tehDialog = document.getElementById(d), 10 | dParent = tehDialog.parentNode, 11 | cButton = document.getElementById('close-button'); 12 | 13 | this.overlay.classList.add('axs_overlay'); 14 | dParent.insertBefore(this.overlay, tehDialog); 15 | 16 | tehDialog.classList.remove('axs_hidden'); 17 | tehDialog.classList.add('axs_dialog_wrapper'); 18 | 19 | tehDialog.setAttribute('tabindex', '-1'); 20 | tehDialog.setAttribute('role', 'dialog'); 21 | tehDialog.setAttribute('aria-labelledby', 'dLabel'); 22 | tehDialog.style.outline = 'none'; 23 | cButton.focus(); 24 | }, 25 | 26 | // Step 9: Allows you to explicitly set the next logical element for focus via the 'n' argument 27 | close: function(d, n) { 28 | var tehDialog = document.getElementById(d), 29 | nextLocation = document.getElementById(n); 30 | this.overlay.remove(); 31 | 32 | tehDialog.classList.remove('axs_dialog_wrapper'); 33 | tehDialog.classList.add('axs_hidden'); 34 | 35 | nextLocation.focus(); 36 | 37 | } 38 | 39 | }; 40 | 41 | var dOpener = document.getElementById('sign-in'); 42 | dOpener.setAttribute('tabindex', '0'); 43 | dOpener.setAttribute('role', 'button'); 44 | 45 | dOpener.addEventListener('click', function() { 46 | axsdialog.open('tehDialog'); 47 | }, false); 48 | 49 | dOpener.addEventListener('keydown', function(event) { 50 | 51 | var code = event.charCode || event.keyCode; 52 | if (event.type === 'keydown') { 53 | if (code === 32 || code === 13) { 54 | event.preventDefault(); 55 | axsdialog.open('tehDialog'); 56 | } 57 | } 58 | }, false); 59 | 60 | document.onkeydown = function(e) { 61 | // ESCAPE key pressed 62 | if (e.keyCode === 27) { 63 | axsdialog.close('tehDialog', 'sign-in'); 64 | } 65 | }; 66 | 67 | 68 | 69 | var closeButton = document.createElement('button'); 70 | closeButton.setAttribute('id', 'close-button'); 71 | closeButton.textContent = 'X'; 72 | 73 | var tehDialogHeader = document.getElementById('tehDialogHeader'); 74 | tehDialogHeader.appendChild(closeButton); 75 | 76 | var closer = document.getElementById('close-button'); 77 | 78 | closer.setAttribute('role', 'button'); 79 | closer.setAttribute('aria-label', 'Close Dialog'); 80 | 81 | closer.addEventListener('click', function() { 82 | axsdialog.close('tehDialog', 'sign-in'); 83 | }, false); 84 | 85 | closer.addEventListener('keydown', function(event) { 86 | 87 | var code = event.charCode || event.keyCode; 88 | if (event.type === 'keydown') { 89 | if (code === 32 || code === 13) { 90 | event.preventDefault(); 91 | axsdialog.close('tehDialog', 'sign-in'); 92 | } 93 | } 94 | }, false); 95 | --------------------------------------------------------------------------------