Disable showModal auto-focusing using HTML attributes - html

As I am aware, the showModal() method runs the following steps which end up focusing elements within an HTML dialog (emphasis mine) :
Let subject be the dialog element on which the method was invoked.
If subject already has an open attribute, then throw an "InvalidStateError"
DOMException.
If subject is not connected, then throw an
"InvalidStateError"
DOMException`.
Add an open attribute to subject, whose value is the empty string.
Set the dialog to the centered alignment mode.
Let subject's node document be
blocked by the modal
dialog subject.
If subject's node document's top
layer does not already
contain subject, then
add subject to
subject's node document's top
layer.
Run the dialog focusing steps for subject.
So the last step, 8, will run the following dialog focusing steps on the dialog. From my understanding (which could be completely wrong), these three steps from the dialog focusing-steps section of the spec specify that the element should only be focused if the element is not inert and is auto-focusable:
If subject is inert, return.
Let control be the first descendant element of the subject, in tree order, that is not inert and has the autofocus attribute specified.
If there isn't one, then let control be the first non-inert descendant
element of subject, in tree order.
If there isn't one of those either, then let control be subject.
Run the focusing steps for control.
...
So, to me, it seems as though if my button below (see snippet) has the inert attribute or is not auto-focusable then it shouldn't get focused when the dialog opens. However, when I try and apply both attributes, it still ends up being focused.
Attempt with the inert boolean attribute (which I thought would've made the dialog focusing steps return above, hence performing no focusing):
const dialog = document.querySelector("#dialog");
document.querySelector("#open-btn").addEventListener('click', () => {
dialog.showModal();
});
document.querySelector("#close-btn").addEventListener('click', () => {
dialog.close();
});
#close-btn:focus {
background: red;
}
<button id="open-btn">Open</button>
<dialog id="dialog">
<button id="close-btn" inert="inert">×</button>
</dialog>
Attempt with the autofocus boolean attribute set to false (I believe this is how you set it to false, I also tried autofocus="false" which didn't work either):
const dialog = document.querySelector("#dialog");
document.querySelector("#open-btn").addEventListener('click', () => {
dialog.showModal();
});
document.querySelector("#close-btn").addEventListener('click', () => {
dialog.close();
});
#close-btn:focus {
background: red;
}
<button id="open-btn">Open</button>
<dialog id="dialog">
<button id="close-btn" autofocus="">×</button>
</dialog>
With both of these failing to work, I searched SO and found this answer which suggested that I might also be able to use tabindex="-1", which didn't work either.
I'm aware that I can blur the button once it is focused using .blur(), but my question specifically is:
Why don't the two fiddles above disable the button from being automatically focused?
Is there an HTML attribute of some sort that I can use to stop my button from being focused?

Disabled elements cannot be focused on. You could add the disabled attribute to close-btn.
But, disabled elements cannot be clicked. Add the onclick attribute to open-btn. Set the onclick to this: setTimeout(function(){document.getElementById('close-btn').disabled = false}). This just enables the button 1 millisecond after the button is clicked. The timeout is required so that it does not enable close-btn before the dialog is opened.
If the dialog is re-opened, The button is automatically focused. We could add another onclick attribute to close-btn. Set onclick on close.btn to this: this.disabled = true. This disables close-btn when it is clicked.
Final result:
const dialog = document.querySelector("#dialog");
document.querySelector("#open-btn").addEventListener('click', () => {
dialog.showModal();
});
document.querySelector("#close-btn").addEventListener('click', () => {
dialog.close();
});
#close-btn:focus {
background: red;
}
<button id="open-btn" onclick='setTimeout(function(){document.getElementById("close-btn").disabled = false})'>Open</button>
<dialog id="dialog">
<button disabled id="close-btn" inert="inert" onclick='this.disabled = true'>×</button>
</dialog>

Related

What is the HTML <dialog> tag used for and when to use it?

The way I've understood it, the tag is used to open and close content like a popup alert. What I fail to understand is what advantages the tag has compared to just using a "div" and styling it with css and adding functionality to it with js. It also seems counter intuitive to manipulate the "open" property in order to show/hide the content instead of using display:none/block; with css.
I also don't understand exactly which scenarios would be considered a dialog box. Is a form login box a dialogbox? What about a popup telling you to disable adblock? Are all popups that can be hidden considered dialog boxes?
The traditional, hacky way to create a dialog, via designing a div via CSS only seems to be intuitive for you because you are used to it. However, you need to implement every functionality related to it, such as:
opening it
closing it
Also, in the future, this will be enhanced by standard functionalities, so, while it's not urgent for already existent code, but when you write code, especially when you start a project, it makes sense to start using it. Let's see an example from [Mozilla's page][1]:
var updateButton = document.getElementById('updateDetails');
var favDialog = document.getElementById('favDialog');
var outputBox = document.querySelector('output');
var selectEl = document.querySelector('select');
var confirmBtn = document.getElementById('confirmBtn');
// "Update details" button opens the <dialog> modally
updateButton.addEventListener('click', function onOpen() {
if (typeof favDialog.showModal === "function") {
favDialog.showModal();
} else {
alert("The <dialog> API is not supported by this browser");
}
});
// "Favorite animal" input sets the value of the submit button
selectEl.addEventListener('change', function onSelect(e) {
confirmBtn.value = selectEl.value;
});
// "Confirm" button of form triggers "close" on dialog because of [method="dialog"]
favDialog.addEventListener('close', function onClose() {
outputBox.value = favDialog.returnValue + " button clicked - " + (new Date()).toString();
});
<!-- Simple pop-up dialog box containing a form -->
<dialog id="favDialog">
<form method="dialog">
<p><label>Favorite animal:
<select>
<option></option>
<option>Brine shrimp</option>
<option>Red panda</option>
<option>Spider monkey</option>
</select>
</label></p>
<menu>
<button value="cancel">Cancel</button>
<button id="confirmBtn" value="default">Confirm</button>
</menu>
</form>
</dialog>
<menu>
<button id="updateDetails">Update details</button>
</menu>
<output aria-live="polite"></output>
However, at the time of this writing (February the 3rd, 2022), this is not supported in all browsers, so it is perfectly feasible to avoid using it for now, until it will become supported everywhere.
[1]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog

How to use "invalid-feedback" class with selectpicker from Bootstrap-Select?

I'm using Bootstrap to do some form validation on my web app. With a normal select menu, it would be really easy to have an error message pop-up when the field is invalid:
<select class="someClass" required>
<option value="">Select an option</option>
<option>foo</option>
<option>bar</option>
</select>
<div class="invalid-feedback">Please make a selection.</div>
However, I'm using Bootstrap-Select's "selectpicker" class, and the "invalid-feedback" message in the div no longer works. Is there anyway to force Bootstrap-Select to recognize the "invalid-feedback" class or am I going to have to go about this a different way?
I figured out how to do this, and more generally this is an answer for anytime you have to "manually" force an error to work with Bootstrap's native validation system. It's really hacky, but I couldn't find anything else that works.
Say you have a "selectpicker" that looks like this:
<select id="mySelect" class="selectpicker" required>
<option value="">Select an option</option>
<option>foo</option>
<option>bar</option>
</select>
<div id="error" class="invalid-feedback">Please make a selection.</div>
The error message "Please make a selection" will not show, even if the select element is invalid; it will show, however, if it also has the "d-block" class:
<div id="error" class="invalid-feedback d-block">Please make a selection.</div>
So to manually force errors, you have to use JavaScript to check for the ":invalid" CSS pseudo-class; if it has this pseudo-class, then you add the "d-block" class to your div to show the error. You can use the matches() method and classList.add():
var selector = document.getElementById("mySelect");
var errorMsg = document.getElementById("error");
if(selector.matches(":invalid"))
{
errorMsg.classList.add("d-block");
}
You do this to add the message and you can remove it by checking for ":valid" and removing "d-block" from the classList.
I had multiple versions of the bootstrap-select elements in one of my forms and was having a really hard time getting this to work. The method below won't show the checkmark or x on the input, but it will show the invalid-feedback and valid-feedback boxes properly.
Using the advice from secretagentmango's answer, you can create a function that loops through all of your inputs with the "selectpicker" class, grab their parent form-group element, and then find the children "valid-feedback" and "invalid-feedback" elements
to add or remove the d-block class and hide/show them.
function bsSelectValidation() {
if ($("#myForm").hasClass('was-validated')) {
$(".selectpicker").each(function (i, el) {
if ($(el).is(":invalid")) {
$(el).closest(".form-group").find(".valid-feedback").removeClass("d-block");
$(el).closest(".form-group").find(".invalid-feedback").addClass("d-block");
}
else {
$(el).closest(".form-group").find(".invalid-feedback").removeClass("d-block");
$(el).closest(".form-group").find(".valid-feedback").addClass("d-block");
}
});
}
}
Now you need to run this function after form submit, and you can add it directly to the sample code from the Bootstrap Docs:
(function () {
'use strict';
window.addEventListener('load', function () {
// Fetch all the forms we want to apply custom Bootstrap validation styles to
var forms = document.getElementsByClassName('needs-validation');
// Loop over them and prevent submission
var validation = Array.prototype.filter.call(forms, function (form) {
form.addEventListener('submit', function (event) {
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add('was-validated');
bsSelectValidation();
}, false);
});
}, false);
})();
The only thing different in the above code from bootstrap's sample is the call to our new function, "bsSelectValidation".
Now you need to listen for changes in the form to automatically update the d-block classes and fix the valid/invalid messages as people make changes to the form:
$('#myForm').change(bsSelectValidation);
Now your select menus should properly show the valid-feedback and invalid-feedback divs on form submit or change.
I found that if I simply remove the value="" part of the "option" element, then the validation message shows properly. That is, if I don't select anything from the dropdown, the my "invalid-feedback" message shows up. When I select something, it goes away and I can proceed further. It's worth a try if you haven't tried it.
My first "option" is simply this: <option>(select)</option> -- no 'value' clause is present.
Hope this helps.

How can i click a span in Behat?

I am using Behat to test an third-party webshop. I have a item in the shoppingcart that i want to delete. A confirmation pop-up shows that asks me if i really want to do it. The structure of this dialog looks as following:
<div>
<strong class="title">Remove item from shoppingcart</strong>
<p>Are you sure you want to delete this product?</p>
<div class="button-container">
<span class="button" data-confirm="true">Yes</span>
<span class="button alt right" data-mfp-close-link="true">No</span>
</div>
</div>
I was able to select the span using xpath with the following code:
public function iConfirmTheWindow()
{
$session = $this->getSession();
$element = $session->getPage()->find(
'xpath',
$session->getSelectorsHandler()->selectorToXpath('css', 'span.button')
);
if (null === $element) {
throw new \InvalidArgumentException(sprintf('Could not find confirmation window'));
}
$element->click();
}
The selecting works, but Behat seems to be unable to click the span.
supports clicking on links and submit or reset buttons only. But "span" provided
I need to click this item, how can i rewrite my function so that it can be clicked?
The answer from #bentcoder doesn't make any different. It uses a different a selector to find the element, but the Minkcontext click functionality doesn't support clicking on span elements.
Which i find quite strange, because with jQuery you can add the button class to and span element and there is your button.
Context code:
/**
* #Given I click the :arg1 element
*/
public function iClickTheElement($selector)
{
$page = $this->getSession()->getPage();
$element = $page->find('css', $selector);
if (empty($element)) {
throw new Exception("No html element found for the selector ('$selector')");
}
$element->click();
}
CLI output:
And I click the "#new_account" element # tests/behat/features/account.feature:14
Behat\Mink\Driver\GoutteDriver supports clicking on links and submit or reset buttons only. But "span" provided (Behat\Mink\Exception\UnsupportedDriverActionException)
I'm assuming that you have have a Behat driver for interpreting javascript. So i've added #javascript to the feature:
like so:
#javascript
Scenario: Create new account
Given I am logged in as "user" user
And I am on "/user/settings"
And I click the ".new_account" element
The code snippet I use is this:
/**
* #Then /^I click on "([^"]*)"$/
*/
public function iClickOn($element)
{
$page = $this->getSession()->getPage();
$findName = $page->find("css", $element);
if (!$findName) {
throw new Exception($element . " could not be found");
} else {
$findName->click();
}
}
As an example, I would write something like this in my scenario:
Feature: Test Click
#javascript
Scenario: Clicking on spans
Given I go to "http://docs.behat.org/en/v2.5/"
And wait "3000"
When I click on "span:contains('behat.yml')"
And wait "3000"
Then I should be on "http://docs.behat.org/en/v2.5/guides/7.config.html"
I hope this helps.

Parent/child click relationships in AngularJS directives

I have a custom directive placed on a Kendo UI treeview widget.
It seems to be working fine side-by-side, except that I'm trying to simply display the custom icons next to the tree node which is clicked on (see sample image below).
So my directive is data-toggle-me, placed next to the Kendo k-template directive as follows :
<div class="reports-tree" kendo-tree-view="nav.treeview"
k-options="nav.treeOptions"
k-data-source="nav.reportsTreeDataSource"
k-on-change="nav.onTreeSelect(dataItem)" >
<span class="tree-node" k-template data-toggle-tree-icons>{{dataItem.text}}</span>
</div>
and the directive code here inserts some custom icons next to the tree node when a user clicks on that tree node :
.directive('toggleMe', function ($compile) {
// Kendo treeview, use the k-template directive to embed a span.
// Icons appear on Click event.
return {
restrict: 'AE',
transclude: true,
template: '<span ng-show="nav.displayIcons" id="myIcons" class="reptIcons" style="display:none;width:50px;align:right;">' +
' <a title="add new folder" ng-click="nav.addAfter(nav.selectedItem)"><i class="fa fa-folder-open"></i></a> ' +
'<a title="add report here" ng-click="nav.addBelow(nav.selectedItem)"><i class="fa fa-plus"></i></a> ' +
'<a title="remove" ng-click="nav.remove(nav.selectedItem)"><i class="fa fa-remove"></i></a> ' +
'<a title="rename" onclick="showRename(this);"><i class="fa fa-pencil"></i></a>' +
'</span>',
link: function (scope, elem, attrs) {
var icons = elem.find("#myIcons");
elem.on('click', function (e) {
$('.reptIcons').css('display', 'none');
icons.css("display", "inline");
icons.css("margin-left", "5px");
});
}
}
})
My biggest problem at this point is getting the icons to appear on the treenode which is clicked on. Then once the user clicks on a different node, the icons will only render again on the newly-clicked node.
This fiddle represents a partially-working example but the icons are appearing on every single treenode - click tree item to show icons
**** UPDATED TREE IMAGE - All child nodes now show icons (not what I want) ****
I'm not sure to understand your issue, you should try to reduce the code to the minimum and have a snippet/jsfiddle that works.
If all you want is not trigger click events when $scope.disableParentClick is set to true, simply add
elem.on('click', function (e) {
// Do not execute click event if disabled
if (!$scope.disableParentClick) { return; }
...
});
Now that seems all not very angular friendly to me. You should externalize your HTML in either the template or templateUrl of your directive, potentially adding to it a ng-if="displayTemplate" which would only display the node when a click would set $scope.displayTemplate = true;
Also, instead of listening for click events this way, you should use the ng-click directive. Everything is doable with directives. I can give more information when you better understand your problem: I suspect you are not approaching it the right way.
UPDATE: if all you want is display the icons list of the clicked element, you could do it way easier. You actually don't need the toggle-me directive, but even if you keep it you can solve all your troubles the angular-way, which is by using ng-click, ng-repeat, etc. Please have a look at the following jsFiffle to see one way of doing that. There are many other ways, but really try using ng-click to avoid troubles:
http://jsfiddle.net/kau9jnoe/
Events in the DOM are always bubbling up. That is a click on a link would trigger an onclick handler on every element up the hierarchy, e.g. also the body element. After all the click happened within body.
The same is true for your directive. Any click within your element triggers its event handler. To circumvent this either attach the event handler somewhere else or ignore clicks from the links.
The event object has a target property that tells you what element initiated the event. So you could do something like this:
elem.on('click', function (e) {
if (e.target.nodeName.toLowerCase() == 'a') return; //ignore click on links

Manually Triggering Form Validation using jQuery

I have a form with several different fieldsets. I have some jQuery that displays the field sets to the users one at a time. For browsers that support HTML5 validation, I'd love to make use of it. However, I need to do it on my terms. I'm using JQuery.
When a user clicks a JS Link to move to the next fieldset, I need the validation to happen on the current fieldset and block the user from moving forward if there is issues.
Ideally, as the user loses focus on an element, validation will occur.
Currently have novalidate going and using jQuery. Would prefer to use the native method. :)
TL;DR: Not caring about old browsers? Use form.reportValidity().
Need legacy browser support? Read on.
It actually is possible to trigger validation manually.
I'll use plain JavaScript in my answer to improve reusability, no jQuery is needed.
Assume the following HTML form:
<form>
<input required>
<button type="button">Trigger validation</button>
</form>
And let's grab our UI elements in JavaScript:
var form = document.querySelector('form')
var triggerButton = document.querySelector('button')
Don't need support for legacy browsers like Internet Explorer? This is for you.
All modern browsers support the reportValidity() method on form elements.
triggerButton.onclick = function () {
form.reportValidity()
}
That's it, we're done. Also, here's a simple CodePen using this approach.
Approach for older browsers
Below is a detailed explanation how reportValidity() can be emulated in older browsers.
However, you don't need to copy&paste those code blocks into your project yourself — there is a ponyfill/polyfill readily available for you.
Where reportValidity() is not supported, we need to trick the browser a little bit. So, what will we do?
Check validity of the form by calling form.checkValidity(). This will tell us if the form is valid, but not show the validation UI.
If the form is invalid, we create a temporary submit button and trigger a click on it. Since the form is not valid, we know it won't actually submit, however, it will show validation hints to the user. We'll remove the temporary submit button immedtiately, so it will never be visible to the user.
If the form is valid, we don't need to interfere at all and let the user proceed.
In code:
triggerButton.onclick = function () {
// Form is invalid!
if (!form.checkValidity()) {
// Create the temporary button, click and remove it
var tmpSubmit = document.createElement('button')
form.appendChild(tmpSubmit)
tmpSubmit.click()
form.removeChild(tmpSubmit)
} else {
// Form is valid, let the user proceed or do whatever we need to
}
}
This code will work in pretty much any common browser (I've tested it successfully down to IE11).
Here's a working CodePen example.
You can't trigger the native validation UI (see edit below), but you can easily take advantage of the validation API on arbitrary input elements:
$('input').blur(function(event) {
event.target.checkValidity();
}).bind('invalid', function(event) {
setTimeout(function() { $(event.target).focus();}, 50);
});
The first event fires checkValidity on every input element as soon as it loses focus, if the element is invalid then the corresponding event will be fired and trapped by the second event handler. This one sets the focus back to the element, but that could be quite annoying, I assume you have a better solution for notifying about the errors. Here's a working example of my code above.
EDIT: All modern browsers support the reportValidity() method for native HTML5 validation, per this answer.
In some extent, You CAN trigger HTML5 form validation and show hints to user without submitting the form!
Two button, one for validate, one for submit
Set a onclick listener on the validate button to set a global flag(say justValidate) to indicate this click is intended to check the validation of the form.
And set a onclick listener on the submit button to set the justValidate flag to false.
Then in the onsubmit handler of the form, you check the flag justValidate to decide the returning value and invoke the preventDefault() to stop the form to submit. As you know, the HTML5 form validation(and the GUI hint to user) is preformed before the onsubmit event, and even if the form is VALID you can stop the form submit by returning false or invoke preventDefault().
And, in HTML5 you have a method to check the form's validation: the form.checkValidity(), then in you can know if the form is validate or not in your code.
OK, here is the demo:
http://jsbin.com/buvuku/2/edit
var field = $("#field")
field.keyup(function(ev){
if(field[0].value.length < 10) {
field[0].setCustomValidity("characters less than 10")
}else if (field[0].value.length === 10) {
field[0].setCustomValidity("characters equal to 10")
}else if (field[0].value.length > 10 && field[0].value.length < 20) {
field[0].setCustomValidity("characters greater than 10 and less than 20")
}else if(field[0].validity.typeMismatch) {
field[0].setCustomValidity("wrong email message")
}else {
field[0].setCustomValidity("") // no more errors
}
field[0].reportValidity()
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="email" id="field">
Somewhat easy to make add or remove HTML5 validation to fieldsets.
$('form').each(function(){
// CLEAR OUT ALL THE HTML5 REQUIRED ATTRS
$(this).find('.required').attr('required', false);
// ADD THEM BACK TO THE CURRENT FIELDSET
// I'M JUST USING A CLASS TO IDENTIFY REQUIRED FIELDS
$(this).find('fieldset.current .required').attr('required', true);
$(this).submit(function(){
var current = $(this).find('fieldset.current')
var next = $(current).next()
// MOVE THE CURRENT MARKER
$(current).removeClass('current');
$(next).addClass('current');
// ADD THE REQUIRED TAGS TO THE NEXT PART
// NO NEED TO REMOVE THE OLD ONES
// SINCE THEY SHOULD BE FILLED OUT CORRECTLY
$(next).find('.required').attr('required', true);
});
});
I seem to find the trick:
Just remove the form target attribute, then use a submit button to validate the form and show hints, check if form valid via JavaScript, and then post whatever. The following code works for me:
<form>
<input name="foo" required>
<button id="submit">Submit</button>
</form>
<script>
$('#submit').click( function(e){
var isValid = true;
$('form input').map(function() {
isValid &= this.validity['valid'] ;
}) ;
if (isValid) {
console.log('valid!');
// post something..
} else
console.log('not valid!');
});
</script>
Html Code:
<form class="validateDontSubmit">
....
<button style="dislay:none">submit</button>
</form>
<button class="outside"></button>
javascript( using Jquery):
<script type="text/javascript">
$(document).on('submit','.validateDontSubmit',function (e) {
//prevent the form from doing a submit
e.preventDefault();
return false;
})
$(document).ready(function(){
// using button outside trigger click
$('.outside').click(function() {
$('.validateDontSubmit button').trigger('click');
});
});
</script>
Hope this will help you
For input field
<input id="PrimaryPhNumber" type="text" name="mobile" required
pattern="^[789]\d{9}$" minlenght="10" maxLength="10" placeholder="Eg: 9444400000"
class="inputBoxCss"/>
$('#PrimaryPhNumber').keyup(function (e) {
console.log(e)
let field=$(this)
if(Number(field.val()).toString()=="NaN"){
field.val('');
field.focus();
field[0].setCustomValidity('Please enter a valid phone number');
field[0].reportValidity()
$(":focus").css("border", "2px solid red");
}
})
$('#id').get(0).reportValidity();
This will trigger the input with ID specified. Use ".classname" for classes.
When there is a very complex (especially asynchronous) validation process, there is a simple workaround:
<form id="form1">
<input type="button" onclick="javascript:submitIfVeryComplexValidationIsOk()" />
<input type="submit" id="form1_submit_hidden" style="display:none" />
</form>
...
<script>
function submitIfVeryComplexValidationIsOk() {
var form1 = document.forms['form1']
if (!form1.checkValidity()) {
$("#form1_submit_hidden").click()
return
}
if (checkForVeryComplexValidation() === 'Ok') {
form1.submit()
} else {
alert('form is invalid')
}
}
</script>
Another way to resolve this problem:
$('input').oninvalid(function (event, errorMessage) {
event.target.focus();
});