WebSpeechAPI to make a site accessible - javascript

WebSpeechAPI to make a site accessible but it first starts with the whole document and then the hovered element again.
How to resolve this?
I referenced most of the code from WebSpeechAPI MDN page.
All I want is for the browser to output the text of the tag that I am hovering over.
But it does that after speaking out all the contents of the document first. I think it does that because it catches the document first before I can reach the element.
var synth = window.speechSynthesis;
var inputForm = document.querySelector('form');
var inputTxt = document.querySelector('.txt');
var voiceSelect = document.querySelector('select');
var title = document.querySelector('#title');
var pitch = document.querySelector('#pitch');
var pitchValue = document.querySelector('.pitch-value');
var rate = document.querySelector('#rate');
var rateValue = document.querySelector('.rate-value');
var voices = []; //creat aan array to get thev voices
function populateVoiceList() {
voices = synth.getVoices(); // get the voices form the browser
for (i = 0; i < voices.length; i++) {
var option = document.createElement('option'); //create an element named option
option.textContent = voices[i].name + ' (' + voices[i].lang + ')'; //get all the info about the voice from the device and store in the text of the option tag
if (voices[i].default) {
option.textContent += ' -- DEFAULT';
}
option.setAttribute('data-lang', voices[i].lang); //set attributes of the option tag
option.setAttribute('data-name', voices[i].name);
voiceSelect.appendChild(option);
}
}
populateVoiceList();
if (speechSynthesis.onvoiceschanged !== undefined) { // this handler gets fired when the list returned by the getVoices function get changed
speechSynthesis.onvoiceschanged = populateVoiceList; //requires a function to handle the change in the list
}
document.onmouseover = function(e) {
var targ;
event.preventDefault(); //prevent default actions of the browser
if (e.target) targ = e.target;
var utterThis = new SpeechSynthesisUtterance(targ.textContent); //The SpeechSynthesisUtterance interface of the Web Speech API represents a speech request.
var selectedOption = voiceSelect.selectedOptions[0].getAttribute('data-name'); //get the data-name attribute of the selected option
for (i = 0; i < voices.length; i++) {
if (voices[i].name === selectedOption) {
utterThis.voice = voices[i]; //. We set the matching voice object to be the value of the SpeechSynthesisUtterance.voice property.
}
}
utterThis.pitch = pitch.value;
utterThis.rate = rate.value;
synth.speak(utterThis);
pitch.onchange = function() {
pitchValue.textContent = pitch.value;
}
rate.onchange = function() {
rateValue.textContent = rate.value;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1 id="Speech sYNTHESIZER" >Speech synthesiser</h1>
<p>Enter some text in the input below and press return to hear it. change voices using the dropdown menu.</p>
<form>
<input type="text" class="txt">
<div>
<label for="rate">Rate</label><input type="range" min="0.5" max="2" value="1" step="0.1" id="rate">
<div class="rate-value">1</div>
<div class="clearfix"></div>
</div>
<div>
<label for="pitch">Pitch</label><input type="range" min="0" max="2" value="1" step="0.1" id="pitch">
<div class="pitch-value">1</div>
<div class="clearfix"></div>
</div>
<select></select>
</form>
</body>
</html>

All I want is for the browser to output the text of the tag that I am hovering over.
You are starting from a legitimately good intention, but in fact except if you are making a special game or an innovative interface, it's a bad idea.
Accessibility on the web simply doesn't work like this. You'd better try to conform to standards like WCAG to make your site accessible.
Several reasons for this, at least two big ones:
Finding elements to be spoken with the mouse is a possibility, but isn't the common way to navigate on the web.
Blind people generally don't use a mouse because they don't care about the placement of elements on the screen, can quickly get lost or miss important information that way. They just need to have them appear logically when navigating with tab, heading by heading or by another mean provided by the screen reader.
For partially sighted users, using the mouse to read elements below the cursor is of help or not depending on their vision, but for the same reasons as blind users, it's often just a complementary help; and screen reader software have the feature built-in.
Screen reader users have their preferences about language, voice, rate, pitch, etc. fortunately they don't need to set them for each site they visit
So, unless you are making something really special or new, prefer stick to largely used means to access your site.
But it does that after speaking out all the contents of the document first. I think it does that because it catches the document first before I can reach the element.
This is probably because of event bubbling.

I am not qualified to comment on the correctness of your accessibility attempt. Other answers are best for that.
Since you mentioned that the whole document is being read, I think it is because you are attaching mouseover event to the document at:
document.onmouseover = function(e) { ... };
With my knowledge and my ES6 syntax, I have written down the following code to actually select all individual tags instead of document.
const tags = document.body.querySelectorAll();
//Adds mouseover event listener to each tags in tags
for(let i=0; i<tags.length(); i++){
let text = tags[i].textContent;
tags[i].addEventListener('mouseover', function(text){
//add code to speak the 'text' variable here
});
}
Basically, I used querySelectorAll to get all the tags to tags array. Next, looped over each tag to extract the textContent for each tag. Finally added event listeners to each tag in tags to run the speaking function whenever mouseover is trigerred.
Hope it helps!
Get all elements in the body tag using pure javascript

Related

Why chrome's rendering result is different when using breakpoints?

At first, i was trying to figuring how to implement a "line-by-line shows" effect, as far as i know, chrome will reflow the page when there is a dom insert action, so i just think the following code could work:
Click the "Click Me" span tag, there will be some lines of "-.-" span occur one-by-one.
<!DOCTYPE html>
<html lang="en">
<body>
<div>
<span id="clickme">Click Me</span>
</div>
<div id="container"></div>
<script>
const container = document.getElementById("container");
const clickme = document.getElementById("clickme");
clickme.addEventListener("click", () => {
for (let i = 0; i < 10; ++i) {
const child = document.createElement("div");
for (let j = 0; j < 20; ++j) {
const span = document.createElement("span");
span.textContent = "-.- ";
child.appendChild(span);
}
container.appendChild(child);
}
});
</script>
</body>
</html>
Unfortunately, chrome seems to apply all the dom change in one batch, so i used performance recording to check if it really does, and here the result just verified the guess:
I was really confused by this result, so i tried again to add a breakpoint at the for-loop entry to see what happened in this process, and the result is completely different from the normal process:
Here i uploaded two screenshots because the rest ones is just the same render result (line-by-line).
So my question is why breakpoints change the behavior of chrome's rendering process, and is there any way to implement the "line-by-line shows" effect only with JavaScript?
(My original purpose is a little more complicated than "line-by-line", and cannot be solved by CSS transition)
When Chrome hits a breakpoint it will enter the special "spin the event loop" algorithm (or a variation of it at least).
This algorithm allows to pause the currently active task and let the event loop process other duties, such as updating the rendering. Note though that no other scripts will run while in this state. So you will have CSS animations update, new DOM changes painted to screen, but you won't e.g have scroll events, or requestAnimationFrame callbacks fire.
Also, note that the browser does not trigger a reflow at each DOM insertion. It will try to trigger a reflow only at the last moment when required (i.e right before painting in most cases). Though some Web-APIs can force such a reflow (e.g all the position getters like HTMLElement#offsetTop.
You can use setInterval as shown in your amended code below. This repeatedly interrupts the processing thread, those allowing the browser to refresh the display. When the number of desired lines is reached, you should invoke clearInterval.
<!DOCTYPE html>
<html lang="en">
<body>
<div>
<span id="clickme">Click Me</span>
</div>
<div id="container"></div>
<script>
const container = document.getElementById("container");
const clickme = document.getElementById("clickme");
clickme.addEventListener("click", () => {
let lines = 0;
const intervalID = setInterval(() => {
const child = document.createElement("div");
for (let j = 0; j < 20; ++j) {
const span = document.createElement("span");
span.textContent = "-.- ";
child.appendChild(span);
}
container.appendChild(child);
if (++lines >= 10) {
clearInterval(intervalID);
}
}, 500);
});
</script>
</body>
</html>

Why does my Javascript Function stop working after a certain period of time?

First things first, I'm brand new to Javascript and Regex. I've only been dipping my toes in this past month. I've been trying to put together away to paste a url into a text input then automatically trim it down to just the host name and validate it before I'm able to push the button.
I've gotten it working a few different times but I keep running into the same issue: After a certain period of time, it simply stops working.
I've reformatted and cleaned up the code a few times (though, I'm sure it's still very sloppy because I'm new at this) and I can get it working again. But after an hour or so of working, it stops working. Reloading the page doesn't make a difference. Even restarting my computer doesn't make a difference. It simply stops working.
My only guess is that there must be something about the way I'm going about this which is causing it crash or stall out. Perhaps a formatting issue, perhaps the methodology altogether is flawed. I just don't know enough to be able to diagnose it yet.
Hopefully, some of you nice people would be able to point out my flaws or point me in the right direction of how to fix this. I've searched and I couldn't find anyone who was trying to do the things I'm doing all in one build (preparing to myself to be proved wrong here).
Here's the code:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Untitled Document</title>
</head>
<body>
<input id="notesUrlInput" type="text" placeholder="URL Goes here" pattern="^(?!www\.)[a-zA-Z0-9\-]+\.[a-zA-Z0-9]+$" autocomplete="off">
<button id="notesExecuteButton" disabled>Execute</button>
<span id="notesUrlOutput"></span>
<!------------------------------------------------------------------------------->
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script>
(function () {
var timeout = null;
var notesUrlOutput = document.getElementById("notesUrlOutput");
var notesExecuteButton = document.getElementById("notesExecuteButton");
document.getElementById('notesUrlInput').addEventListener('keyup',
function (e) {
clearTimeout(timeout);
timeout = setTimeout(
function () {
rawInput = $('#notesUrlInput').val();
cleanInput = rawInput.replace('www.', '');
cleanInput = cleanInput.replace('http://', '');
cleanInput = cleanInput.replace('https://', '');
cleanInput = cleanInput.replace(/\/.*/,'');
$('#notesUrlInput').val(cleanInput);
if (cleanInput.value == "") {
notesUrlOutput.innerHTML = "";
notesExecuteButton.disabled = true; return false;
} else if(!notesUrlInput.checkValidity()) {
notesUrlOutput.innerHTML = "Invalid URL: Please provide a valid URL";
notesExecuteButton.disabled = true; return false;
} else {
notesUrlOutput.innerHTML = "Input OK";
notesExecuteButton.disabled = false; return false;
}
}, 400);
});
})();
</script>
</body>
</html>
Frustratingly, when I pasted this code in here and ran it, it worked. As soon as I opened the file I copied this from in my browser. It stopped working. I just don't understand it.
From your code it looks like you want to extract just the domain name from the input field.
You mix JavaScript DOM calls and jQuery, which is fine. It is usually easier to interact with the DOM using just jQuery. Here is your code rewritten in jQuery:
const cleanRegex = /^https?:\/\/(?:www\.)?(.*)\/.*$/;
const validRegex = /^[\w\-]+(\.[\w]+)+$/;
(function () {
$('#notesExecuteButton').prop('disabled', true);
$('#notesUrlInput').on('input', function(event) {
let val = $(this).val();
let cleaned = val.replace(cleanRegex, '$1');
$(this).val(cleaned);
if(!cleaned) {
$('#notesUrlOutput').text('');
$('#notesExecuteButton').prop('disabled', true);
} else if(!cleaned.match(validRegex)) {
$('#notesUrlOutput').text('Invalid URL: Please provide a valid URL');
$('#notesExecuteButton').prop('disabled', true);
} else {
$('#notesUrlOutput').text('Input OK');
$('#notesExecuteButton').prop('disabled', false);
}
});
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<input id="notesUrlInput" />
<button id="notesExecuteButton" style="disabled: disabled;">Go</button>
<div id="notesUrlOutput"></div>
Explanation:
.on('input') - fires every time something changes in the input field- val.replace(cleanRegex, '$1') - clean up: strip protocol and www prefix, and URL path (any text after domain
cleaned.match(validRegex) - check validity of domain
.prop('disabled', true/false) - add/remove disable property

How do I get this traffic Light sequence to work with the click of the button???

So, I can get it to go from red to amber, but I am stuck on how to further get it to change to green, back to amber and then red again. Any help would be highly appreciated.
Also, I have created this on dreamwaver.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1>Traffic Lights </h1>
<img id="light" img src="../../red.jpg">
<button type="button" onClick="changeLights()">Change Lights</button>
<script>
function changeLights(){
document.getElementById('light').setAttribute("src","../../amber.jpg")
document.getElementById('light').setAttribute("src","../../green.jpg")
document.getElementById('light').setAttribute("src","../../amber.jpg")
//document.getElementById('light').setAttribute("src","../../red.jpg")
}
</script>
</body>
</html>
The best solution is to put the image names into an array and the cycle through the array using a counter. That counter can increase or decrease the count depending on which "end" of the array we've last hit.
Also, don't use inline HTML event handling attributes (onclick, etc.) as they create "spaghetti code", they cause global wrapper functions that alter the this binding and they don't follow the W3C Event Model standard. Instead wire elements them up to event handlers via code using .addEventListener().
// Get references to DOM elements:
var img = document.getElementById('light');
var btn = document.getElementById('btn');
// Hook click of button up to event handling function
btn.addEventListener("click", changeLights);
// Put image names into an array:
var imgs = ["green.jpg" , "amber.jpg", "red.jpg"];
// Establish counter variable and directional variable
var count = 0;
var additive = 1;
function changeLights(){
// Set image by getting element from array based on current counter value
img.setAttribute("src","../../" + imgs[count]);
// Verification of action
console.clear();
console.log(img);
// When we hit the edges, reverse direction,
if(count === 2) {
additive = -1; // Go backward
} else if(count === 0) {
additive = 1; // Go forward
}
// Adjust the count accordingly
count+= additive;
}
<h1>Traffic Lights </h1>
<img id="light" img src="../../red.jpg">
<button type="button" id='btn'>Change Lights</button>

Unable to use select option value in form for new window in JavaScript correctly

I'm a newbie with JavaScript and I asked a question earlier and got answers which helped me, but I thought I could incorporate it into the larger form I was working with and now I'm stuck again.
I have a large form with one select option. When the form is filled out, a new window opens and incorporates the values submitted into an invitation type page.
Everything else is working except for this select option. The problem I am having is depending on the selection, I want different text to be written into the new window (as part of the overall new window invitation page).
I'm really close, mostly b/c of help I received earlier today -- I can either get the new window to show just my named option value or I can get a whole new window with the different text (that is not part of the invitation page). I just can't combine the two.
I wrote up a smaller page in case someone wants to take a look at it. Thanks in advance.
<!doctype html>
<html>
<head>
<title>JavaScript Forms</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="mystyle.css">
<script type="text/JavaScript">
function newWindow() {
allInfo = open("", "displayWindow");
allInfo.document.open();
allInfo.document.write('<!doctype html><html><head><link rel="stylesheet" type="text/css" href="mystyle_invite.css"><title>Resume</title><meta charset="utf-8"> </head><body>');
allInfo.document.write(document.getElementById ('firstname').value);
allInfo.document.write('</body></html>');
allInfo.document.close();
}
function showName() {
var doIt=document.getElementById('firstname').value;
if ( doIt == "Michael" ) {
allInfo.document.write("Mr. " + doIt); //only "Mikey" is written out
}
else if ( doIt == "Sam" ) {
allInfo.document.write("Mrs. " + doIt);
}
else {
allInfo.document.write("Sir " + doIt);
}
}
</script>
</head>
<body>
<script type="text/JavaScript">
</script>
<form id="infoForm" method="post" name="infoForm">
<p>First Name:</p>
<p><select id="firstname" onChange="showName()" >
<option value="Mikey">nickname1</option>
<option value="Sammy">nickname2</option>
<option value="Sir Doug">nickname3</option>
</select></p>
<p> <input type="button" value="Submit Information" onClick="newWindow()"></p>
</form>
</body>
</html>
There are somethings that can help a lot here. First opening a second window can be a painful experience both for the user and the programmer. Unless you have an absolute need I would recommend manipulating the current window's DOM to display a popup instead.
Next if you can use DOM methods to change the page contents. document.write comes with a lot of problems which in your case are not apparent. Mainly it can erase you current DOM. You don't notice this in your example because the new window is blank so rewriting it is incidental.
Finally your use of allInfo is a quirky way to reference a global variable. It is not easily understood that this is happening from the style of code. In fact any linter will throw an error for your use of the global and will case an error if you declare "use strict" in your functions. Best to learn the good coding practises way.
Since we will want to interact with a variable (allInfo) in your case we should encapsulate the value in an object. This object can hold the state of that reference and offer some abstracted interactions with it. By doing so you avoid polluting the global name space and allow you to swap out your implementation without having to rewrite the parts of your program that depend on it.
// Our welcome window object
function WelcomeWindow() {
// save a reference to the content of your new window
// to be printed when ready. (Lazy execution)
this.innerHTML = '';
}
WelcomeWindow.prototype.open = function() {
this.win = window.open("", "displayWindow");
return this;
};
WelcomeWindow.prototype.close = function() {
this.win.close();
return this;
};
WelcomeWindow.prototype.write = function(html) {
this.innerHTML += '' + html;
return this;
};
WelcomeWindow.prototype.render = function() {
if (!this.win) { throw new Error("window has not been opened yet."); }
this.win.open();
this.win.write('<!doctype html><html><head><link rel="stylesheet" type="text/css" href="mystyle_invite.css"><title>Resume</title><meta charset="utf-8"> </head><body>');
this.win.write(this.innerHTML);
this.win.write('</body></html>');
this.win.close();
return this;
};
This allows us to declaratively manipulate the window before it is opened. For example if we want to add the name to the window:
function NameField(id) {
this.element = document.getElementById(id);
}
NameField.prototype.toString = function() {
var name = this.element.value;
switch (name) {
case 'Michael': return 'Mr. Michael';
case 'Sam': return 'Mrs. Sam';
default: return 'Sir ' + name;
}
};
NameField.prototype.toHtml = function() {
return '<strong>' + this.toString() + '</strong>';
};
Linking it together using code instead because adding events into the DOM only confuses the separation of markup and code.
window.onload = function() {
var form = document.getElementById('infoForm');
form.onsubmit = function(e) {
e.preventDefault();
var name = new NameField('firstName');
new WelcomWindow()
.write(name.toHtml())
.open()
.render();
return false;
};
};

Replace the surround of an html element with another document

I have an html page with (among other things) a Unity3D window. I would like to replace everything on the page without causing the Unity window to reload. I have tried the following jquery-tastic
function replaceSurround(keepElem, newElem)
{
keepElem.siblings().remove();
keepElem.prepend(newElem.prevAll());
keepElem.append(newElem.nextAll());
var keepParent = keepElem.parent();
var newParent = newElem.parent();
if (keepParent && newParent)
{
replaceSurround(keepParent, newParent);
}
}
where keepElem is an element in the original document and newElem is the corresponding element in the new document, but it did not work very well.
Here is what I've got, it seems to work...
jQuery.fn.rewrap = function(newWrap){
var $parent = jQuery(this).parent();
var $clone = jQuery(this).siblings().clone()
var $newParent = $clone.wrap(newWrap).parent().clone();
$parent.replaceWith($newParent);
}
$('#header').rewrap('<div class="container" style="background-color:blue;" />');
I tested it on the Stackoverflow website. One small problem though, it seems to be refiring some onX events...?
[edit]
On second thought, that is not what you meant at all....
Can't you just do something like:
$('#result').load('ajax/test.html #result');
?

Categories